From 3f04a2cee507203c6ad2922d9943542b27aef7a2 Mon Sep 17 00:00:00 2001 From: frevib Date: Wed, 19 Nov 2025 21:24:59 +0100 Subject: [PATCH 01/85] Add Gradle plugin bootstrap --- lang/java/.gitignore | 3 + lang/java/gradle-plugin/build.gradle.kts | 53 ++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + lang/java/gradle-plugin/gradlew | 251 ++++++++++++++++++ lang/java/gradle-plugin/gradlew.bat | 94 +++++++ lang/java/gradle-plugin/pom.xml | 75 ++++++ lang/java/gradle-plugin/settings.gradle.kts | 32 +++ .../apache/avro/gradle/plugin/GradlePlugin.kt | 11 + .../plugin/tasks/AbstractCompileTask.kt | 7 + .../gradle/plugin/tasks/CompileSchemaTask.kt | 48 ++++ .../org.apache.avro.gradle.plugin.properties | 1 + .../avro/gradle/plugin/SamplePluginTest.kt | 16 ++ pom.xml | 3 + 14 files changed, 601 insertions(+) create mode 100644 lang/java/gradle-plugin/build.gradle.kts create mode 100644 lang/java/gradle-plugin/gradle/wrapper/gradle-wrapper.jar create mode 100644 lang/java/gradle-plugin/gradle/wrapper/gradle-wrapper.properties create mode 100755 lang/java/gradle-plugin/gradlew create mode 100644 lang/java/gradle-plugin/gradlew.bat create mode 100644 lang/java/gradle-plugin/pom.xml create mode 100644 lang/java/gradle-plugin/settings.gradle.kts create mode 100644 lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt create mode 100644 lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt create mode 100644 lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt create mode 100644 lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties create mode 100644 lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt diff --git a/lang/java/.gitignore b/lang/java/.gitignore index 5a536dfcc82..250e79d5ba7 100644 --- a/lang/java/.gitignore +++ b/lang/java/.gitignore @@ -20,3 +20,6 @@ dependency-reduced-pom.xml mapred/userlogs/ tools/userlogs/ +gradle-plugin/build +gradle-plugin/.kotlin +gradle-plugin/.gradle diff --git a/lang/java/gradle-plugin/build.gradle.kts b/lang/java/gradle-plugin/build.gradle.kts new file mode 100644 index 00000000000..734258563fb --- /dev/null +++ b/lang/java/gradle-plugin/build.gradle.kts @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +plugins { + kotlin("jvm") version "2.2.10" + `java-gradle-plugin` +} + +group = "org.apache.avro" +version = "1.13.0-SNAPSHOT" + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + //implementation("org.gradle:gradle-tooling-api:7.1.1") + implementation("org.apache.avro:avro-compiler:${version}") + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} + + +gradlePlugin { + plugins { + create("gradlePlugin") { + id = "org.apache.avro.gradle.plugin" + implementationClass = "org.apache.avro.gradle.plugin.GradlePlugin" + } + } +} diff --git a/lang/java/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/lang/java/gradle-plugin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + 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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || 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="\\\"\\\"" + + +# 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, 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" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# 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/lang/java/gradle-plugin/gradlew.bat b/lang/java/gradle-plugin/gradlew.bat new file mode 100644 index 00000000000..db3a6ac207e --- /dev/null +++ b/lang/java/gradle-plugin/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +: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/lang/java/gradle-plugin/pom.xml b/lang/java/gradle-plugin/pom.xml new file mode 100644 index 00000000000..d6de00b7f48 --- /dev/null +++ b/lang/java/gradle-plugin/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + + avro-parent + org.apache.avro + 1.13.0-SNAPSHOT + ../pom.xml + + + avro-gradle-plugin + pom + + Apache Avro Gradle Plugin + Gradle plugin for Avro IDL and Specific API Compilers + + + ${project.parent.parent.basedir} + 3.3.0 + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + + run-gradle-task + package + + exec + + + ./gradlew + + assemble + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + + true + + + + + + diff --git a/lang/java/gradle-plugin/settings.gradle.kts b/lang/java/gradle-plugin/settings.gradle.kts new file mode 100644 index 00000000000..9a0fa94962e --- /dev/null +++ b/lang/java/gradle-plugin/settings.gradle.kts @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} +rootProject.name = "gradle-plugin" + + + diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt new file mode 100644 index 00000000000..39459b6a1bd --- /dev/null +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt @@ -0,0 +1,11 @@ +package org.apache.avro.gradle.plugin + +import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask +import org.gradle.api.Plugin +import org.gradle.api.Project + +abstract class GradlePlugin : Plugin { + override fun apply(project: Project) { + project.tasks.register("compile", CompileSchemaTask::class.java) + } +} diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt new file mode 100644 index 00000000000..dc08dacc7ec --- /dev/null +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -0,0 +1,7 @@ +package org.apache.avro.gradle.plugin.tasks + +import org.gradle.api.DefaultTask + +abstract class AbstractCompileTask : DefaultTask() { + +} diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt new file mode 100644 index 00000000000..27c3b1c0989 --- /dev/null +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt @@ -0,0 +1,48 @@ +package org.apache.avro.gradle.plugin.tasks + +import org.apache.avro.Schema +import org.apache.avro.SchemaParseException +import org.apache.avro.SchemaParser +import org.apache.avro.compiler.specific.SpecificCompiler +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.io.IOException +import java.util.* +import java.util.function.Function +import java.util.stream.Collectors + +abstract class CompileSchemaTask : AbstractCompileTask() { + /** + * A parser used to parse all schema files. Using a common parser will + * facilitate the import of external schemas. + */ + private val schemaParser = SchemaParser() + /** + * A set of Ant-like inclusion patterns used to select files from the source + * directory for processing. By default, the pattern `**/*.avdl` + * is used to select IDL files. + * + * @parameter + */ + private val includes = arrayOf("**/*.avdl") + + /** + * A set of Ant-like inclusion patterns used to select files from the source + * directory for processing. By default, the pattern `**/*.avdl` + * is used to select IDL files. + * + * @parameter + */ + private val testIncludes = arrayOf("**/*.avdl") + + @get:Input + abstract val gradleExtensionProperty: Property + + @TaskAction + fun compileSchema() { + println("Starting compilation for project name: '${project.name}'") + + } +} diff --git a/lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties b/lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties new file mode 100644 index 00000000000..13fbf0d2be3 --- /dev/null +++ b/lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties @@ -0,0 +1 @@ +implementation-class=org.apache.avro.gradle.plugin.GradlePlugin diff --git a/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt b/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt new file mode 100644 index 00000000000..becf280c8be --- /dev/null +++ b/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt @@ -0,0 +1,16 @@ +package org.apache.avro.gradle.plugin + +import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask +import org.gradle.testfixtures.ProjectBuilder +import kotlin.test.Test + + +class SamplePluginTest { + @Test + fun `plugin is applied correctly to the project`() { + val project = ProjectBuilder.builder().build() + project.pluginManager.apply("org.apache.avro.gradle.plugin") + assert(project.tasks.getByName("compile") is CompileSchemaTask) + } + +} diff --git a/pom.xml b/pom.xml index 70da6e44505..28f16a27ecd 100644 --- a/pom.xml +++ b/pom.xml @@ -524,6 +524,9 @@ lang/py/userlogs/** lang/py/docs/build/** lang/ruby/Manifest + **/.gradle/** + lang/java/gradle-plugin/gradle/** + lang/java/gradle-plugin/build/** CHANGES.txt DIST_README.txt From 7a18d79c56979c04749ced80bc855f1f59007804 Mon Sep 17 00:00:00 2001 From: frevib Date: Sat, 22 Nov 2025 21:36:00 +0100 Subject: [PATCH 02/85] Add test for generating Java code --- doc/package-lock.json | 927 ------------------ .../apache/avro/gradle/plugin/GradlePlugin.kt | 11 +- .../plugin/extension/GradlePluginExtension.kt | 20 + .../plugin/tasks/AbstractCompileTask.kt | 12 + .../gradle/plugin/tasks/CompileSchemaTask.kt | 147 ++- .../src/test/avro/AvdlClasspathImport.avdl | 26 + .../gradle-plugin/src/test/avro/User.avdl | 32 + .../gradle-plugin/src/test/avro/User.avpr | 41 + .../gradle-plugin/src/test/avro/User.avsc | 45 + .../directImport/PrivacyDirectImport.avsc | 7 + .../src/test/avro/extends/Custom.avsc | 18 + .../src/test/avro/imports/PrivacyImport.avsc | 7 + .../multipleSchemas/ApplicationEvent.avsc | 44 + .../avro/multipleSchemas/DocumentInfo.avsc | 19 + .../test/avro/multipleSchemas/MyResponse.avsc | 14 + .../src/test/avro/multipleSchemas/README.md | 8 + .../avro/gradle/plugin/SamplePluginTest.kt | 90 ++ .../apache/avro/mojo/AbstractAvroMojo.java | 16 - lang/java/pom.xml | 1 + 19 files changed, 516 insertions(+), 969 deletions(-) delete mode 100644 doc/package-lock.json create mode 100644 lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt create mode 100644 lang/java/gradle-plugin/src/test/avro/AvdlClasspathImport.avdl create mode 100644 lang/java/gradle-plugin/src/test/avro/User.avdl create mode 100644 lang/java/gradle-plugin/src/test/avro/User.avpr create mode 100644 lang/java/gradle-plugin/src/test/avro/User.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/directImport/PrivacyDirectImport.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/extends/Custom.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/imports/PrivacyImport.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/multipleSchemas/ApplicationEvent.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/multipleSchemas/DocumentInfo.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/multipleSchemas/MyResponse.avsc create mode 100644 lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md diff --git a/doc/package-lock.json b/doc/package-lock.json deleted file mode 100644 index 9153207d6cb..00000000000 --- a/doc/package-lock.json +++ /dev/null @@ -1,927 +0,0 @@ -{ - "name": "doc", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "autoprefixer": "^10.4.22", - "postcss": "^8.5.6", - "postcss-cli": "^11.0.1" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", - "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001755", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz", - "integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/dependency-graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", - "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.240", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", - "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-cli": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz", - "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.3.0", - "dependency-graph": "^1.0.0", - "fs-extra": "^11.0.0", - "picocolors": "^1.0.0", - "postcss-load-config": "^5.0.0", - "postcss-reporter": "^7.0.0", - "pretty-hrtime": "^1.0.3", - "read-cache": "^1.0.0", - "slash": "^5.0.0", - "tinyglobby": "^0.2.12", - "yargs": "^17.0.0" - }, - "bin": { - "postcss": "index.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-load-config": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", - "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1", - "yaml": "^2.4.2" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - } - } - }, - "node_modules/postcss-reporter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", - "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "thenby": "^1.3.4" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/thenby": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", - "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - } - } -} diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt index 39459b6a1bd..9c1ba5a4a2d 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt @@ -1,11 +1,20 @@ package org.apache.avro.gradle.plugin +import org.apache.avro.gradle.plugin.extension.GradlePluginExtension import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask import org.gradle.api.Plugin import org.gradle.api.Project abstract class GradlePlugin : Plugin { override fun apply(project: Project) { - project.tasks.register("compile", CompileSchemaTask::class.java) + val extension = project.extensions.create("avro", GradlePluginExtension::class.java) + + project.tasks.register("compile", CompileSchemaTask::class.java) { + it.srcDirectory.set(extension.srcDirectory) + it.outputDirectory.set(extension.outputDirectory) + it.includes.set(extension.includes) + it.doFirst { println("Starting compilation for project name: '${project.name}'") } + it.doLast { println("Finished compilation for project name: '${project.name}'") } + } } } diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt new file mode 100644 index 00000000000..3bb91497fb8 --- /dev/null +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt @@ -0,0 +1,20 @@ +package org.apache.avro.gradle.plugin.extension + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import javax.inject.Inject + +abstract class GradlePluginExtension @Inject constructor(objects: ObjectFactory) { + + + val srcDirectory: Property = objects.property(String::class.java) + + val outputDirectory: Property = objects.property(String::class.java) + + val includes: ListProperty = objects.listProperty(String::class.java) + + //// Input source directory + //val sourceDir: DirectoryProperty = objects.directoryProperty() + +} diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt index dc08dacc7ec..376c85a80ed 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -1,7 +1,19 @@ package org.apache.avro.gradle.plugin.tasks import org.gradle.api.DefaultTask +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.Input abstract class AbstractCompileTask : DefaultTask() { + /** + * A set of Ant-like inclusion patterns used to select files from the source + * directory for processing. By default, the pattern `**/*.avdl` + * is used to select IDL files. + * + * @parameter + */ + @get:Input + abstract val includes: ListProperty + } diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt index 27c3b1c0989..df27d0d1566 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt @@ -4,45 +4,142 @@ import org.apache.avro.Schema import org.apache.avro.SchemaParseException import org.apache.avro.SchemaParser import org.apache.avro.compiler.specific.SpecificCompiler +import org.gradle.api.Project +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.File import java.io.IOException -import java.util.* +import java.util.Arrays +import java.util.Comparator import java.util.function.Function import java.util.stream.Collectors abstract class CompileSchemaTask : AbstractCompileTask() { - /** - * A parser used to parse all schema files. Using a common parser will - * facilitate the import of external schemas. - */ - private val schemaParser = SchemaParser() - /** - * A set of Ant-like inclusion patterns used to select files from the source - * directory for processing. By default, the pattern `**/*.avdl` - * is used to select IDL files. - * - * @parameter - */ - private val includes = arrayOf("**/*.avdl") - - /** - * A set of Ant-like inclusion patterns used to select files from the source - * directory for processing. By default, the pattern `**/*.avdl` - * is used to select IDL files. - * - * @parameter - */ - private val testIncludes = arrayOf("**/*.avdl") @get:Input - abstract val gradleExtensionProperty: Property + abstract val srcDirectory: Property + + @get:Input + abstract val outputDirectory: Property + @TaskAction fun compileSchema() { - println("Starting compilation for project name: '${project.name}'") + println("Compiling schema...") + + val sourceDirectoryFullPath = + project.layout.projectDirectory.dir(srcDirectory.get()).asFile + + val outputDirectoryFullPath = project.layout.buildDirectory + .dir(outputDirectory).get().asFile + + + //val includes = arrayOf("**/*.avsc") + val excludes = emptyArray() + + + val res = project.getIncludedFiles2( + sourcePath = srcDirectory.get(), + excludes = excludes, + includes = includes.get().toTypedArray() + ) + + + //println("Included files: ${res.joinToString(",") }}") + //println("sourceDir: ${sourceDirectoryFullPath.path}") + //println("outputDir: ${outputDirectoryFullPath.path}") + + doCompile(res, sourceDirectoryFullPath, outputDirectoryFullPath) + + val files = File(outputDirectoryFullPath, "test").list().joinToString(",") + //val files2 = project.fileTree(File(outputDirectoryFullPath, "test")) + // + println("here are all files: ${files}") + + } + + fun Project.getIncludedFiles2( + sourcePath: String, + excludes: Array, + includes: Array + ): Array { + println("Including files from path: $sourcePath") + + val fullPath = project.layout.projectDirectory.dir(sourcePath) + + val files = fileTree(fullPath) { + it.include(*includes) + it.exclude(*excludes) + } + + return files.files + .map { it.relativeTo(fullPath.asFile).path } + .toTypedArray() + } + + protected fun doCompile(fileNames: Array, sourceDirectory: File, outputDirectory: File) { + val sourceFiles: List = Arrays.stream(fileNames) + .map { filename: String -> File(sourceDirectory, filename) }.collect(Collectors.toList()) + val sourceFileForModificationDetection = + sourceFiles.stream().filter { file: File? -> file!!.lastModified() > 0 } + .max(Comparator.comparing(Function { obj: File? -> obj!!.lastModified() })).orElse(null) + val schemas: MutableList? + + try { + // This is necessary to maintain backward-compatibility. If there are + // no imported files then isolate the schemas from each other, otherwise + // allow them to share a single schema so reuse and sharing of schema + // is possible. +// val parser = if (imports == null) SchemaParser() else schemaParser + val parser = SchemaParser() + for (sourceFile in sourceFiles) { + parser.parse(sourceFile) + } + schemas = parser.parsedNamedSchemas + + doCompile(sourceFileForModificationDetection, SpecificCompiler(schemas), outputDirectory) + } catch (ex: IOException) { + throw RuntimeException("IO ex: Error compiling a file in " + sourceDirectory + " to " + outputDirectory, ex) + } catch (ex: SchemaParseException) { + throw RuntimeException("SchemaParse ex Error compiling a file in " + sourceDirectory + " to " + outputDirectory, ex) + } + } + + // @Throws(IOException::class) + private fun doCompile( + sourceFileForModificationDetection: File, + compiler: SpecificCompiler, + outputDirectory: File + ) { + setCompilerProperties(compiler) +// try { +// for (customConversion in customConversions) { +// compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion)) +// } +// } catch (e: ClassNotFoundException) { +// throw IOException(e) +// } + compiler.compileToDestination(sourceFileForModificationDetection, outputDirectory) + } + + protected fun setCompilerProperties(compiler: SpecificCompiler) { +// compiler.setTemplateDir(templateDirectory) +// compiler.setStringType(GenericData.StringType.valueOf(stringType)) +// compiler.setFieldVisibility(getFieldVisibility()) +// compiler.setCreateOptionalGetters(createOptionalGetters) +// compiler.setGettersReturnOptional(gettersReturnOptional) +// compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly) +// compiler.setCreateSetters(createSetters) +// compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations) +// compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable) +// compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull) +// compiler.setEnableDecimalLogicalType(enableDecimalLogicalType) +// compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding")) +// compiler.setAdditionalVelocityTools(instantiateAdditionalVelocityTools()) +// compiler.setRecordSpecificClass(this.recordSpecificClass) +// compiler.setErrorSpecificClass(this.errorSpecificClass) } } diff --git a/lang/java/gradle-plugin/src/test/avro/AvdlClasspathImport.avdl b/lang/java/gradle-plugin/src/test/avro/AvdlClasspathImport.avdl new file mode 100644 index 00000000000..81bdb609445 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/AvdlClasspathImport.avdl @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +namespace test; + +import idl "avro/User.avdl"; + +/** Ignored Doc Comment */ +/** IDL User */ +record IdlUserWrapper { + union { null, test.IdlUser } wrapped; +} diff --git a/lang/java/gradle-plugin/src/test/avro/User.avdl b/lang/java/gradle-plugin/src/test/avro/User.avdl new file mode 100644 index 00000000000..98de878d9d0 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/User.avdl @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +@namespace("test") +protocol IdlTest { + + enum IdlPrivacy { + Public, Private + } + + record IdlUser { + union { null, string } id; + union { null, long } createdOn; + timestamp_ms modifiedOn; + union { null, IdlPrivacy } privacy; + } + +} diff --git a/lang/java/gradle-plugin/src/test/avro/User.avpr b/lang/java/gradle-plugin/src/test/avro/User.avpr new file mode 100644 index 00000000000..6dd8b9b8900 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/User.avpr @@ -0,0 +1,41 @@ +{ + "protocol" : "ProtocolTest", + "namespace" : "test", + "types" : [ + { + "type" : "enum", + "name" : "ProtocolPrivacy", + "symbols" : [ "Public", "Private"] + }, + { + "type": "record", + "namespace": "test", + "name": "ProtocolUser", + "doc": "User Test Bean", + "fields": [ + { + "name": "id", + "type": ["null", "string"], + "default": null + }, + { + "name": "createdOn", + "type": ["null", "long"], + "default": null + }, + { + "name": "privacy", + "type": ["null", "ProtocolPrivacy"], + "default": null + }, + { + "name": "modifiedOn", + "type": { + "type": "long", + "logicalType": "timestamp-millis" + } + } + ] + } + ] +} diff --git a/lang/java/gradle-plugin/src/test/avro/User.avsc b/lang/java/gradle-plugin/src/test/avro/User.avsc new file mode 100644 index 00000000000..a93e0d13f21 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/User.avsc @@ -0,0 +1,45 @@ +{ + "type": "record", + "namespace": "test", + "name": "SchemaUser", + "doc": "User Test Bean", + "fields": [ + { + "name": "id", + "type": ["null", "string"], + "default": null + }, + { + "name": "createdOn", + "type": ["null", "long"], + "default": null + }, + { + "name": "privacy", + "type": ["null", { + "type": "enum", + "name": "SchemaPrivacy", + "namespace": "test", + "symbols" : ["Public","Private"] + }], + "default": null + }, + { + "name": "privacyImported", + "type": ["null", "test.PrivacyImport"], + "default": null + }, + { + "name": "privacyDirectImport", + "type": ["null", "test.PrivacyDirectImport"], + "default": null + }, + { + "name": "time", + "type": { + "type": "long", + "logicalType": "timestamp-millis" + } + } + ] +} diff --git a/lang/java/gradle-plugin/src/test/avro/directImport/PrivacyDirectImport.avsc b/lang/java/gradle-plugin/src/test/avro/directImport/PrivacyDirectImport.avsc new file mode 100644 index 00000000000..a5b62959206 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/directImport/PrivacyDirectImport.avsc @@ -0,0 +1,7 @@ +{ + "type": "enum", + "namespace": "test", + "name": "PrivacyDirectImport", + "doc": "Privacy Test Enum", + "symbols" : ["Public","Private"] +} diff --git a/lang/java/gradle-plugin/src/test/avro/extends/Custom.avsc b/lang/java/gradle-plugin/src/test/avro/extends/Custom.avsc new file mode 100644 index 00000000000..63056e5d17f --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/extends/Custom.avsc @@ -0,0 +1,18 @@ +{ + "type": "record", + "namespace": "test", + "name": "SchemaCustom", + "doc": "Custom Test Bean", + "fields": [ + { + "name": "id", + "type": ["null", "string"], + "default": null + }, + { + "name": "createdOn", + "type": ["null", "long"], + "default": null + } + ] +} diff --git a/lang/java/gradle-plugin/src/test/avro/imports/PrivacyImport.avsc b/lang/java/gradle-plugin/src/test/avro/imports/PrivacyImport.avsc new file mode 100644 index 00000000000..f454f1d3996 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/imports/PrivacyImport.avsc @@ -0,0 +1,7 @@ +{ + "type": "enum", + "namespace": "test", + "name": "PrivacyImport", + "doc": "Privacy Test Enum", + "symbols" : ["Public","Private"] +} diff --git a/lang/java/gradle-plugin/src/test/avro/multipleSchemas/ApplicationEvent.avsc b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/ApplicationEvent.avsc new file mode 100644 index 00000000000..efc7fbf6139 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/ApplicationEvent.avsc @@ -0,0 +1,44 @@ +{ + "namespace": "model", + "type": "record", + "doc": "", + "name": "ApplicationEvent", + "fields": [ + { + "name": "applicationId", + "type": "string", + "doc": "Application ID" + }, + { + "name": "status", + "type": "string", + "doc": "Application Status" + }, + { + "name": "documents", + "type": ["null", { + "type": "array", + "items": "model.DocumentInfo" + }], + "doc": "", + "default": null + }, + { + "name": "response", + "type": { + "namespace": "model", + "type": "record", + "doc": "", + "name": "MyResponse", + "fields": [ + { + "name": "isSuccessful", + "type": "boolean", + "doc": "Indicator for successful or unsuccessful call" + } + ] + } + } + ] + +} diff --git a/lang/java/gradle-plugin/src/test/avro/multipleSchemas/DocumentInfo.avsc b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/DocumentInfo.avsc new file mode 100644 index 00000000000..95dd4243ea6 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/DocumentInfo.avsc @@ -0,0 +1,19 @@ +{ + "namespace": "model", + "type": "record", + "doc": "", + "name": "DocumentInfo", + "fields": [ + { + "name": "documentId", + "type": "string", + "doc": "Document ID" + }, + { + "name": "filePath", + "type": "string", + "doc": "Document Path" + } + ] + +} diff --git a/lang/java/gradle-plugin/src/test/avro/multipleSchemas/MyResponse.avsc b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/MyResponse.avsc new file mode 100644 index 00000000000..ac6d08291d9 --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/MyResponse.avsc @@ -0,0 +1,14 @@ +{ + "namespace": "model", + "type": "record", + "doc": "", + "name": "MyResponse", + "fields": [ + { + "name": "isSuccessful", + "type": "boolean", + "doc": "Indicator for successful or unsuccessful call" + } + ] + +} diff --git a/lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md new file mode 100644 index 00000000000..fe3541b660e --- /dev/null +++ b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md @@ -0,0 +1,8 @@ +## test for parsing multiple files. +This folder aims to test `public List Schema.parse(Iterable sources) throws IOException` method. + +The objective is to check that a record schema define in a file can be use in another record schema as a field type. +Here, ApplicationEvent.avsc file contains a field of type DocumentInfo, defined in file DocumentInfo.avsc. + +The is written at TestSchema.testParseMultipleFile. + diff --git a/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt b/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt index becf280c8be..81103c19c0c 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SamplePluginTest.kt @@ -2,10 +2,22 @@ package org.apache.avro.gradle.plugin import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask import org.gradle.testfixtures.ProjectBuilder +import org.gradle.testkit.runner.GradleRunner +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Arrays +import java.util.HashSet +import kotlin.io.path.writeText import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue class SamplePluginTest { + @Test fun `plugin is applied correctly to the project`() { val project = ProjectBuilder.builder().build() @@ -13,4 +25,82 @@ class SamplePluginTest { assert(project.tasks.getByName("compile") is CompileSchemaTask) } + @Test + fun `plugin executes greet task successfully2`() { + // Create a temporary project directory using NIO + val projectDir: Path = Files.createTempDirectory("gradle-plugin-test-") + + // Write minimal settings and build files + val settingsFile = projectDir.resolve("settings.gradle.kts") + val buildFile = projectDir.resolve("build.gradle.kts") + + + // 1. Find test/avro dir inside THIS project + val localAvroDir = File("src/test/avro") + require(localAvroDir.exists()) { "src/test/avro not found" } + + // ---- 2. Create test project structure ---- + val testResourcesDir = File(projectDir.toFile(), "src/test/avro") + testResourcesDir.mkdirs() + + // ---- 3. Copy resource files to test project ---- + localAvroDir.copyRecursively(testResourcesDir, overwrite = true) + + + + settingsFile.writeText("") + buildFile.writeText( + """ + plugins { + id("org.apache.avro.gradle.plugin") + } + + avro { + srcDirectory = "src/test/avro" + outputDirectory = "generated-sources/avro" + includes = listOf("**/*.avsc") + } + """.trimIndent() + ) + + val outputDirectory = File(projectDir.toFile(), "build/generated-sources/avro/test") + + // Run Gradle using TestKit + val result = GradleRunner.create() + .withProjectDir(projectDir.toFile()) // still needs File for GradleRunner + .withArguments("compile") + .withPluginClasspath() + .build() + + println("result.output: ${result.output}") + + val expectedFiles = setOf( + "SchemaPrivacy.java", + "SchemaUser.java", + "PrivacyImport.java", + "SchemaCustom.java", + "PrivacyDirectImport.java" + ) + + // Verify output + assertFilesExist(outputDirectory, expectedFiles) + + //assertTrue(result.output.contains("Hello from project")) + //assertTrue(result.output.contains("BUILD SUCCESSFUL")) + + // Optional: clean up directory + projectDir.toFile().deleteRecursively() + } + + fun assertFilesExist(directory: File, expectedFiles: Set) { + assertNotNull(directory) + assertTrue(directory.exists(), "Directory " + directory.toString() + " does not exists") + assertNotNull(expectedFiles) + assertTrue(expectedFiles.size > 0) + + val filesInDirectory: Set = HashSet(Arrays.asList(*directory.list())) + + assertEquals(expectedFiles, filesInDirectory) + } + } diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java index 5615df36e3e..c9b2b3a60be 100644 --- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java +++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java @@ -315,22 +315,6 @@ private String[] getIncludedFiles(String absPath, String[] excludes, String[] in fs.setDirectory(absPath); fs.setFollowSymlinks(false); - // exclude imports directory since it has already been compiled. - if (imports != null) { - String importExclude = null; - - for (String importFile : this.imports) { - File file = new File(importFile); - - if (file.isDirectory()) { - importExclude = file.getName() + "/**"; - } else if (file.isFile()) { - importExclude = "**/" + file.getName(); - } - - fs.addExclude(importExclude); - } - } for (String include : includes) { fs.addInclude(include); } diff --git a/lang/java/pom.xml b/lang/java/pom.xml index 64c4b7b098a..d5f56839b49 100644 --- a/lang/java/pom.xml +++ b/lang/java/pom.xml @@ -334,6 +334,7 @@ com.diffplug.spotless spotless-maven-plugin + + true + diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/GradlePlugin.kt similarity index 93% rename from lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt rename to lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/GradlePlugin.kt index 05b46415d89..d8dc623d6bb 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/GradlePlugin.kt @@ -1,7 +1,7 @@ -package org.apache.avro.gradle.plugin +package eu.eventloopsoftware.avro.gradle.plugin -import org.apache.avro.gradle.plugin.extension.GradlePluginExtension -import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask +import eu.eventloopsoftware.avro.gradle.plugin.extension.GradlePluginExtension +import eu.eventloopsoftware.avro.gradle.plugin.tasks.CompileSchemaTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginExtension @@ -23,7 +23,7 @@ abstract class GradlePlugin : Plugin { runPlugin(compileSchemaTask, extension, project, sourceDirectory, outputDirectory) } - project.tasks.register("avroGenerateTestJavaClasses", CompileSchemaTask::class.java) {compileSchemaTask -> + project.tasks.register("avroGenerateTestJavaClasses", CompileSchemaTask::class.java) { compileSchemaTask -> val sourceDirectory = extension.testSourceDirectory.get() val outputDirectory = extension.testOutputDirectory.get() runPlugin(compileSchemaTask, extension, project, sourceDirectory, outputDirectory) @@ -67,7 +67,7 @@ abstract class GradlePlugin : Plugin { addGeneratedSourcesToProject(project, compileTask.outputDirectory.get()) } - SchemaType.idl -> TODO() + SchemaType.protocol -> TODO() } } @@ -83,5 +83,5 @@ abstract class GradlePlugin : Plugin { enum class SchemaType { schema, - idl + protocol } diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/GradlePluginExtension.kt similarity index 99% rename from lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt rename to lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/GradlePluginExtension.kt index 763b4e40005..6916bc7e085 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/extension/GradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/GradlePluginExtension.kt @@ -1,4 +1,4 @@ -package org.apache.avro.gradle.plugin.extension +package eu.eventloopsoftware.avro.gradle.plugin.extension import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt similarity index 95% rename from lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt rename to lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt index 81be641e3ea..e84494be76b 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -1,6 +1,5 @@ -package org.apache.avro.gradle.plugin.tasks +package eu.eventloopsoftware.avro.gradle.plugin.tasks -import org.gradle.api.DefaultTask import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input diff --git a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileSchemaTask.kt similarity index 97% rename from lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt rename to lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileSchemaTask.kt index 69dff11cf21..762635979ae 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileSchemaTask.kt @@ -1,11 +1,10 @@ -package org.apache.avro.gradle.plugin.tasks +package eu.eventloopsoftware.avro.gradle.plugin.tasks import org.apache.avro.SchemaParseException import org.apache.avro.SchemaParser import org.apache.avro.compiler.specific.SpecificCompiler import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility import org.apache.avro.generic.GenericData -import org.gradle.api.Project import org.gradle.api.file.FileTree import org.gradle.api.provider.Property import org.gradle.api.tasks.TaskAction @@ -114,9 +113,9 @@ abstract class CompileSchemaTask : AbstractCompileTask() { private fun getFieldV(): FieldVisibility { try { - val upperCaseFieldVisibility = fieldVisibility.get().trim().uppercase(Locale.getDefault()) + val upperCaseFieldVisibility = fieldVisibility.get().trim().uppercase() return FieldVisibility.valueOf(upperCaseFieldVisibility) - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { logger.warn("Could not parse field visibility, using PRIVATE") return FieldVisibility.PRIVATE } diff --git a/lang/java/gradle-plugin/src/main/resources/META-INF/eu.eventloopsoftware.properties b/lang/java/gradle-plugin/src/main/resources/META-INF/eu.eventloopsoftware.properties new file mode 100644 index 00000000000..adbba26c642 --- /dev/null +++ b/lang/java/gradle-plugin/src/main/resources/META-INF/eu.eventloopsoftware.properties @@ -0,0 +1 @@ +implementation-class=eu.eventloopsoftware.avro.gradle.plugin.GradlePlugin diff --git a/lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties b/lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties deleted file mode 100644 index 13fbf0d2be3..00000000000 --- a/lang/java/gradle-plugin/src/main/resources/META-INF/org.apache.avro.gradle.plugin.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=org.apache.avro.gradle.plugin.GradlePlugin diff --git a/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt similarity index 96% rename from lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt rename to lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt index 7da2008d75e..fde87579405 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt @@ -1,4 +1,4 @@ -package org.apache.avro.gradle.plugin +package eu.eventloopsoftware.avro.gradle.plugin import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome @@ -38,7 +38,7 @@ class SchemaCompileTaskTest { tempBuildFile.writeText( """ plugins { - id("org.apache.avro.avro-gradle-plugin") + id("eu.eventloopsoftware.avro-gradle-plugin") } avro { @@ -96,7 +96,7 @@ class SchemaCompileTaskTest { tempBuildFile.writeText( """ plugins { - id("org.apache.avro.avro-gradle-plugin") + id("eu.eventloopsoftware.avro-gradle-plugin") } avro { @@ -162,7 +162,7 @@ class SchemaCompileTaskTest { tempBuildFile.writeText( """ plugins { - id("org.apache.avro.avro-gradle-plugin") + id("eu.eventloopsoftware.avro-gradle-plugin") } avro { @@ -221,7 +221,7 @@ class SchemaCompileTaskTest { tempBuildFile.writeText( """ plugins { - id("org.apache.avro.avro-gradle-plugin") + id("eu.eventloopsoftware.avro-gradle-plugin") } avro { From de150a218ee08fb22790c5c914248dd2615c2108 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 5 Jan 2026 15:47:28 +0100 Subject: [PATCH 41/85] AVRO-4223 Add questions to FAQ --- lang/java/gradle-plugin/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lang/java/gradle-plugin/README.md b/lang/java/gradle-plugin/README.md index 7dbc3ca9101..725ba72e210 100644 --- a/lang/java/gradle-plugin/README.md +++ b/lang/java/gradle-plugin/README.md @@ -56,4 +56,16 @@ tasks.named("compileKotlin") { dependsOn(tasks.named("avroGenerateJavaClasses")) ## Example project that uses avro-gradle-plugin https://codeberg.org/frevib/use-gradle-plugin-test +## FAQ + +#### How can I benefit from Kotlin's null safety? +Use `createNullSafeAnnotations = true` and Java getters will be annotated with +`@org.jetbrains.annotations.NotNull`/ `@org.jetbrains.annotations.Nullable`. This way +Kotlin will recognize which value is nullable. + +#### When I update my schemas and generate code, the Java classes stay the same +Yes, for now you have to `./gradlew clean` every time you update your Avro schemas. + + + From 485dbfdda1d3cb1a8ec2e437c3cef3dfaf80fd7f Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 5 Jan 2026 16:01:16 +0100 Subject: [PATCH 42/85] AVRO-4223 Cleanup readme --- lang/java/gradle-plugin/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lang/java/gradle-plugin/README.md b/lang/java/gradle-plugin/README.md index 725ba72e210..033c1803732 100644 --- a/lang/java/gradle-plugin/README.md +++ b/lang/java/gradle-plugin/README.md @@ -7,19 +7,21 @@ Gradle plugin that generates Java code from Avro schemas ### Add avro extension In `build.gradle.kts`: -Add plugin +### Add plugin ```kotlin plugins { id("eu.eventloopsoftware.avro-gradle-plugin") version "0.0.2" } ``` -Add Avro dependency +### Add Avro dependency ```kotlin implementation("org.apache.avro:avro:1.12.1") ``` -Configure Avro Gradle plugin + +### Configure Avro Gradle plugin + ```kotlin avro { sourceDirectory = "src/main/avro" @@ -27,6 +29,7 @@ avro { } ``` +### Configure Maven dependency repository In `settings.gradle.kts`: Plugin is published on Maven Central: @@ -39,8 +42,6 @@ pluginManagement { } ``` - - ### Add a task hook For Intellij to recognize the newly generated Java files add this to `build.gradle.kts`: From 91807d8a197bdc4ec775ba9e7ea942c9eb118436 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 5 Jan 2026 16:19:20 +0100 Subject: [PATCH 43/85] AVRO-4223 Use build instead fo assemble Remove spotless as this is not Java code --- lang/java/gradle-plugin/pom.xml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lang/java/gradle-plugin/pom.xml b/lang/java/gradle-plugin/pom.xml index 318150cd26d..4590ededd71 100644 --- a/lang/java/gradle-plugin/pom.xml +++ b/lang/java/gradle-plugin/pom.xml @@ -57,20 +57,13 @@ ./gradlew - assemble + build + -i - - - com.diffplug.spotless - spotless-maven-plugin - - true - - From 916a73257a3b83a9a3bd05304753e24998467851 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 5 Jan 2026 16:20:51 +0100 Subject: [PATCH 44/85] AVRO-4223 Revert skip --- lang/java/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/lang/java/pom.xml b/lang/java/pom.xml index 7cbf4870bf7..3e503842bbc 100644 --- a/lang/java/pom.xml +++ b/lang/java/pom.xml @@ -334,7 +334,6 @@ com.diffplug.spotless spotless-maven-plugin - true + true @@ -49,7 +51,7 @@ - run-gradle-task + run-gradle-task-assemble compile exec @@ -63,7 +65,7 @@ - run-gradle-task + run-gradle-task-test test exec @@ -77,7 +79,7 @@ - run-gradle-task + run-gradle-task-build package exec @@ -91,7 +93,7 @@ - run-gradle-task + run-gradle-task-publish deploy exec @@ -109,4 +111,77 @@ + + + windows + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + run-gradle-task-assemble + compile + + exec + + + gradlew.bat + + assemble + -i + + + + + run-gradle-task-test + test + + exec + + + gradlew.bat + + test + -i + + + + + run-gradle-task-build + package + + exec + + + gradlew.bat + + build + -i + + + + + run-gradle-task-publish + deploy + + exec + + + gradlew.bat + + publishPlugins + -i + + + + + + + + + + diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt index 18675553a34..f333403e4cd 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt @@ -1,6 +1,5 @@ package eu.eventloopsoftware.avro.gradle.plugin.extension -import org.gradle.api.file.RegularFile import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property diff --git a/lang/java/pom.xml b/lang/java/pom.xml index 87f3c200fed..3f99c6d98a9 100644 --- a/lang/java/pom.xml +++ b/lang/java/pom.xml @@ -78,6 +78,7 @@ idl compiler maven-plugin + gradle-plugin ipc ipc-jetty ipc-netty From 9e7bd24a8dfaef4d638401b4cbf2389979b0aacb Mon Sep 17 00:00:00 2001 From: H de Vries Date: Sun, 11 Jan 2026 13:12:27 +0100 Subject: [PATCH 68/85] AVRO-4223 Remove unused file --- .../gradle-plugin/src/test/avro/multipleSchemas/README.md | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md diff --git a/lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md b/lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md deleted file mode 100644 index fe3541b660e..00000000000 --- a/lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md +++ /dev/null @@ -1,8 +0,0 @@ -## test for parsing multiple files. -This folder aims to test `public List Schema.parse(Iterable sources) throws IOException` method. - -The objective is to check that a record schema define in a file can be use in another record schema as a field type. -Here, ApplicationEvent.avsc file contains a field of type DocumentInfo, defined in file DocumentInfo.avsc. - -The is written at TestSchema.testParseMultipleFile. - From 1cf876263b27835c910b5068186575261fbb256a Mon Sep 17 00:00:00 2001 From: H de Vries Date: Sun, 11 Jan 2026 14:09:38 +0100 Subject: [PATCH 69/85] AVRO-4223 Template directory is inside the classpath, so it should not be an @get:InputDirectory --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 2 +- .../gradle/plugin/extension/AvroGradlePluginExtension.kt | 2 +- .../avro/gradle/plugin/tasks/AbstractCompileTask.kt | 6 +++--- .../avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt | 3 +-- .../avro/gradle/plugin/SchemaCompileTaskTest.kt | 3 +-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index c14b382b54d..b38cd3cf467 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -83,7 +83,7 @@ abstract class AvroGradlePlugin : Plugin { compileTask.testExcludes.set(extension.testExcludes) compileTask.stringType.set(extension.stringType) compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get()) - compileTask.templateDirectory.set(project.layout.projectDirectory.dir(extension.templateDirectory)) + compileTask.templateDirectory.set(extension.templateDirectory) compileTask.recordSpecificClass.set(extension.recordSpecificClass) compileTask.errorSpecificClass.set(extension.errorSpecificClass) compileTask.createOptionalGetters.set(extension.createOptionalGetters) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt index f333403e4cd..f534e3dc4ae 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt @@ -21,7 +21,7 @@ abstract class AvroGradlePluginExtension @Inject constructor(objects: ObjectFact /** * A list of zip files that contain Avro schema files. All generated - * Java classes are added to the classpath. + * Java classes are added to the classpath. *

* Defaults to {@code emptyList()}. */ diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt index a81e8aeb91f..3db4bbf4ddf 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -2,6 +2,7 @@ package eu.eventloopsoftware.avro.gradle.plugin.tasks import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property @@ -24,9 +25,8 @@ abstract class AbstractCompileTask : DefaultTask() { @get:Input abstract val velocityToolsClassesNames: ListProperty - @get:InputDirectory - @get:Internal - abstract val templateDirectory: DirectoryProperty + @get:Input + abstract val templateDirectory: Property @get:Input abstract val recordSpecificClass: Property diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt index 25403b7d171..654d6a78026 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt @@ -91,8 +91,7 @@ abstract class CompileAvroSchemaTask : AbstractCompileTask() { private fun setCompilerProperties(compiler: SpecificCompiler) { - println("templateDirectory: ${templateDirectory.get().asFile.absolutePath}") - compiler.setTemplateDir(templateDirectory.get().asFile.absolutePath + File.separator) + compiler.setTemplateDir(templateDirectory.get()) compiler.setStringType(GenericData.StringType.valueOf(stringType.get())) compiler.setFieldVisibility(getFieldV()) compiler.setCreateOptionalGetters(createOptionalGetters.get()) diff --git a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt index ba6d2aebdec..a415ea8f96c 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt @@ -137,7 +137,6 @@ class SchemaCompileTaskTest { val tempSettingsFile = tempDir.resolve("settings.gradle.kts") val tempBuildFile = tempDir.resolve("build.gradle.kts") val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() - //val bla = tempDir.resolve("src/aap").createDirectories() val tempVelocityToolClassesDir = tempDir.resolve("src/test/resources/templates").createDirectories() val testAvroFilesDir = Path.of("src/test/avro") @@ -169,7 +168,7 @@ class SchemaCompileTaskTest { schemaType = "schema" sourceDirectory = "$testAvroFilesDir" outputDirectory = "$testAvroOutPutDir" - templateDirectory = "$testVelocityToolClassesDir" + templateDirectory = "${tempDir.resolve(testVelocityToolClassesDir).toString() + "/"}" velocityToolsClassesNames = listOf("java.lang.String") } """.trimIndent() From 6183c73f3a1663f57bb6aab81cd14b22d1ed123e Mon Sep 17 00:00:00 2001 From: H de Vries Date: Sun, 11 Jan 2026 14:39:37 +0100 Subject: [PATCH 70/85] AVRO-4223 Move reusable methods to abstract class --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 89 ++++++++------- .../plugin/tasks/AbstractCompileTask.kt | 102 +++++++++++++++++- .../plugin/tasks/CompileAvroSchemaTask.kt | 93 ---------------- 3 files changed, 143 insertions(+), 141 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index b38cd3cf467..6cdf8ecd6cf 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -1,6 +1,7 @@ package eu.eventloopsoftware.avro.gradle.plugin import eu.eventloopsoftware.avro.gradle.plugin.extension.AvroGradlePluginExtension +import eu.eventloopsoftware.avro.gradle.plugin.tasks.AbstractCompileTask import eu.eventloopsoftware.avro.gradle.plugin.tasks.CompileAvroSchemaTask import org.gradle.api.Plugin import org.gradle.api.Project @@ -58,17 +59,35 @@ abstract class AvroGradlePlugin : Plugin { } private fun configurePlugin( - compileTask: CompileAvroSchemaTask, + compileTask: AbstractCompileTask, extension: AvroGradlePluginExtension, project: Project, sourceDirectory: Property, outputDirectory: Property, classPathFiles: Set ) { - val schemaType: SchemaType = SchemaType.valueOf(extension.schemaType.get()) - - when (schemaType) { - SchemaType.schema -> { + compileTask.outputDirectory.set(project.layout.buildDirectory.dir(outputDirectory)) + compileTask.fieldVisibility.set(extension.fieldVisibility) + compileTask.testExcludes.set(extension.testExcludes) + compileTask.stringType.set(extension.stringType) + compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get()) + compileTask.templateDirectory.set(extension.templateDirectory) + compileTask.recordSpecificClass.set(extension.recordSpecificClass) + compileTask.errorSpecificClass.set(extension.errorSpecificClass) + compileTask.createOptionalGetters.set(extension.createOptionalGetters) + compileTask.gettersReturnOptional.set(extension.gettersReturnOptional) + compileTask.createSetters.set(extension.createSetters) + compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations) + compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable) + compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull) + compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly) + compileTask.customConversions.set(extension.customConversions) + compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories) + compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType) + + + when (compileTask) { + is CompileAvroSchemaTask -> { compileTask.schemaFiles.from(project.fileTree(sourceDirectory).apply { setIncludes(listOf("**/*.avsc")) setExcludes(extension.excludes.get()) @@ -78,54 +97,32 @@ abstract class AvroGradlePlugin : Plugin { project.zipTree(zipPath).matching { it.include(setOf("**/*.avsc")) } ) } - compileTask.outputDirectory.set(project.layout.buildDirectory.dir(outputDirectory)) - compileTask.fieldVisibility.set(extension.fieldVisibility) - compileTask.testExcludes.set(extension.testExcludes) - compileTask.stringType.set(extension.stringType) - compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get()) - compileTask.templateDirectory.set(extension.templateDirectory) - compileTask.recordSpecificClass.set(extension.recordSpecificClass) - compileTask.errorSpecificClass.set(extension.errorSpecificClass) - compileTask.createOptionalGetters.set(extension.createOptionalGetters) - compileTask.gettersReturnOptional.set(extension.gettersReturnOptional) - compileTask.createSetters.set(extension.createSetters) - compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations) - compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable) - compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull) - compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly) - compileTask.customConversions.set(extension.customConversions) - compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories) - compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType) - compileTask.runtimeClassPathFileCollection.from(classPathFiles) + } - SchemaType.protocol -> TODO() + else -> TODO() } - } - private fun addGeneratedSourcesToJavaProject( - project: Project, - compileTask: TaskProvider, - compileTestTask: TaskProvider - ) { - val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets - sourceSets.getByName("main").java.srcDir(compileTask.flatMap { it.outputDirectory }) - sourceSets.getByName("test").java.srcDir(compileTestTask.flatMap { it.outputDirectory }) } +} - private fun addGeneratedSourcesToKotlinProject( - project: Project, - compileTask: TaskProvider, - compileTestTask: TaskProvider, - ) { - val sourceSets = project.extensions.getByType(KotlinJvmExtension::class.java).sourceSets - sourceSets.getByName("main").kotlin.srcDir(compileTask.flatMap { it.outputDirectory }) - sourceSets.getByName("test").kotlin.srcDir(compileTestTask.flatMap { it.outputDirectory }) - } +private fun addGeneratedSourcesToJavaProject( + project: Project, + compileTask: TaskProvider, + compileTestTask: TaskProvider +) { + val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets + sourceSets.getByName("main").java.srcDir(compileTask.flatMap { it.outputDirectory }) + sourceSets.getByName("test").java.srcDir(compileTestTask.flatMap { it.outputDirectory }) } -enum class SchemaType { - schema, - protocol +private fun addGeneratedSourcesToKotlinProject( + project: Project, + compileTask: TaskProvider, + compileTestTask: TaskProvider, +) { + val sourceSets = project.extensions.getByType(KotlinJvmExtension::class.java).sourceSets + sourceSets.getByName("main").kotlin.srcDir(compileTask.flatMap { it.outputDirectory }) + sourceSets.getByName("test").kotlin.srcDir(compileTestTask.flatMap { it.outputDirectory }) } diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt index 3db4bbf4ddf..95e9c2489b6 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -1,12 +1,23 @@ package eu.eventloopsoftware.avro.gradle.plugin.tasks +import org.apache.avro.LogicalTypes +import org.apache.avro.compiler.specific.SpecificCompiler +import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility +import org.apache.avro.generic.GenericData import org.gradle.api.DefaultTask +import org.gradle.api.GradleException import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import java.io.File +import java.io.IOException +import java.net.URL +import java.net.URLClassLoader abstract class AbstractCompileTask : DefaultTask() { @@ -64,5 +75,92 @@ abstract class AbstractCompileTask : DefaultTask() { @get:Input abstract val enableDecimalLogicalType: Property + @get:InputFiles + @get:Classpath + abstract val runtimeClassPathFileCollection: ConfigurableFileCollection + + protected fun doCompile( + sourceFileForModificationDetection: File?, + compiler: SpecificCompiler, + outputDirectory: File + ) { + setCompilerProperties(compiler) + try { + for (customConversion in customConversions.get()) { + compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion)) + } + } catch (e: ClassNotFoundException) { + throw IOException(e) + } + compiler.compileToDestination(sourceFileForModificationDetection, outputDirectory) + } + + + private fun setCompilerProperties(compiler: SpecificCompiler) { + compiler.setTemplateDir(templateDirectory.get()) + compiler.setStringType(GenericData.StringType.valueOf(stringType.get())) + compiler.setFieldVisibility(getFieldV()) + compiler.setCreateOptionalGetters(createOptionalGetters.get()) + compiler.setGettersReturnOptional(gettersReturnOptional.get()) + compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly.get()) + compiler.setCreateSetters(createSetters.get()) + compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations.get()) + compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable.get()) + compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull.get()) + compiler.setEnableDecimalLogicalType(enableDecimalLogicalType.get()) + // TODO: likely not needed +// compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding")) + compiler.setAdditionalVelocityTools(instantiateAdditionalVelocityTools(velocityToolsClassesNames.get())) + compiler.setRecordSpecificClass(recordSpecificClass.get()) + compiler.setErrorSpecificClass(errorSpecificClass.get()) + } + + private fun getFieldV(): FieldVisibility { + try { + val upperCaseFieldVisibility = fieldVisibility.get().trim().uppercase() + return FieldVisibility.valueOf(upperCaseFieldVisibility) + } catch (_: IllegalArgumentException) { + logger.warn("Could not parse field visibility: ${fieldVisibility.get()}, using PRIVATE") + return FieldVisibility.PRIVATE + } + } + + private fun instantiateAdditionalVelocityTools(velocityToolsClassesNames: List): List { + return velocityToolsClassesNames.map { velocityToolClassName -> + try { + Class.forName(velocityToolClassName) + .getDeclaredConstructor() + .newInstance() + } catch (e: Exception) { + throw RuntimeException(e) + } + } + } + + protected fun loadLogicalTypesFactories() = + createClassLoader().use { classLoader -> + customLogicalTypeFactories.get().forEach { factory -> + try { + @Suppress("UNCHECKED_CAST") + val logicalTypeFactoryClass = + classLoader.loadClass(factory) as Class + val factoryInstance = logicalTypeFactoryClass.getDeclaredConstructor().newInstance() + LogicalTypes.register(factoryInstance) + } catch (e: ClassNotFoundException) { + throw IOException(e) + } catch (e: ReflectiveOperationException) { + throw GradleException("Failed to instantiate logical type factory class: $factory", e) + } + } + } + + private fun createClassLoader(): URLClassLoader { + val urls = classPathFileCollection() + return URLClassLoader(urls.toTypedArray(), Thread.currentThread().contextClassLoader) + } + + private fun classPathFileCollection(): List = + runtimeClassPathFileCollection.files.map { it.toURI().toURL() } + } diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt index 654d6a78026..c2d305031c8 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt @@ -1,21 +1,14 @@ package eu.eventloopsoftware.avro.gradle.plugin.tasks -import org.apache.avro.LogicalTypes import org.apache.avro.SchemaParseException import org.apache.avro.SchemaParser import org.apache.avro.compiler.specific.SpecificCompiler -import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility -import org.apache.avro.generic.GenericData -import org.gradle.api.GradleException import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction import java.io.File import java.io.IOException -import java.net.URL -import java.net.URLClassLoader abstract class CompileAvroSchemaTask : AbstractCompileTask() { @@ -23,9 +16,6 @@ abstract class CompileAvroSchemaTask : AbstractCompileTask() { @get:SkipWhenEmpty abstract val schemaFiles: ConfigurableFileCollection - @get:InputFiles - @get:Classpath - abstract val runtimeClassPathFileCollection: ConfigurableFileCollection @TaskAction fun compileSchema() { @@ -73,87 +63,4 @@ abstract class CompileAvroSchemaTask : AbstractCompileTask() { } } - private fun doCompile( - sourceFileForModificationDetection: File?, - compiler: SpecificCompiler, - outputDirectory: File - ) { - setCompilerProperties(compiler) - try { - for (customConversion in customConversions.get()) { - compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion)) - } - } catch (e: ClassNotFoundException) { - throw IOException(e) - } - compiler.compileToDestination(sourceFileForModificationDetection, outputDirectory) - } - - - private fun setCompilerProperties(compiler: SpecificCompiler) { - compiler.setTemplateDir(templateDirectory.get()) - compiler.setStringType(GenericData.StringType.valueOf(stringType.get())) - compiler.setFieldVisibility(getFieldV()) - compiler.setCreateOptionalGetters(createOptionalGetters.get()) - compiler.setGettersReturnOptional(gettersReturnOptional.get()) - compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly.get()) - compiler.setCreateSetters(createSetters.get()) - compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations.get()) - compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable.get()) - compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull.get()) - compiler.setEnableDecimalLogicalType(enableDecimalLogicalType.get()) - // TODO: likely not needed -// compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding")) - compiler.setAdditionalVelocityTools(instantiateAdditionalVelocityTools(velocityToolsClassesNames.get())) - compiler.setRecordSpecificClass(recordSpecificClass.get()) - compiler.setErrorSpecificClass(errorSpecificClass.get()) - } - - private fun getFieldV(): FieldVisibility { - try { - val upperCaseFieldVisibility = fieldVisibility.get().trim().uppercase() - return FieldVisibility.valueOf(upperCaseFieldVisibility) - } catch (_: IllegalArgumentException) { - logger.warn("Could not parse field visibility: ${fieldVisibility.get()}, using PRIVATE") - return FieldVisibility.PRIVATE - } - } - - protected fun instantiateAdditionalVelocityTools(velocityToolsClassesNames: List): List { - return velocityToolsClassesNames.map { velocityToolClassName -> - try { - Class.forName(velocityToolClassName) - .getDeclaredConstructor() - .newInstance() - } catch (e: Exception) { - throw RuntimeException(e) - } - } - } - - private fun loadLogicalTypesFactories() = - createClassLoader().use { classLoader -> - customLogicalTypeFactories.get().forEach { factory -> - try { - @Suppress("UNCHECKED_CAST") - val logicalTypeFactoryClass = - classLoader.loadClass(factory) as Class - val factoryInstance = logicalTypeFactoryClass.getDeclaredConstructor().newInstance() - LogicalTypes.register(factoryInstance) - } catch (e: ClassNotFoundException) { - throw IOException(e) - } catch (e: ReflectiveOperationException) { - throw GradleException("Failed to instantiate logical type factory class: $factory", e) - } - } - } - - private fun createClassLoader(): URLClassLoader { - val urls = classPathFileCollection() - return URLClassLoader(urls.toTypedArray(), Thread.currentThread().contextClassLoader) - } - - private fun classPathFileCollection(): List = - runtimeClassPathFileCollection.files.map { it.toURI().toURL() } - } From f0af7c59bea65cbc58222471c60895220e2b7c12 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Sun, 18 Jan 2026 12:23:47 +0100 Subject: [PATCH 71/85] AVRO-4223 refactor --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 177 ++++++++++++------ .../plugin/tasks/AbstractCompileTask.kt | 6 + .../plugin/tasks/CompileAvroProtocolTask.kt | 58 ++++++ 3 files changed, 179 insertions(+), 62 deletions(-) create mode 100644 lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 6cdf8ecd6cf..3b857d4044f 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -2,6 +2,7 @@ package eu.eventloopsoftware.avro.gradle.plugin import eu.eventloopsoftware.avro.gradle.plugin.extension.AvroGradlePluginExtension import eu.eventloopsoftware.avro.gradle.plugin.tasks.AbstractCompileTask +import eu.eventloopsoftware.avro.gradle.plugin.tasks.CompileAvroProtocolTask import eu.eventloopsoftware.avro.gradle.plugin.tasks.CompileAvroSchemaTask import org.gradle.api.Plugin import org.gradle.api.Project @@ -10,10 +11,18 @@ import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.Property import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.dsl.KotlinJvmExtension -import java.io.File abstract class AvroGradlePlugin : Plugin { + enum class SchemaType { + schema, + protocol; + + companion object { + val entriesString = SchemaType.entries.map { it.toString() } + } + } + override fun apply(project: Project) { project.logger.info("Running Avro Gradle plugin for project: ${project.name}") @@ -22,49 +31,110 @@ abstract class AvroGradlePlugin : Plugin { // Required so that we can get the sourceSets from the java extension below. project.pluginManager.apply("java") - val compileAvroSchemaTask = - project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> - configurePlugin( - compileSchemaTask, - extension, - project, - extension.sourceDirectory, - extension.outputDirectory, - project.configurations.getByName("runtimeClasspath").files + val schemaType = extension.schemaType.get() + require(schemaType in SchemaType.entriesString) { + "Invalid schema type ${schemaType}. Must be one of ${SchemaType.entriesString}" + } + + when (SchemaType.valueOf(schemaType)) { + SchemaType.schema -> { + val compileAvroSchemaTask = addSchemaTask(extension, project) + val compileTestAvroSchemaTask = addSchemaTestTask(extension, project) + registerPluginHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + } + + SchemaType.protocol -> { + + } + } + + } + + private fun registerPluginHook( + project: Project, + compileAvroSchemaTask: TaskProvider, + compileTestAvroSchemaTask: TaskProvider + ) { + project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + addGeneratedSourcesToKotlinProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + } + + project.plugins.withType(JavaPlugin::class.java) { + addGeneratedSourcesToJavaProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + } + } + + private fun addSchemaTask(extension: AvroGradlePluginExtension, project: Project) = + project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> + addProperties( + compileSchemaTask, + extension, + project, + extension.outputDirectory + ) + + compileSchemaTask.schemaFiles.from(project.fileTree(extension.sourceDirectory).apply { + setIncludes(listOf("**/*.avsc")) + setExcludes(extension.excludes.get()) + }) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.schemaFiles.from( + project.zipTree(zipPath).matching { it.include(setOf("**/*.avsc")) } ) } + compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) + } - val compileTestAvroSchemaTask = project.tasks.register( + private fun addSchemaTestTask(extension: AvroGradlePluginExtension, project: Project) = + project.tasks.register( "avroGenerateTestJavaClasses", CompileAvroSchemaTask::class.java, ) { compileSchemaTask -> - configurePlugin( + addProperties( compileSchemaTask, extension, project, - extension.testSourceDirectory, - extension.testOutputDirectory, - project.configurations.getByName("testRuntimeClasspath").files + extension.testOutputDirectory ) - } - // Add generated code before compilation - project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { - addGeneratedSourcesToKotlinProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + compileSchemaTask.schemaFiles.from(project.fileTree(extension.testSourceDirectory).apply { + setIncludes(listOf("**/*.avsc")) + setExcludes(extension.excludes.get()) + }) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.schemaFiles.from( + project.zipTree(zipPath).matching { it.include(setOf("**/*.avsc")) } + ) + } + compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("testRuntimeClasspath").files) } - project.plugins.withType(JavaPlugin::class.java) { - addGeneratedSourcesToJavaProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + private fun addProtocolTask(extension: AvroGradlePluginExtension, project: Project) = + project.tasks.register("avroGenerateJavaClasses", CompileAvroProtocolTask::class.java) { compileSchemaTask -> + addProperties( + compileSchemaTask, + extension, + project, + extension.outputDirectory + ) + + compileSchemaTask.schemaFiles.from(project.fileTree(extension.sourceDirectory).apply { + setIncludes(listOf("**/*.avpr")) + setExcludes(extension.excludes.get()) + }) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.schemaFiles.from( + project.zipTree(zipPath).matching { it.include(setOf("**/*.avpr")) } + ) + } + compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) } - } - private fun configurePlugin( + private fun addProperties( compileTask: AbstractCompileTask, extension: AvroGradlePluginExtension, project: Project, - sourceDirectory: Property, - outputDirectory: Property, - classPathFiles: Set + outputDirectory: Property ) { compileTask.outputDirectory.set(project.layout.buildDirectory.dir(outputDirectory)) compileTask.fieldVisibility.set(extension.fieldVisibility) @@ -84,45 +154,28 @@ abstract class AvroGradlePlugin : Plugin { compileTask.customConversions.set(extension.customConversions) compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories) compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType) + } + private fun addGeneratedSourcesToJavaProject( + project: Project, + compileTask: TaskProvider, + compileTestTask: TaskProvider + ) { + val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets + sourceSets.getByName("main").java.srcDir(compileTask.flatMap { it.outputDirectory }) + sourceSets.getByName("test").java.srcDir(compileTestTask.flatMap { it.outputDirectory }) + } - when (compileTask) { - is CompileAvroSchemaTask -> { - compileTask.schemaFiles.from(project.fileTree(sourceDirectory).apply { - setIncludes(listOf("**/*.avsc")) - setExcludes(extension.excludes.get()) - }) - extension.sourceZipFiles.get().forEach { zipPath -> - compileTask.schemaFiles.from( - project.zipTree(zipPath).matching { it.include(setOf("**/*.avsc")) } - ) - } - compileTask.runtimeClassPathFileCollection.from(classPathFiles) - - } - - else -> TODO() - } - + private fun addGeneratedSourcesToKotlinProject( + project: Project, + compileTask: TaskProvider, + compileTestTask: TaskProvider, + ) { + val sourceSets = project.extensions.getByType(KotlinJvmExtension::class.java).sourceSets + sourceSets.getByName("main").kotlin.srcDir(compileTask.flatMap { it.outputDirectory }) + sourceSets.getByName("test").kotlin.srcDir(compileTestTask.flatMap { it.outputDirectory }) } } -private fun addGeneratedSourcesToJavaProject( - project: Project, - compileTask: TaskProvider, - compileTestTask: TaskProvider -) { - val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets - sourceSets.getByName("main").java.srcDir(compileTask.flatMap { it.outputDirectory }) - sourceSets.getByName("test").java.srcDir(compileTestTask.flatMap { it.outputDirectory }) -} -private fun addGeneratedSourcesToKotlinProject( - project: Project, - compileTask: TaskProvider, - compileTestTask: TaskProvider, -) { - val sourceSets = project.extensions.getByType(KotlinJvmExtension::class.java).sourceSets - sourceSets.getByName("main").kotlin.srcDir(compileTask.flatMap { it.outputDirectory }) - sourceSets.getByName("test").kotlin.srcDir(compileTestTask.flatMap { it.outputDirectory }) -} + diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt index 95e9c2489b6..f3b24ae2a1e 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -1,6 +1,7 @@ package eu.eventloopsoftware.avro.gradle.plugin.tasks import org.apache.avro.LogicalTypes +import org.apache.avro.Protocol import org.apache.avro.compiler.specific.SpecificCompiler import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility import org.apache.avro.generic.GenericData @@ -79,6 +80,11 @@ abstract class AbstractCompileTask : DefaultTask() { @get:Classpath abstract val runtimeClassPathFileCollection: ConfigurableFileCollection + + protected fun doCompile(sourceFileForModificationDetection: File?, protocol: Protocol, outputDirectory: File?) { + doCompile(sourceFileForModificationDetection, SpecificCompiler(protocol), outputDirectory!!) + } + protected fun doCompile( sourceFileForModificationDetection: File?, compiler: SpecificCompiler, diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt new file mode 100644 index 00000000000..aac864f7cfc --- /dev/null +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt @@ -0,0 +1,58 @@ +package eu.eventloopsoftware.avro.gradle.plugin.tasks + +import org.apache.avro.Protocol +import org.apache.avro.SchemaParseException +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.io.IOException + +abstract class CompileAvroProtocolTask : AbstractCompileTask() { + + @get:InputFiles + @get:SkipWhenEmpty + abstract val schemaFiles: ConfigurableFileCollection + + @get:Input + abstract val other: String + + @TaskAction + fun compileProtocol() { + logger.info("Generating Java files from ${schemaFiles.files.size} Avro Protocol files...") + + compileAvroFiles(schemaFiles, outputDirectory.get().asFile) + + logger.info("Done generating Java files from Avro Protocol files...") + } + + private fun compileAvroFiles(schemaFileTree: ConfigurableFileCollection, outputDirectory: File) { + // Need to register custom logical type factories before schema compilation. + try { + loadLogicalTypesFactories() + } catch (e: IOException) { + throw RuntimeException("Error while loading logical types factories ", e) + } + + try { + for (sourceFile in schemaFileTree.files) { + val protocol = Protocol.parse(sourceFile) + doCompile(sourceFile, protocol, outputDirectory) + } + } catch (ex: IOException) { + // TODO: more concrete exceptions + throw RuntimeException( + "IO ex: Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, + ex + ) + } catch (ex: SchemaParseException) { + throw RuntimeException( + "SchemaParse ex Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, + ex + ) + } + } + +} From 1443f1897ac3c2daf7b1fe3ce37c7d0db1706713 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Sun, 18 Jan 2026 12:26:39 +0100 Subject: [PATCH 72/85] AVRO-4223 refactor --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 8 ++++---- .../avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 3b857d4044f..584491d772d 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -38,8 +38,8 @@ abstract class AvroGradlePlugin : Plugin { when (SchemaType.valueOf(schemaType)) { SchemaType.schema -> { - val compileAvroSchemaTask = addSchemaTask(extension, project) - val compileTestAvroSchemaTask = addSchemaTestTask(extension, project) + val compileAvroSchemaTask = registerSchemaTask(extension, project) + val compileTestAvroSchemaTask = registerSchemaTestTask(extension, project) registerPluginHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } @@ -64,7 +64,7 @@ abstract class AvroGradlePlugin : Plugin { } } - private fun addSchemaTask(extension: AvroGradlePluginExtension, project: Project) = + private fun registerSchemaTask(extension: AvroGradlePluginExtension, project: Project) = project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> addProperties( compileSchemaTask, @@ -85,7 +85,7 @@ abstract class AvroGradlePlugin : Plugin { compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) } - private fun addSchemaTestTask(extension: AvroGradlePluginExtension, project: Project) = + private fun registerSchemaTestTask(extension: AvroGradlePluginExtension, project: Project) = project.tasks.register( "avroGenerateTestJavaClasses", CompileAvroSchemaTask::class.java, diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt index aac864f7cfc..7043e35f6a6 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt @@ -16,9 +16,6 @@ abstract class CompileAvroProtocolTask : AbstractCompileTask() { @get:SkipWhenEmpty abstract val schemaFiles: ConfigurableFileCollection - @get:Input - abstract val other: String - @TaskAction fun compileProtocol() { logger.info("Generating Java files from ${schemaFiles.files.size} Avro Protocol files...") From cce2cfc7a6f9198d249be242838a41870511faa2 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Sun, 18 Jan 2026 12:31:44 +0100 Subject: [PATCH 73/85] AVRO-4223 Rename --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 4 ++-- .../avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt | 7 +++---- ...hemaCompileTaskTest.kt => CompileAvroSchemaTaskTest.kt} | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) rename lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/{SchemaCompileTaskTest.kt => CompileAvroSchemaTaskTest.kt} (99%) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 584491d772d..fadb697c8df 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -118,12 +118,12 @@ abstract class AvroGradlePlugin : Plugin { extension.outputDirectory ) - compileSchemaTask.schemaFiles.from(project.fileTree(extension.sourceDirectory).apply { + compileSchemaTask.protocolFiles.from(project.fileTree(extension.sourceDirectory).apply { setIncludes(listOf("**/*.avpr")) setExcludes(extension.excludes.get()) }) extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.schemaFiles.from( + compileSchemaTask.protocolFiles.from( project.zipTree(zipPath).matching { it.include(setOf("**/*.avpr")) } ) } diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt index 7043e35f6a6..1f41f1d907d 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt @@ -3,7 +3,6 @@ package eu.eventloopsoftware.avro.gradle.plugin.tasks import org.apache.avro.Protocol import org.apache.avro.SchemaParseException import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction @@ -14,13 +13,13 @@ abstract class CompileAvroProtocolTask : AbstractCompileTask() { @get:InputFiles @get:SkipWhenEmpty - abstract val schemaFiles: ConfigurableFileCollection + abstract val protocolFiles: ConfigurableFileCollection @TaskAction fun compileProtocol() { - logger.info("Generating Java files from ${schemaFiles.files.size} Avro Protocol files...") + logger.info("Generating Java files from ${protocolFiles.files.size} Avro Protocol files...") - compileAvroFiles(schemaFiles, outputDirectory.get().asFile) + compileAvroFiles(protocolFiles, outputDirectory.get().asFile) logger.info("Done generating Java files from Avro Protocol files...") } diff --git a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt similarity index 99% rename from lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt rename to lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt index a415ea8f96c..3b200dcf225 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/SchemaCompileTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt @@ -11,7 +11,7 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue @ExperimentalPathApi -class SchemaCompileTaskTest { +class CompileAvroSchemaTaskTest { @TempDir lateinit var tempDir: Path From 0e06ead827a16632d436198b8fe072b4a9a667d8 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 26 Jan 2026 16:52:52 +0100 Subject: [PATCH 74/85] AVRO-4223 Add Protocol support --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 77 ++++++++----------- .../plugin/tasks/CompileAvroProtocolTask.kt | 54 ------------- .../plugin/tasks/CompileAvroSchemaTask.kt | 11 ++- .../plugin/CompileAvroSchemaTaskTest.kt | 22 +++++- 4 files changed, 59 insertions(+), 105 deletions(-) delete mode 100644 lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index fadb697c8df..3bf34237d22 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -2,7 +2,6 @@ package eu.eventloopsoftware.avro.gradle.plugin import eu.eventloopsoftware.avro.gradle.plugin.extension.AvroGradlePluginExtension import eu.eventloopsoftware.avro.gradle.plugin.tasks.AbstractCompileTask -import eu.eventloopsoftware.avro.gradle.plugin.tasks.CompileAvroProtocolTask import eu.eventloopsoftware.avro.gradle.plugin.tasks.CompileAvroSchemaTask import org.gradle.api.Plugin import org.gradle.api.Project @@ -14,15 +13,6 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmExtension abstract class AvroGradlePlugin : Plugin { - enum class SchemaType { - schema, - protocol; - - companion object { - val entriesString = SchemaType.entries.map { it.toString() } - } - } - override fun apply(project: Project) { project.logger.info("Running Avro Gradle plugin for project: ${project.name}") @@ -31,22 +21,9 @@ abstract class AvroGradlePlugin : Plugin { // Required so that we can get the sourceSets from the java extension below. project.pluginManager.apply("java") - val schemaType = extension.schemaType.get() - require(schemaType in SchemaType.entriesString) { - "Invalid schema type ${schemaType}. Must be one of ${SchemaType.entriesString}" - } - - when (SchemaType.valueOf(schemaType)) { - SchemaType.schema -> { - val compileAvroSchemaTask = registerSchemaTask(extension, project) - val compileTestAvroSchemaTask = registerSchemaTestTask(extension, project) - registerPluginHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) - } - - SchemaType.protocol -> { - - } - } + val compileAvroSchemaTask = registerSchemaTask(extension, project) + val compileTestAvroSchemaTask = registerSchemaTestTask(extension, project) + registerPluginHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } @@ -62,10 +39,15 @@ abstract class AvroGradlePlugin : Plugin { project.plugins.withType(JavaPlugin::class.java) { addGeneratedSourcesToJavaProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } + } private fun registerSchemaTask(extension: AvroGradlePluginExtension, project: Project) = project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> + + val includesAvsc = setOf("**/*.avsc") + val includesProtocol = setOf("**/*.avpr") + addProperties( compileSchemaTask, extension, @@ -74,12 +56,22 @@ abstract class AvroGradlePlugin : Plugin { ) compileSchemaTask.schemaFiles.from(project.fileTree(extension.sourceDirectory).apply { - setIncludes(listOf("**/*.avsc")) + setIncludes(includesAvsc) setExcludes(extension.excludes.get()) }) extension.sourceZipFiles.get().forEach { zipPath -> compileSchemaTask.schemaFiles.from( - project.zipTree(zipPath).matching { it.include(setOf("**/*.avsc")) } + project.zipTree(zipPath).matching { it.include(includesAvsc) } + ) + } + + compileSchemaTask.protocolFiles.from(project.fileTree(extension.sourceDirectory).apply { + setIncludes(includesProtocol) + setExcludes(extension.excludes.get()) + }) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.protocolFiles.from( + project.zipTree(zipPath).matching { it.include(includesProtocol) } ) } compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) @@ -90,6 +82,10 @@ abstract class AvroGradlePlugin : Plugin { "avroGenerateTestJavaClasses", CompileAvroSchemaTask::class.java, ) { compileSchemaTask -> + + val includesAvsc = setOf("**/*.avsc") + val includesProtocol = setOf("**/*.avpr") + addProperties( compileSchemaTask, extension, @@ -98,36 +94,27 @@ abstract class AvroGradlePlugin : Plugin { ) compileSchemaTask.schemaFiles.from(project.fileTree(extension.testSourceDirectory).apply { - setIncludes(listOf("**/*.avsc")) + setIncludes(includesAvsc) setExcludes(extension.excludes.get()) }) + extension.sourceZipFiles.get().forEach { zipPath -> compileSchemaTask.schemaFiles.from( - project.zipTree(zipPath).matching { it.include(setOf("**/*.avsc")) } + project.zipTree(zipPath).matching { it.include(includesAvsc) } ) } - compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("testRuntimeClasspath").files) - } - private fun addProtocolTask(extension: AvroGradlePluginExtension, project: Project) = - project.tasks.register("avroGenerateJavaClasses", CompileAvroProtocolTask::class.java) { compileSchemaTask -> - addProperties( - compileSchemaTask, - extension, - project, - extension.outputDirectory - ) - - compileSchemaTask.protocolFiles.from(project.fileTree(extension.sourceDirectory).apply { - setIncludes(listOf("**/*.avpr")) + compileSchemaTask.protocolFiles.from(project.fileTree(extension.testSourceDirectory).apply { + setIncludes(includesProtocol) setExcludes(extension.excludes.get()) }) extension.sourceZipFiles.get().forEach { zipPath -> compileSchemaTask.protocolFiles.from( - project.zipTree(zipPath).matching { it.include(setOf("**/*.avpr")) } + project.zipTree(zipPath).matching { it.include(includesProtocol) } ) } - compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) + + compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("testRuntimeClasspath").files) } private fun addProperties( diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt deleted file mode 100644 index 1f41f1d907d..00000000000 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroProtocolTask.kt +++ /dev/null @@ -1,54 +0,0 @@ -package eu.eventloopsoftware.avro.gradle.plugin.tasks - -import org.apache.avro.Protocol -import org.apache.avro.SchemaParseException -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.SkipWhenEmpty -import org.gradle.api.tasks.TaskAction -import java.io.File -import java.io.IOException - -abstract class CompileAvroProtocolTask : AbstractCompileTask() { - - @get:InputFiles - @get:SkipWhenEmpty - abstract val protocolFiles: ConfigurableFileCollection - - @TaskAction - fun compileProtocol() { - logger.info("Generating Java files from ${protocolFiles.files.size} Avro Protocol files...") - - compileAvroFiles(protocolFiles, outputDirectory.get().asFile) - - logger.info("Done generating Java files from Avro Protocol files...") - } - - private fun compileAvroFiles(schemaFileTree: ConfigurableFileCollection, outputDirectory: File) { - // Need to register custom logical type factories before schema compilation. - try { - loadLogicalTypesFactories() - } catch (e: IOException) { - throw RuntimeException("Error while loading logical types factories ", e) - } - - try { - for (sourceFile in schemaFileTree.files) { - val protocol = Protocol.parse(sourceFile) - doCompile(sourceFile, protocol, outputDirectory) - } - } catch (ex: IOException) { - // TODO: more concrete exceptions - throw RuntimeException( - "IO ex: Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, - ex - ) - } catch (ex: SchemaParseException) { - throw RuntimeException( - "SchemaParse ex Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, - ex - ) - } - } - -} diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt index c2d305031c8..ab478549568 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt @@ -1,5 +1,6 @@ package eu.eventloopsoftware.avro.gradle.plugin.tasks +import org.apache.avro.Protocol import org.apache.avro.SchemaParseException import org.apache.avro.SchemaParser import org.apache.avro.compiler.specific.SpecificCompiler @@ -16,6 +17,9 @@ abstract class CompileAvroSchemaTask : AbstractCompileTask() { @get:SkipWhenEmpty abstract val schemaFiles: ConfigurableFileCollection + @get:InputFiles + @get:SkipWhenEmpty + abstract val protocolFiles: ConfigurableFileCollection @TaskAction fun compileSchema() { @@ -47,10 +51,13 @@ abstract class CompileAvroSchemaTask : AbstractCompileTask() { parser.parse(sourceFile) } val schemas = parser.parsedNamedSchemas - doCompile(sourceFileForModificationDetection, SpecificCompiler(schemas), outputDirectory) + + for (sourceFile in protocolFiles.files) { + val protocol = Protocol.parse(sourceFile) + doCompile(sourceFile, protocol, outputDirectory) + } } catch (ex: IOException) { - // TODO: more concrete exceptions throw RuntimeException( "IO ex: Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, ex diff --git a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt index 3b200dcf225..5757832ce50 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt @@ -42,7 +42,6 @@ class CompileAvroSchemaTaskTest { } avro { - schemaType = "schema" sourceDirectory = "$testAvroFiles" outputDirectory = "$testAvroOutPutDir" } @@ -62,7 +61,10 @@ class CompileAvroSchemaTaskTest { "SchemaUser.java", "PrivacyImport.java", "SchemaCustom.java", - "PrivacyDirectImport.java" + "PrivacyDirectImport.java", + "ProtocolTest.java", + "ProtocolPrivacy.java", + "ProtocolUser.java" ) // then @@ -71,6 +73,9 @@ class CompileAvroSchemaTaskTest { val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() assertTrue(schemaUserContent.contains("java.time.Instant")) + + val protocolUserContent = testOutPutDirectory.resolve("ProtocolUser.java").readText() + assertTrue(protocolUserContent.contains("java.time.Instant")) } @@ -120,7 +125,10 @@ class CompileAvroSchemaTaskTest { "SchemaUser.java", "PrivacyImport.java", "SchemaCustom.java", - "PrivacyDirectImport.java" + "PrivacyDirectImport.java", + "ProtocolTest.java", + "ProtocolPrivacy.java", + "ProtocolUser.java" ) // then @@ -129,6 +137,9 @@ class CompileAvroSchemaTaskTest { val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() assertTrue(schemaUserContent.contains("java.time.Instant")) + + val protocolUserContent = testOutPutDirectory.resolve("ProtocolUser.java").readText() + assertTrue(protocolUserContent.contains("java.time.Instant")) } @Test @@ -187,7 +198,10 @@ class CompileAvroSchemaTaskTest { "SchemaUser.java", "PrivacyImport.java", "SchemaCustom.java", - "PrivacyDirectImport.java" + "PrivacyDirectImport.java", + "ProtocolTest.java", + "ProtocolPrivacy.java", + "ProtocolUser.java" ) // then From 761371facc60f4a0c7a88a8e4083267e319f5afd Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 26 Jan 2026 16:56:44 +0100 Subject: [PATCH 75/85] AVRO-4223 Fix tests --- .../gradle/plugin/extension/AvroGradlePluginExtension.kt | 5 ----- .../avro/gradle/plugin/CompileAvroSchemaTaskTest.kt | 5 +---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt index f534e3dc4ae..7e55f258609 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt @@ -7,11 +7,6 @@ import javax.inject.Inject abstract class AvroGradlePluginExtension @Inject constructor(objects: ObjectFactory) { - /** - * Schema type: "schema", "idl", "protocol" are valid. Default is "schema" - */ - val schemaType: Property = objects.property(String::class.java).convention("schema") - /** * The source directory containing Avro schema files. *

diff --git a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt index 5757832ce50..1501981a3ac 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt @@ -105,7 +105,6 @@ class CompileAvroSchemaTaskTest { } avro { - schemaType = "schema" testSourceDirectory = "$testAvroFiles" testOutputDirectory = "$testAvroOutPutDir" } @@ -176,7 +175,6 @@ class CompileAvroSchemaTaskTest { } avro { - schemaType = "schema" sourceDirectory = "$testAvroFilesDir" outputDirectory = "$testAvroOutPutDir" templateDirectory = "${tempDir.resolve(testVelocityToolClassesDir).toString() + "/"}" @@ -238,7 +236,6 @@ class CompileAvroSchemaTaskTest { } avro { - schemaType = "schema" sourceDirectory = "$testAvroFiles" outputDirectory = "$testAvroOutPutDir" recordSpecificClass = "org.apache.avro.custom.CustomRecordBase" @@ -265,7 +262,7 @@ class CompileAvroSchemaTaskTest { assertEquals(1, extendsLines.size) val extendLine = extendsLines[0] - assertTrue(extendLine.contains(" org.apache.avro.custom.CustomRecordBase ")) + assertTrue(extendLine.contains("org.apache.avro.custom.CustomRecordBase")) assertFalse(extendLine.contains("org.apache.avro.specific.SpecificRecordBase")) } From c75d6c41ebdf558f0d46497cef2e98b21800452a Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 26 Jan 2026 17:09:28 +0100 Subject: [PATCH 76/85] AVRO-4223 Refactor --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 89 ++++++++++--------- .../extension/AvroGradlePluginExtension.kt | 13 ++- 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 3bf34237d22..932b405c9b2 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -45,8 +45,8 @@ abstract class AvroGradlePlugin : Plugin { private fun registerSchemaTask(extension: AvroGradlePluginExtension, project: Project) = project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> - val includesAvsc = setOf("**/*.avsc") - val includesProtocol = setOf("**/*.avpr") + val includesAvsc: Set = extension.includedSchemaFiles.get() + val includesProtocol: Set = extension.includedProtocolFiles.get() addProperties( compileSchemaTask, @@ -55,25 +55,9 @@ abstract class AvroGradlePlugin : Plugin { extension.outputDirectory ) - compileSchemaTask.schemaFiles.from(project.fileTree(extension.sourceDirectory).apply { - setIncludes(includesAvsc) - setExcludes(extension.excludes.get()) - }) - extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.schemaFiles.from( - project.zipTree(zipPath).matching { it.include(includesAvsc) } - ) - } - - compileSchemaTask.protocolFiles.from(project.fileTree(extension.sourceDirectory).apply { - setIncludes(includesProtocol) - setExcludes(extension.excludes.get()) - }) - extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.protocolFiles.from( - project.zipTree(zipPath).matching { it.include(includesProtocol) } - ) - } + addSchemaFiles(compileSchemaTask, project, extension, includesAvsc, extension.sourceDirectory) + addProtocolFiles(compileSchemaTask, project, extension, includesProtocol, extension.testSourceDirectory) + compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) } @@ -83,8 +67,8 @@ abstract class AvroGradlePlugin : Plugin { CompileAvroSchemaTask::class.java, ) { compileSchemaTask -> - val includesAvsc = setOf("**/*.avsc") - val includesProtocol = setOf("**/*.avpr") + val includesAvsc: Set = extension.includedSchemaFiles.get() + val includesProtocol: Set = extension.includedProtocolFiles.get() addProperties( compileSchemaTask, @@ -93,30 +77,13 @@ abstract class AvroGradlePlugin : Plugin { extension.testOutputDirectory ) - compileSchemaTask.schemaFiles.from(project.fileTree(extension.testSourceDirectory).apply { - setIncludes(includesAvsc) - setExcludes(extension.excludes.get()) - }) - - extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.schemaFiles.from( - project.zipTree(zipPath).matching { it.include(includesAvsc) } - ) - } - - compileSchemaTask.protocolFiles.from(project.fileTree(extension.testSourceDirectory).apply { - setIncludes(includesProtocol) - setExcludes(extension.excludes.get()) - }) - extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.protocolFiles.from( - project.zipTree(zipPath).matching { it.include(includesProtocol) } - ) - } + addSchemaFiles(compileSchemaTask, project, extension, includesAvsc, extension.testSourceDirectory) + addProtocolFiles(compileSchemaTask, project, extension, includesProtocol, extension.testSourceDirectory) compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("testRuntimeClasspath").files) } + private fun addProperties( compileTask: AbstractCompileTask, extension: AvroGradlePluginExtension, @@ -143,6 +110,42 @@ abstract class AvroGradlePlugin : Plugin { compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType) } + private fun addSchemaFiles( + compileSchemaTask: CompileAvroSchemaTask, + project: Project, + extension: AvroGradlePluginExtension, + includes: Set, + sourceDirectory: Property + ) { + compileSchemaTask.schemaFiles.from(project.fileTree(sourceDirectory).apply { + setIncludes(includes) + setExcludes(extension.excludes.get()) + }) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.schemaFiles.from( + project.zipTree(zipPath).matching { it.include(includes) } + ) + } + } + + private fun addProtocolFiles( + compileSchemaTask: CompileAvroSchemaTask, + project: Project, + extension: AvroGradlePluginExtension, + includesProtocol: Set, + sourceDirectory: Property + ) { + compileSchemaTask.protocolFiles.from(project.fileTree(sourceDirectory).apply { + setIncludes(includesProtocol) + setExcludes(extension.excludes.get()) + }) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.protocolFiles.from( + project.zipTree(zipPath).matching { it.include(includesProtocol) } + ) + } + } + private fun addGeneratedSourcesToJavaProject( project: Project, compileTask: TaskProvider, diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt index 7e55f258609..ed78172f7c5 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt @@ -3,6 +3,7 @@ package eu.eventloopsoftware.avro.gradle.plugin.extension import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty import javax.inject.Inject abstract class AvroGradlePluginExtension @Inject constructor(objects: ObjectFactory) { @@ -58,7 +59,17 @@ abstract class AvroGradlePluginExtension @Inject constructor(objects: ObjectFact * * @parameter */ - val includes: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) + val includedSchemaFiles: SetProperty = objects.setProperty(String::class.java).convention(setOf("**/*.avsc")) + + /** + * A set of Ant-like inclusion patterns used to select files from the source + * directory for processing. The default pattern is different for Schema, + * Protocol and IDL files. + * + * @parameter + */ + val includedProtocolFiles: SetProperty = objects.setProperty(String::class.java).convention(setOf("**/*.avpr")) + /** * A set of Ant-like exclusion patterns used to prevent certain files from being From c36cb61aaf1e1b5689e09c8e60294e300a8eb908 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 26 Jan 2026 17:15:51 +0100 Subject: [PATCH 77/85] AVRO-4223 Refactor --- .../eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 932b405c9b2..3522c94e4e8 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -23,11 +23,11 @@ abstract class AvroGradlePlugin : Plugin { val compileAvroSchemaTask = registerSchemaTask(extension, project) val compileTestAvroSchemaTask = registerSchemaTestTask(extension, project) - registerPluginHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + addGeneratedSourcesHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } - private fun registerPluginHook( + private fun addGeneratedSourcesHook( project: Project, compileAvroSchemaTask: TaskProvider, compileTestAvroSchemaTask: TaskProvider @@ -39,7 +39,6 @@ abstract class AvroGradlePlugin : Plugin { project.plugins.withType(JavaPlugin::class.java) { addGeneratedSourcesToJavaProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } - } private fun registerSchemaTask(extension: AvroGradlePluginExtension, project: Project) = From eccfed608f9055f7a62099daf3be33ee11eb8c72 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 26 Jan 2026 17:22:23 +0100 Subject: [PATCH 78/85] AVRO-4223 Release 0.1.0 --- lang/java/gradle-plugin/README.md | 8 ++++++++ lang/java/gradle-plugin/build.gradle.kts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lang/java/gradle-plugin/README.md b/lang/java/gradle-plugin/README.md index eb6607a911b..d2edb6b29ae 100644 --- a/lang/java/gradle-plugin/README.md +++ b/lang/java/gradle-plugin/README.md @@ -2,6 +2,10 @@ Gradle plugin that generates Java code from Avro schemas +## Requirements +* Java 21 or higher +* Gradle 9 or higher + ## Version `0.0.2` @@ -24,6 +28,10 @@ It is not needed to add `tasks.named("compileKotlin") { dependsOn(tasks.named("a Add `sourceZipFiles` property to add zip files with schemas in them pu +`0.1.0` + +Add Avro Protocol support + ## Usage ### Add avro extension diff --git a/lang/java/gradle-plugin/build.gradle.kts b/lang/java/gradle-plugin/build.gradle.kts index 45ce2a159ca..611123cfdab 100644 --- a/lang/java/gradle-plugin/build.gradle.kts +++ b/lang/java/gradle-plugin/build.gradle.kts @@ -23,7 +23,7 @@ plugins { } group = "eu.eventloopsoftware" -version = "0.0.9-SNAPSHOT" +version = "0.1.0" repositories { mavenCentral() From 83c3a50468ac43631301f426329b89fd7962dffd Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 14:54:54 +0100 Subject: [PATCH 79/85] AVRO-4223 Improve docs on add sources from JAR files --- lang/java/gradle-plugin/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lang/java/gradle-plugin/README.md b/lang/java/gradle-plugin/README.md index d2edb6b29ae..bb72dae5d33 100644 --- a/lang/java/gradle-plugin/README.md +++ b/lang/java/gradle-plugin/README.md @@ -75,11 +75,15 @@ Use `createNullSafeAnnotations = true` and Java getters will be annotated with Kotlin will recognize which value is nullable. #### I get my Avro schemas from a Maven dependency, how can I add JAR files that contain schemas? -Use `sourceZipFiles = listOf("file_path")`, e.g. +Use `Configuration` to get a reference to the JAR files and add to `sourceZipFiles`: ```kotlin +val avroSources: Configuration by configurations.creating + +avroSources("some.group.id:dependency-containing-avro-sources-artifact-id:1.0.0") + avro { - sourceZipFiles = listOf("/home/user/.gradle/caches/modules-2/files-2.1/eu.eventloopsoftware.group-id/artifact-id/1.0.0/92ac3d0533de9dd79ac35373c892ebaa01763d4d/jar_with_schemas-1.0.0.jar") + sourceZipFiles = avroSources.files.map { it.path } } ``` From aec2b3dfa79755da80987d3294d2771ba489af85 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 16:24:15 +0100 Subject: [PATCH 80/85] AVRO-4223 Add license files --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 18 ++++++++++++++++++ .../extension/AvroGradlePluginExtension.kt | 18 ++++++++++++++++++ .../gradle/plugin/tasks/AbstractCompileTask.kt | 18 ++++++++++++++++++ .../plugin/tasks/CompileAvroSchemaTask.kt | 18 ++++++++++++++++++ .../gradle/plugin/CompileAvroSchemaTaskTest.kt | 18 ++++++++++++++++++ 5 files changed, 90 insertions(+) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 3522c94e4e8..3ec2c6eb3d5 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package eu.eventloopsoftware.avro.gradle.plugin import eu.eventloopsoftware.avro.gradle.plugin.extension.AvroGradlePluginExtension diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt index ed78172f7c5..88d74b8d0d0 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package eu.eventloopsoftware.avro.gradle.plugin.extension import org.gradle.api.model.ObjectFactory diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt index f3b24ae2a1e..b3988e72b6d 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package eu.eventloopsoftware.avro.gradle.plugin.tasks import org.apache.avro.LogicalTypes diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt index ab478549568..44b19d54330 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package eu.eventloopsoftware.avro.gradle.plugin.tasks import org.apache.avro.Protocol diff --git a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt index 1501981a3ac..864f4b2cb9f 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + package eu.eventloopsoftware.avro.gradle.plugin import org.gradle.testkit.runner.GradleRunner From 43803fd63f856aaf0297ebe8fcaf1047782ad688 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 16:29:40 +0100 Subject: [PATCH 81/85] AVRO-4223 Format with Spotless --- .../avro/gradle/plugin/AvroGradlePlugin.kt | 330 +++++++------ .../extension/AvroGradlePluginExtension.kt | 466 ++++++++---------- .../plugin/tasks/AbstractCompileTask.kt | 260 +++++----- .../plugin/tasks/CompileAvroSchemaTask.kt | 124 +++-- .../plugin/CompileAvroSchemaTaskTest.kt | 313 ++++++------ 5 files changed, 710 insertions(+), 783 deletions(-) diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt index 3ec2c6eb3d5..75b3850b833 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/AvroGradlePlugin.kt @@ -1,21 +1,20 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ package eu.eventloopsoftware.avro.gradle.plugin import eu.eventloopsoftware.avro.gradle.plugin.extension.AvroGradlePluginExtension @@ -31,158 +30,169 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmExtension abstract class AvroGradlePlugin : Plugin { - override fun apply(project: Project) { - project.logger.info("Running Avro Gradle plugin for project: ${project.name}") + override fun apply(project: Project) { + project.logger.info("Running Avro Gradle plugin for project: ${project.name}") - val extension = project.extensions.create("avro", AvroGradlePluginExtension::class.java) + val extension = project.extensions.create("avro", AvroGradlePluginExtension::class.java) - // Required so that we can get the sourceSets from the java extension below. - project.pluginManager.apply("java") + // Required so that we can get the sourceSets from the java extension below. + project.pluginManager.apply("java") - val compileAvroSchemaTask = registerSchemaTask(extension, project) - val compileTestAvroSchemaTask = registerSchemaTestTask(extension, project) - addGeneratedSourcesHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + val compileAvroSchemaTask = registerSchemaTask(extension, project) + val compileTestAvroSchemaTask = registerSchemaTestTask(extension, project) + addGeneratedSourcesHook(project, compileAvroSchemaTask, compileTestAvroSchemaTask) + } + private fun addGeneratedSourcesHook( + project: Project, + compileAvroSchemaTask: TaskProvider, + compileTestAvroSchemaTask: TaskProvider, + ) { + project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + addGeneratedSourcesToKotlinProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } - private fun addGeneratedSourcesHook( - project: Project, - compileAvroSchemaTask: TaskProvider, - compileTestAvroSchemaTask: TaskProvider - ) { - project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { - addGeneratedSourcesToKotlinProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) - } - - project.plugins.withType(JavaPlugin::class.java) { - addGeneratedSourcesToJavaProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) - } - } - - private fun registerSchemaTask(extension: AvroGradlePluginExtension, project: Project) = - project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> - - val includesAvsc: Set = extension.includedSchemaFiles.get() - val includesProtocol: Set = extension.includedProtocolFiles.get() - - addProperties( - compileSchemaTask, - extension, - project, - extension.outputDirectory - ) - - addSchemaFiles(compileSchemaTask, project, extension, includesAvsc, extension.sourceDirectory) - addProtocolFiles(compileSchemaTask, project, extension, includesProtocol, extension.testSourceDirectory) - - compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("runtimeClasspath").files) - } - - private fun registerSchemaTestTask(extension: AvroGradlePluginExtension, project: Project) = - project.tasks.register( - "avroGenerateTestJavaClasses", - CompileAvroSchemaTask::class.java, - ) { compileSchemaTask -> - - val includesAvsc: Set = extension.includedSchemaFiles.get() - val includesProtocol: Set = extension.includedProtocolFiles.get() - - addProperties( - compileSchemaTask, - extension, - project, - extension.testOutputDirectory - ) - - addSchemaFiles(compileSchemaTask, project, extension, includesAvsc, extension.testSourceDirectory) - addProtocolFiles(compileSchemaTask, project, extension, includesProtocol, extension.testSourceDirectory) - - compileSchemaTask.runtimeClassPathFileCollection.from(project.configurations.getByName("testRuntimeClasspath").files) - } - - - private fun addProperties( - compileTask: AbstractCompileTask, - extension: AvroGradlePluginExtension, - project: Project, - outputDirectory: Property - ) { - compileTask.outputDirectory.set(project.layout.buildDirectory.dir(outputDirectory)) - compileTask.fieldVisibility.set(extension.fieldVisibility) - compileTask.testExcludes.set(extension.testExcludes) - compileTask.stringType.set(extension.stringType) - compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get()) - compileTask.templateDirectory.set(extension.templateDirectory) - compileTask.recordSpecificClass.set(extension.recordSpecificClass) - compileTask.errorSpecificClass.set(extension.errorSpecificClass) - compileTask.createOptionalGetters.set(extension.createOptionalGetters) - compileTask.gettersReturnOptional.set(extension.gettersReturnOptional) - compileTask.createSetters.set(extension.createSetters) - compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations) - compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable) - compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull) - compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly) - compileTask.customConversions.set(extension.customConversions) - compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories) - compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType) + project.plugins.withType(JavaPlugin::class.java) { + addGeneratedSourcesToJavaProject(project, compileAvroSchemaTask, compileTestAvroSchemaTask) } - - private fun addSchemaFiles( - compileSchemaTask: CompileAvroSchemaTask, - project: Project, - extension: AvroGradlePluginExtension, - includes: Set, - sourceDirectory: Property - ) { - compileSchemaTask.schemaFiles.from(project.fileTree(sourceDirectory).apply { - setIncludes(includes) - setExcludes(extension.excludes.get()) - }) - extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.schemaFiles.from( - project.zipTree(zipPath).matching { it.include(includes) } - ) + } + + private fun registerSchemaTask(extension: AvroGradlePluginExtension, project: Project) = + project.tasks.register("avroGenerateJavaClasses", CompileAvroSchemaTask::class.java) { compileSchemaTask -> + val includesAvsc: Set = extension.includedSchemaFiles.get() + val includesProtocol: Set = extension.includedProtocolFiles.get() + + addProperties(compileSchemaTask, extension, project, extension.outputDirectory) + + addSchemaFiles( + compileSchemaTask, + project, + extension, + includesAvsc, + extension.sourceDirectory, + ) + addProtocolFiles( + compileSchemaTask, + project, + extension, + includesProtocol, + extension.testSourceDirectory, + ) + + compileSchemaTask.runtimeClassPathFileCollection.from( + project.configurations.getByName("runtimeClasspath").files + ) + } + + private fun registerSchemaTestTask(extension: AvroGradlePluginExtension, project: Project) = + project.tasks.register( + "avroGenerateTestJavaClasses", + CompileAvroSchemaTask::class.java, + ) { compileSchemaTask -> + val includesAvsc: Set = extension.includedSchemaFiles.get() + val includesProtocol: Set = extension.includedProtocolFiles.get() + + addProperties(compileSchemaTask, extension, project, extension.testOutputDirectory) + + addSchemaFiles( + compileSchemaTask, + project, + extension, + includesAvsc, + extension.testSourceDirectory, + ) + addProtocolFiles( + compileSchemaTask, + project, + extension, + includesProtocol, + extension.testSourceDirectory, + ) + + compileSchemaTask.runtimeClassPathFileCollection.from( + project.configurations.getByName("testRuntimeClasspath").files + ) + } + + private fun addProperties( + compileTask: AbstractCompileTask, + extension: AvroGradlePluginExtension, + project: Project, + outputDirectory: Property, + ) { + compileTask.outputDirectory.set(project.layout.buildDirectory.dir(outputDirectory)) + compileTask.fieldVisibility.set(extension.fieldVisibility) + compileTask.testExcludes.set(extension.testExcludes) + compileTask.stringType.set(extension.stringType) + compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get()) + compileTask.templateDirectory.set(extension.templateDirectory) + compileTask.recordSpecificClass.set(extension.recordSpecificClass) + compileTask.errorSpecificClass.set(extension.errorSpecificClass) + compileTask.createOptionalGetters.set(extension.createOptionalGetters) + compileTask.gettersReturnOptional.set(extension.gettersReturnOptional) + compileTask.createSetters.set(extension.createSetters) + compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations) + compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable) + compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull) + compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly) + compileTask.customConversions.set(extension.customConversions) + compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories) + compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType) + } + + private fun addSchemaFiles( + compileSchemaTask: CompileAvroSchemaTask, + project: Project, + extension: AvroGradlePluginExtension, + includes: Set, + sourceDirectory: Property, + ) { + compileSchemaTask.schemaFiles.from( + project.fileTree(sourceDirectory).apply { + setIncludes(includes) + setExcludes(extension.excludes.get()) } + ) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.schemaFiles.from(project.zipTree(zipPath).matching { it.include(includes) }) } - - private fun addProtocolFiles( - compileSchemaTask: CompileAvroSchemaTask, - project: Project, - extension: AvroGradlePluginExtension, - includesProtocol: Set, - sourceDirectory: Property - ) { - compileSchemaTask.protocolFiles.from(project.fileTree(sourceDirectory).apply { - setIncludes(includesProtocol) - setExcludes(extension.excludes.get()) - }) - extension.sourceZipFiles.get().forEach { zipPath -> - compileSchemaTask.protocolFiles.from( - project.zipTree(zipPath).matching { it.include(includesProtocol) } - ) + } + + private fun addProtocolFiles( + compileSchemaTask: CompileAvroSchemaTask, + project: Project, + extension: AvroGradlePluginExtension, + includesProtocol: Set, + sourceDirectory: Property, + ) { + compileSchemaTask.protocolFiles.from( + project.fileTree(sourceDirectory).apply { + setIncludes(includesProtocol) + setExcludes(extension.excludes.get()) } + ) + extension.sourceZipFiles.get().forEach { zipPath -> + compileSchemaTask.protocolFiles.from(project.zipTree(zipPath).matching { it.include(includesProtocol) }) } - - private fun addGeneratedSourcesToJavaProject( - project: Project, - compileTask: TaskProvider, - compileTestTask: TaskProvider - ) { - val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets - sourceSets.getByName("main").java.srcDir(compileTask.flatMap { it.outputDirectory }) - sourceSets.getByName("test").java.srcDir(compileTestTask.flatMap { it.outputDirectory }) - } - - private fun addGeneratedSourcesToKotlinProject( - project: Project, - compileTask: TaskProvider, - compileTestTask: TaskProvider, - ) { - val sourceSets = project.extensions.getByType(KotlinJvmExtension::class.java).sourceSets - sourceSets.getByName("main").kotlin.srcDir(compileTask.flatMap { it.outputDirectory }) - sourceSets.getByName("test").kotlin.srcDir(compileTestTask.flatMap { it.outputDirectory }) - } + } + + private fun addGeneratedSourcesToJavaProject( + project: Project, + compileTask: TaskProvider, + compileTestTask: TaskProvider, + ) { + val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets + sourceSets.getByName("main").java.srcDir(compileTask.flatMap { it.outputDirectory }) + sourceSets.getByName("test").java.srcDir(compileTestTask.flatMap { it.outputDirectory }) + } + + private fun addGeneratedSourcesToKotlinProject( + project: Project, + compileTask: TaskProvider, + compileTestTask: TaskProvider, + ) { + val sourceSets = project.extensions.getByType(KotlinJvmExtension::class.java).sourceSets + sourceSets.getByName("main").kotlin.srcDir(compileTask.flatMap { it.outputDirectory }) + sourceSets.getByName("test").kotlin.srcDir(compileTestTask.flatMap { it.outputDirectory }) + } } - - - diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt index 88d74b8d0d0..ed030c174f6 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/extension/AvroGradlePluginExtension.kt @@ -1,262 +1,226 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ package eu.eventloopsoftware.avro.gradle.plugin.extension +import javax.inject.Inject import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty -import javax.inject.Inject abstract class AvroGradlePluginExtension @Inject constructor(objects: ObjectFactory) { - /** - * The source directory containing Avro schema files. - *

- * Defaults to {@code src/main/avro}. - */ - val sourceDirectory: Property = objects.property(String::class.java).convention("src/main/avro") - - /** - * A list of zip files that contain Avro schema files. All generated - * Java classes are added to the classpath. - *

- * Defaults to {@code emptyList()}. - */ - val sourceZipFiles: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) - - /** - * The output directory for the generated Java code. - */ - val outputDirectory: Property = objects.property(String::class.java).convention("generated-sources-avro") - - - /** - * The output directory for the generated test Java code. - */ - val testSourceDirectory: Property = objects.property(String::class.java).convention("src/test/avro") - - /** - * @parameter property="outputDirectory" - * default-value="${project.layout.buildDirectory}/generated-test-sources/avro" - */ - val testOutputDirectory: Property = - objects.property(String::class.java).convention("generated-test-sources-avro") - - - /** - * The field visibility indicator for the fields of the generated class, as - * string values of SpecificCompiler.FieldVisibility. The text is case - * insensitive. - * - * @parameter default-value="PRIVATE" - */ - val fieldVisibility: Property = objects.property(String::class.java).convention("PRIVATE") - - - /** - * A set of Ant-like inclusion patterns used to select files from the source - * directory for processing. The default pattern is different for Schema, - * Protocol and IDL files. - * - * @parameter - */ - val includedSchemaFiles: SetProperty = objects.setProperty(String::class.java).convention(setOf("**/*.avsc")) - - /** - * A set of Ant-like inclusion patterns used to select files from the source - * directory for processing. The default pattern is different for Schema, - * Protocol and IDL files. - * - * @parameter - */ - val includedProtocolFiles: SetProperty = objects.setProperty(String::class.java).convention(setOf("**/*.avpr")) - - - /** - * A set of Ant-like exclusion patterns used to prevent certain files from being - * processed. By default, this set is empty such that no files are excluded. - * - * @parameter - */ - val excludes: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) - - /** - * A set of Ant-like exclusion patterns used to prevent certain files from being - * processed. By default, this set is empty such that no files are excluded. - * - * @parameter - */ - val testExcludes: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) - - /** - * The Java type to use for Avro strings. May be one of CharSequence, String or - * Utf8. String by default. - * - * @parameter property="stringType" - */ - val stringType: Property = objects.property(String::class.java).convention("String") - - - /** - * The qualified names of classes which the plugin will look up, instantiate - * (through an empty constructor that must exist) and set up to be injected into - * Velocity templates by Avro compiler. - * - * @parameter property="velocityToolsClassesNames" - */ - val velocityToolsClassesNames: ListProperty = - objects.listProperty(String::class.java).convention(emptyList()) - - - /** - * The directory (within the java classpath) that contains the velocity - * templates to use for code generation. The default value points to the - * templates included with the avro-maven-plugin. - * - * @parameter property="templateDirectory" - */ - val templateDirectory: Property = - objects.property(String::class.java).convention("/org/apache/avro/compiler/specific/templates/java/classic/") - - - /** - * Generated record schema classes will extend this class. - * - * @parameter property="recordSpecificClass" - */ - val recordSpecificClass: Property = - objects.property(String::class.java).convention("org.apache.avro.specific.SpecificRecordBase") - - - /** - * Generated error schema classes will extend this class. - * - * @parameter property="errorSpecificClass" - */ - val errorSpecificClass: Property = - objects.property(String::class.java).convention("org.apache.avro.specific.SpecificExceptionBase") - - - /** - * The createOptionalGetters parameter enables generating the getOptional... - * methods that return an Optional of the requested type. This works ONLY on - * Java 8+ - * - * @parameter property="createOptionalGetters" - */ - val createOptionalGetters: Property = objects.property(Boolean::class.java).convention(false) - - /** - * The gettersReturnOptional parameter enables generating get... methods that - * return an Optional of the requested type. This works ONLY on Java 8+ - * - * @parameter property="gettersReturnOptional" - */ - val gettersReturnOptional: Property = objects.property(Boolean::class.java).convention(false) - - /** - * The optionalGettersForNullableFieldsOnly parameter works in conjunction with - * gettersReturnOptional option. If it is set, Optional getters will be - * generated only for fields that are nullable. If the field is mandatory, - * regular getter will be generated. This works ONLY on Java 8+. - * - * @parameter property="optionalGettersForNullableFieldsOnly" - */ - val optionalGettersForNullableFieldsOnly: Property = - objects.property(Boolean::class.java).convention(false) - - - /** - * Determines whether or not to create setters for the fields of the record. The - * default is to create setters. - * - * @parameter default-value="true" - */ - val createSetters: Property = objects.property(Boolean::class.java).convention(true) - - /** - * If set to true, @Nullable and @NotNull annotations are - * added to fields of the record. The default is false. If enabled, JetBrains - * annotations are used by default but other annotations can be specified via - * the nullSafeAnnotationNullable and nullSafeAnnotationNotNull parameters. - * - * @parameter property="createNullSafeAnnotations" - * - * @see [ - * JetBrains nullability annotations](https://www.jetbrains.com/help/idea/annotating-source-code.html.nullability-annotations) - */ - val createNullSafeAnnotations: Property = objects.property(Boolean::class.java).convention(false) - - /** - * Controls which annotation should be added to nullable fields if - * createNullSafeAnnotations is enabled. The default is - * org.jetbrains.annotations.Nullable. - * - * @parameter property="nullSafeAnnotationNullable" - * - * @see [ - * JetBrains nullability annotations](https://www.jetbrains.com/help/idea/annotating-source-code.html.nullability-annotations) - */ - val nullSafeAnnotationNullable: Property = - objects.property(String::class.java).convention("org.jetbrains.annotations.Nullable") - - /** - * Controls which annotation should be added to non-nullable fields if - * createNullSafeAnnotations is enabled. The default is - * org.jetbrains.annotations.NotNull. - * - * @parameter property="nullSafeAnnotationNotNull" - * - * @see [ - * JetBrains nullability annotations](https://www.jetbrains.com/help/idea/annotating-source-code.html.nullability-annotations) - */ - val nullSafeAnnotationNotNull: Property = - objects.property(String::class.java).convention("org.jetbrains.annotations.NotNull") - - /** - * A set of fully qualified class names of custom - * {@link org.apache.avro.Conversion} implementations to add to the compiler. - * The classes must be on the classpath at compile time and whenever the Java - * objects are serialized. - * - * @parameter property="customConversions" - */ - val customConversions: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) - - /** - * A set of fully qualified class names of custom - * [org.apache.avro.LogicalTypes.LogicalTypeFactory] implementations to - * add to the compiler. The classes must be on the classpath at compile time and - * whenever the Java objects are serialized. - * - * @parameter property="customLogicalTypeFactories" - */ - val customLogicalTypeFactories: ListProperty = - objects.listProperty(String::class.java).convention(emptyList()) - - - /** - * Determines whether or not to use Java classes for decimal types - * - * @parameter default-value="false" - */ - val enableDecimalLogicalType: Property = objects.property(Boolean::class.java).convention(false) - + /** + * The source directory containing Avro schema files. + * + *

+ * Defaults to {@code src/main/avro}. + */ + val sourceDirectory: Property = objects.property(String::class.java).convention("src/main/avro") + + /** + * A list of zip files that contain Avro schema files. All generated Java classes are added to the classpath. + * + *

+ * Defaults to {@code emptyList()}. + */ + val sourceZipFiles: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) + + /** The output directory for the generated Java code. */ + val outputDirectory: Property = objects.property(String::class.java).convention("generated-sources-avro") + + /** The output directory for the generated test Java code. */ + val testSourceDirectory: Property = objects.property(String::class.java).convention("src/test/avro") + + /** + * @parameter property="outputDirectory" default-value="${project.layout.buildDirectory}/generated-test-sources/avro" + */ + val testOutputDirectory: Property = + objects.property(String::class.java).convention("generated-test-sources-avro") + + /** + * The field visibility indicator for the fields of the generated class, as string values of + * SpecificCompiler.FieldVisibility. The text is case insensitive. + * + * @parameter default-value="PRIVATE" + */ + val fieldVisibility: Property = objects.property(String::class.java).convention("PRIVATE") + + /** + * A set of Ant-like inclusion patterns used to select files from the source directory for processing. The default + * pattern is different for Schema, Protocol and IDL files. + * + * @parameter + */ + val includedSchemaFiles: SetProperty = objects.setProperty(String::class.java).convention(setOf("**/*.avsc")) + + /** + * A set of Ant-like inclusion patterns used to select files from the source directory for processing. The default + * pattern is different for Schema, Protocol and IDL files. + * + * @parameter + */ + val includedProtocolFiles: SetProperty = + objects.setProperty(String::class.java).convention(setOf("**/*.avpr")) + + /** + * A set of Ant-like exclusion patterns used to prevent certain files from being processed. By default, this set is + * empty such that no files are excluded. + * + * @parameter + */ + val excludes: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) + + /** + * A set of Ant-like exclusion patterns used to prevent certain files from being processed. By default, this set is + * empty such that no files are excluded. + * + * @parameter + */ + val testExcludes: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) + + /** + * The Java type to use for Avro strings. May be one of CharSequence, String or Utf8. String by default. + * + * @parameter property="stringType" + */ + val stringType: Property = objects.property(String::class.java).convention("String") + + /** + * The qualified names of classes which the plugin will look up, instantiate (through an empty constructor that must + * exist) and set up to be injected into Velocity templates by Avro compiler. + * + * @parameter property="velocityToolsClassesNames" + */ + val velocityToolsClassesNames: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) + + /** + * The directory (within the java classpath) that contains the velocity templates to use for code generation. The + * default value points to the templates included with the avro-maven-plugin. + * + * @parameter property="templateDirectory" + */ + val templateDirectory: Property = + objects.property(String::class.java).convention("/org/apache/avro/compiler/specific/templates/java/classic/") + + /** + * Generated record schema classes will extend this class. + * + * @parameter property="recordSpecificClass" + */ + val recordSpecificClass: Property = + objects.property(String::class.java).convention("org.apache.avro.specific.SpecificRecordBase") + + /** + * Generated error schema classes will extend this class. + * + * @parameter property="errorSpecificClass" + */ + val errorSpecificClass: Property = + objects.property(String::class.java).convention("org.apache.avro.specific.SpecificExceptionBase") + + /** + * The createOptionalGetters parameter enables generating the getOptional... methods that return an Optional of the + * requested type. This works ONLY on Java 8+ + * + * @parameter property="createOptionalGetters" + */ + val createOptionalGetters: Property = objects.property(Boolean::class.java).convention(false) + + /** + * The gettersReturnOptional parameter enables generating get... methods that return an Optional of the requested + * type. This works ONLY on Java 8+ + * + * @parameter property="gettersReturnOptional" + */ + val gettersReturnOptional: Property = objects.property(Boolean::class.java).convention(false) + + /** + * The optionalGettersForNullableFieldsOnly parameter works in conjunction with gettersReturnOptional option. If it is + * set, Optional getters will be generated only for fields that are nullable. If the field is mandatory, regular + * getter will be generated. This works ONLY on Java 8+. + * + * @parameter property="optionalGettersForNullableFieldsOnly" + */ + val optionalGettersForNullableFieldsOnly: Property = objects.property(Boolean::class.java).convention(false) + + /** + * Determines whether or not to create setters for the fields of the record. The default is to create setters. + * + * @parameter default-value="true" + */ + val createSetters: Property = objects.property(Boolean::class.java).convention(true) + + /** + * If set to true, @Nullable and @NotNull annotations are added to fields of the record. The default is false. If + * enabled, JetBrains annotations are used by default but other annotations can be specified via the + * nullSafeAnnotationNullable and nullSafeAnnotationNotNull parameters. + * + * @parameter property="createNullSafeAnnotations" + * @see + * [ JetBrains nullability annotations](https://www.jetbrains.com/help/idea/annotating-source-code.html.nullability-annotations) + */ + val createNullSafeAnnotations: Property = objects.property(Boolean::class.java).convention(false) + + /** + * Controls which annotation should be added to nullable fields if createNullSafeAnnotations is enabled. The default + * is org.jetbrains.annotations.Nullable. + * + * @parameter property="nullSafeAnnotationNullable" + * @see + * [ JetBrains nullability annotations](https://www.jetbrains.com/help/idea/annotating-source-code.html.nullability-annotations) + */ + val nullSafeAnnotationNullable: Property = + objects.property(String::class.java).convention("org.jetbrains.annotations.Nullable") + + /** + * Controls which annotation should be added to non-nullable fields if createNullSafeAnnotations is enabled. The + * default is org.jetbrains.annotations.NotNull. + * + * @parameter property="nullSafeAnnotationNotNull" + * @see + * [ JetBrains nullability annotations](https://www.jetbrains.com/help/idea/annotating-source-code.html.nullability-annotations) + */ + val nullSafeAnnotationNotNull: Property = + objects.property(String::class.java).convention("org.jetbrains.annotations.NotNull") + + /** + * A set of fully qualified class names of custom {@link org.apache.avro.Conversion} implementations to add to the + * compiler. The classes must be on the classpath at compile time and whenever the Java objects are serialized. + * + * @parameter property="customConversions" + */ + val customConversions: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) + + /** + * A set of fully qualified class names of custom [org.apache.avro.LogicalTypes.LogicalTypeFactory] implementations to + * add to the compiler. The classes must be on the classpath at compile time and whenever the Java objects are + * serialized. + * + * @parameter property="customLogicalTypeFactories" + */ + val customLogicalTypeFactories: ListProperty = + objects.listProperty(String::class.java).convention(emptyList()) + + /** + * Determines whether or not to use Java classes for decimal types + * + * @parameter default-value="false" + */ + val enableDecimalLogicalType: Property = objects.property(Boolean::class.java).convention(false) } diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt index b3988e72b6d..ac067ed7093 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/AbstractCompileTask.kt @@ -1,23 +1,26 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ package eu.eventloopsoftware.avro.gradle.plugin.tasks +import java.io.File +import java.io.IOException +import java.net.URL +import java.net.URLClassLoader import org.apache.avro.LogicalTypes import org.apache.avro.Protocol import org.apache.avro.compiler.specific.SpecificCompiler @@ -33,158 +36,131 @@ import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory -import java.io.File -import java.io.IOException -import java.net.URL -import java.net.URLClassLoader abstract class AbstractCompileTask : DefaultTask() { - @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Input - abstract val fieldVisibility: Property + @get:Input abstract val fieldVisibility: Property - @get:Input - abstract val testExcludes: ListProperty + @get:Input abstract val testExcludes: ListProperty - @get:Input - abstract val stringType: Property + @get:Input abstract val stringType: Property - @get:Input - abstract val velocityToolsClassesNames: ListProperty + @get:Input abstract val velocityToolsClassesNames: ListProperty - @get:Input - abstract val templateDirectory: Property + @get:Input abstract val templateDirectory: Property - @get:Input - abstract val recordSpecificClass: Property + @get:Input abstract val recordSpecificClass: Property - @get:Input - abstract val errorSpecificClass: Property + @get:Input abstract val errorSpecificClass: Property - @get:Input - abstract val createOptionalGetters: Property + @get:Input abstract val createOptionalGetters: Property - @get:Input - abstract val gettersReturnOptional: Property + @get:Input abstract val gettersReturnOptional: Property - @get:Input - abstract val optionalGettersForNullableFieldsOnly: Property + @get:Input abstract val optionalGettersForNullableFieldsOnly: Property - @get:Input - abstract val createSetters: Property + @get:Input abstract val createSetters: Property - @get:Input - abstract val createNullSafeAnnotations: Property + @get:Input abstract val createNullSafeAnnotations: Property - @get:Input - abstract val nullSafeAnnotationNullable: Property + @get:Input abstract val nullSafeAnnotationNullable: Property - @get:Input - abstract val nullSafeAnnotationNotNull: Property + @get:Input abstract val nullSafeAnnotationNotNull: Property - @get:Input - abstract val customConversions: ListProperty + @get:Input abstract val customConversions: ListProperty - @get:Input - abstract val customLogicalTypeFactories: ListProperty + @get:Input abstract val customLogicalTypeFactories: ListProperty - @get:Input - abstract val enableDecimalLogicalType: Property + @get:Input abstract val enableDecimalLogicalType: Property - @get:InputFiles - @get:Classpath - abstract val runtimeClassPathFileCollection: ConfigurableFileCollection + @get:InputFiles @get:Classpath abstract val runtimeClassPathFileCollection: ConfigurableFileCollection + protected fun doCompile( + sourceFileForModificationDetection: File?, + protocol: Protocol, + outputDirectory: File?, + ) { + doCompile(sourceFileForModificationDetection, SpecificCompiler(protocol), outputDirectory!!) + } - protected fun doCompile(sourceFileForModificationDetection: File?, protocol: Protocol, outputDirectory: File?) { - doCompile(sourceFileForModificationDetection, SpecificCompiler(protocol), outputDirectory!!) + protected fun doCompile( + sourceFileForModificationDetection: File?, + compiler: SpecificCompiler, + outputDirectory: File, + ) { + setCompilerProperties(compiler) + try { + for (customConversion in customConversions.get()) { + compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion)) + } + } catch (e: ClassNotFoundException) { + throw IOException(e) } - - protected fun doCompile( - sourceFileForModificationDetection: File?, - compiler: SpecificCompiler, - outputDirectory: File - ) { - setCompilerProperties(compiler) - try { - for (customConversion in customConversions.get()) { - compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion)) - } - } catch (e: ClassNotFoundException) { - throw IOException(e) - } - compiler.compileToDestination(sourceFileForModificationDetection, outputDirectory) + compiler.compileToDestination(sourceFileForModificationDetection, outputDirectory) + } + + private fun setCompilerProperties(compiler: SpecificCompiler) { + compiler.setTemplateDir(templateDirectory.get()) + compiler.setStringType(GenericData.StringType.valueOf(stringType.get())) + compiler.setFieldVisibility(getFieldV()) + compiler.setCreateOptionalGetters(createOptionalGetters.get()) + compiler.setGettersReturnOptional(gettersReturnOptional.get()) + compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly.get()) + compiler.setCreateSetters(createSetters.get()) + compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations.get()) + compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable.get()) + compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull.get()) + compiler.setEnableDecimalLogicalType(enableDecimalLogicalType.get()) + // TODO: likely not needed + // + // compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding")) + compiler.setAdditionalVelocityTools(instantiateAdditionalVelocityTools(velocityToolsClassesNames.get())) + compiler.setRecordSpecificClass(recordSpecificClass.get()) + compiler.setErrorSpecificClass(errorSpecificClass.get()) + } + + private fun getFieldV(): FieldVisibility { + try { + val upperCaseFieldVisibility = fieldVisibility.get().trim().uppercase() + return FieldVisibility.valueOf(upperCaseFieldVisibility) + } catch (_: IllegalArgumentException) { + logger.warn("Could not parse field visibility: ${fieldVisibility.get()}, using PRIVATE") + return FieldVisibility.PRIVATE } - - - private fun setCompilerProperties(compiler: SpecificCompiler) { - compiler.setTemplateDir(templateDirectory.get()) - compiler.setStringType(GenericData.StringType.valueOf(stringType.get())) - compiler.setFieldVisibility(getFieldV()) - compiler.setCreateOptionalGetters(createOptionalGetters.get()) - compiler.setGettersReturnOptional(gettersReturnOptional.get()) - compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly.get()) - compiler.setCreateSetters(createSetters.get()) - compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations.get()) - compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable.get()) - compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull.get()) - compiler.setEnableDecimalLogicalType(enableDecimalLogicalType.get()) - // TODO: likely not needed -// compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding")) - compiler.setAdditionalVelocityTools(instantiateAdditionalVelocityTools(velocityToolsClassesNames.get())) - compiler.setRecordSpecificClass(recordSpecificClass.get()) - compiler.setErrorSpecificClass(errorSpecificClass.get()) - } - - private fun getFieldV(): FieldVisibility { - try { - val upperCaseFieldVisibility = fieldVisibility.get().trim().uppercase() - return FieldVisibility.valueOf(upperCaseFieldVisibility) - } catch (_: IllegalArgumentException) { - logger.warn("Could not parse field visibility: ${fieldVisibility.get()}, using PRIVATE") - return FieldVisibility.PRIVATE - } - } - - private fun instantiateAdditionalVelocityTools(velocityToolsClassesNames: List): List { - return velocityToolsClassesNames.map { velocityToolClassName -> - try { - Class.forName(velocityToolClassName) - .getDeclaredConstructor() - .newInstance() - } catch (e: Exception) { - throw RuntimeException(e) - } - } + } + + private fun instantiateAdditionalVelocityTools(velocityToolsClassesNames: List): List { + return velocityToolsClassesNames.map { velocityToolClassName -> + try { + Class.forName(velocityToolClassName).getDeclaredConstructor().newInstance() + } catch (e: Exception) { + throw RuntimeException(e) + } } - - protected fun loadLogicalTypesFactories() = - createClassLoader().use { classLoader -> - customLogicalTypeFactories.get().forEach { factory -> - try { - @Suppress("UNCHECKED_CAST") - val logicalTypeFactoryClass = - classLoader.loadClass(factory) as Class - val factoryInstance = logicalTypeFactoryClass.getDeclaredConstructor().newInstance() - LogicalTypes.register(factoryInstance) - } catch (e: ClassNotFoundException) { - throw IOException(e) - } catch (e: ReflectiveOperationException) { - throw GradleException("Failed to instantiate logical type factory class: $factory", e) - } - } + } + + protected fun loadLogicalTypesFactories() = + createClassLoader().use { classLoader -> + customLogicalTypeFactories.get().forEach { factory -> + try { + @Suppress("UNCHECKED_CAST") + val logicalTypeFactoryClass = classLoader.loadClass(factory) as Class + val factoryInstance = logicalTypeFactoryClass.getDeclaredConstructor().newInstance() + LogicalTypes.register(factoryInstance) + } catch (e: ClassNotFoundException) { + throw IOException(e) + } catch (e: ReflectiveOperationException) { + throw GradleException("Failed to instantiate logical type factory class: $factory", e) + } } + } - private fun createClassLoader(): URLClassLoader { - val urls = classPathFileCollection() - return URLClassLoader(urls.toTypedArray(), Thread.currentThread().contextClassLoader) - } - - private fun classPathFileCollection(): List = - runtimeClassPathFileCollection.files.map { it.toURI().toURL() } - + private fun createClassLoader(): URLClassLoader { + val urls = classPathFileCollection() + return URLClassLoader(urls.toTypedArray(), Thread.currentThread().contextClassLoader) + } + private fun classPathFileCollection(): List = runtimeClassPathFileCollection.files.map { it.toURI().toURL() } } diff --git a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt index 44b19d54330..118e9caaaf2 100644 --- a/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt +++ b/lang/java/gradle-plugin/src/main/kotlin/eu/eventloopsoftware/avro/gradle/plugin/tasks/CompileAvroSchemaTask.kt @@ -1,23 +1,24 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ package eu.eventloopsoftware.avro.gradle.plugin.tasks +import java.io.File +import java.io.IOException import org.apache.avro.Protocol import org.apache.avro.SchemaParseException import org.apache.avro.SchemaParser @@ -26,66 +27,55 @@ import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction -import java.io.File -import java.io.IOException abstract class CompileAvroSchemaTask : AbstractCompileTask() { - @get:InputFiles - @get:SkipWhenEmpty - abstract val schemaFiles: ConfigurableFileCollection + @get:InputFiles @get:SkipWhenEmpty abstract val schemaFiles: ConfigurableFileCollection - @get:InputFiles - @get:SkipWhenEmpty - abstract val protocolFiles: ConfigurableFileCollection + @get:InputFiles @get:SkipWhenEmpty abstract val protocolFiles: ConfigurableFileCollection - @TaskAction - fun compileSchema() { - logger.info("Generating Java files from ${schemaFiles.files.size} Avro schemas...") + @TaskAction + fun compileSchema() { + logger.info("Generating Java files from ${schemaFiles.files.size} Avro schemas...") - compileSchemas(schemaFiles, outputDirectory.get().asFile) + compileSchemas(schemaFiles, outputDirectory.get().asFile) - logger.info("Done generating Java files from Avro schemas...") - } - - private fun compileSchemas(schemaFileTree: ConfigurableFileCollection, outputDirectory: File) { - val sourceFileForModificationDetection: File? = - schemaFileTree.asFileTree - .files - .filter { file: File -> file.lastModified() > 0 } - .maxBy { it.lastModified() } + logger.info("Done generating Java files from Avro schemas...") + } + private fun compileSchemas(schemaFileTree: ConfigurableFileCollection, outputDirectory: File) { + val sourceFileForModificationDetection: File? = + schemaFileTree.asFileTree.files.filter { file: File -> file.lastModified() > 0 }.maxBy { it.lastModified() } - // Need to register custom logical type factories before schema compilation. - try { - loadLogicalTypesFactories() - } catch (e: IOException) { - throw RuntimeException("Error while loading logical types factories ", e) - } + // Need to register custom logical type factories before schema compilation. + try { + loadLogicalTypesFactories() + } catch (e: IOException) { + throw RuntimeException("Error while loading logical types factories ", e) + } - try { - val parser = SchemaParser() - for (sourceFile in schemaFileTree.files) { - parser.parse(sourceFile) - } - val schemas = parser.parsedNamedSchemas - doCompile(sourceFileForModificationDetection, SpecificCompiler(schemas), outputDirectory) + try { + val parser = SchemaParser() + for (sourceFile in schemaFileTree.files) { + parser.parse(sourceFile) + } + val schemas = parser.parsedNamedSchemas + doCompile(sourceFileForModificationDetection, SpecificCompiler(schemas), outputDirectory) - for (sourceFile in protocolFiles.files) { - val protocol = Protocol.parse(sourceFile) - doCompile(sourceFile, protocol, outputDirectory) - } - } catch (ex: IOException) { - throw RuntimeException( - "IO ex: Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, - ex - ) - } catch (ex: SchemaParseException) { - throw RuntimeException( - "SchemaParse ex Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, - ex - ) - } + for (sourceFile in protocolFiles.files) { + val protocol = Protocol.parse(sourceFile) + doCompile(sourceFile, protocol, outputDirectory) + } + } catch (ex: IOException) { + throw RuntimeException( + "IO ex: Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, + ex, + ) + } catch (ex: SchemaParseException) { + throw RuntimeException( + "SchemaParse ex Error compiling a file in " + schemaFileTree.asPath + " to " + outputDirectory, + ex, + ) } - + } } diff --git a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt index 864f4b2cb9f..e7bdf05f82d 100644 --- a/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt +++ b/lang/java/gradle-plugin/src/test/kotlin/eu/eventloopsoftware/avro/gradle/plugin/CompileAvroSchemaTaskTest.kt @@ -1,60 +1,54 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ package eu.eventloopsoftware.avro.gradle.plugin -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.io.path.* import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.io.TempDir @ExperimentalPathApi class CompileAvroSchemaTaskTest { - @TempDir - lateinit var tempDir: Path + @TempDir lateinit var tempDir: Path - @Test - fun `plugin executes avroGenerateJavaClasses task successfully`() { - // given - val tempSettingsFile = tempDir.resolve("settings.gradle.kts") - val tempBuildFile = tempDir.resolve("build.gradle.kts") - val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() + @Test + fun `plugin executes avroGenerateJavaClasses task successfully`() { + // given + val tempSettingsFile = tempDir.resolve("settings.gradle.kts") + val tempBuildFile = tempDir.resolve("build.gradle.kts") + val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() - val testAvroFiles = Path.of("src/test/avro") - val testAvroOutPutDir = Path.of("generated-sources/avro") + val testAvroFiles = Path.of("src/test/avro") + val testAvroOutPutDir = Path.of("generated-sources/avro") - val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") + val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") - testAvroFiles.copyToRecursively( - tempAvroSrcDir, - overwrite = true, - followLinks = false - ) + testAvroFiles.copyToRecursively(tempAvroSrcDir, overwrite = true, followLinks = false) - tempSettingsFile.writeText("") - tempBuildFile.writeText( - """ + tempSettingsFile.writeText("") + tempBuildFile.writeText( + """ plugins { id("eu.eventloopsoftware.avro-gradle-plugin") } @@ -63,18 +57,21 @@ class CompileAvroSchemaTaskTest { sourceDirectory = "$testAvroFiles" outputDirectory = "$testAvroOutPutDir" } - """.trimIndent() - ) + """ + .trimIndent() + ) - // when - val result = GradleRunner.create() + // when + val result = + GradleRunner.create() .withProjectDir(tempDir.toFile()) .withArguments("avroGenerateJavaClasses") .withPluginClasspath() .forwardOutput() // to see printLn in code .build() - val expectedFiles = setOf( + val expectedFiles = + setOf( "SchemaPrivacy.java", "SchemaUser.java", "PrivacyImport.java", @@ -82,42 +79,37 @@ class CompileAvroSchemaTaskTest { "PrivacyDirectImport.java", "ProtocolTest.java", "ProtocolPrivacy.java", - "ProtocolUser.java" + "ProtocolUser.java", ) - // then - assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateJavaClasses")?.outcome) - assertFilesExist(testOutPutDirectory, expectedFiles) + // then + assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateJavaClasses")?.outcome) + assertFilesExist(testOutPutDirectory, expectedFiles) - val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() - assertTrue(schemaUserContent.contains("java.time.Instant")) + val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() + assertTrue(schemaUserContent.contains("java.time.Instant")) - val protocolUserContent = testOutPutDirectory.resolve("ProtocolUser.java").readText() - assertTrue(protocolUserContent.contains("java.time.Instant")) - } + val protocolUserContent = testOutPutDirectory.resolve("ProtocolUser.java").readText() + assertTrue(protocolUserContent.contains("java.time.Instant")) + } + @Test + fun `plugin executes avroGenerateTestJavaClasses task successfully - for files in test directory`() { + // given + val tempSettingsFile = tempDir.resolve("settings.gradle.kts") + val tempBuildFile = tempDir.resolve("build.gradle.kts") + val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() - @Test - fun `plugin executes avroGenerateTestJavaClasses task successfully - for files in test directory`() { - // given - val tempSettingsFile = tempDir.resolve("settings.gradle.kts") - val tempBuildFile = tempDir.resolve("build.gradle.kts") - val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() + val testAvroFiles = Path.of("src/test/avro") + val testAvroOutPutDir = Path.of("generated-test-sources-avro") - val testAvroFiles = Path.of("src/test/avro") - val testAvroOutPutDir = Path.of("generated-test-sources-avro") + val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") - val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") + testAvroFiles.copyToRecursively(tempAvroSrcDir, overwrite = true, followLinks = false) - testAvroFiles.copyToRecursively( - tempAvroSrcDir, - overwrite = true, - followLinks = false - ) - - tempSettingsFile.writeText("") - tempBuildFile.writeText( - """ + tempSettingsFile.writeText("") + tempBuildFile.writeText( + """ plugins { id("eu.eventloopsoftware.avro-gradle-plugin") } @@ -126,18 +118,21 @@ class CompileAvroSchemaTaskTest { testSourceDirectory = "$testAvroFiles" testOutputDirectory = "$testAvroOutPutDir" } - """.trimIndent() - ) + """ + .trimIndent() + ) - // when - val result = GradleRunner.create() + // when + val result = + GradleRunner.create() .withProjectDir(tempDir.toFile()) .withArguments("avroGenerateTestJavaClasses") .withPluginClasspath() .forwardOutput() // to see printLn in code .build() - val expectedFiles = setOf( + val expectedFiles = + setOf( "SchemaPrivacy.java", "SchemaUser.java", "PrivacyImport.java", @@ -145,49 +140,45 @@ class CompileAvroSchemaTaskTest { "PrivacyDirectImport.java", "ProtocolTest.java", "ProtocolPrivacy.java", - "ProtocolUser.java" + "ProtocolUser.java", ) - // then - assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateTestJavaClasses")?.outcome) - assertFilesExist(testOutPutDirectory, expectedFiles) + // then + assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateTestJavaClasses")?.outcome) + assertFilesExist(testOutPutDirectory, expectedFiles) - val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() - assertTrue(schemaUserContent.contains("java.time.Instant")) + val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() + assertTrue(schemaUserContent.contains("java.time.Instant")) - val protocolUserContent = testOutPutDirectory.resolve("ProtocolUser.java").readText() - assertTrue(protocolUserContent.contains("java.time.Instant")) - } + val protocolUserContent = testOutPutDirectory.resolve("ProtocolUser.java").readText() + assertTrue(protocolUserContent.contains("java.time.Instant")) + } - @Test - fun `plugin executes avroGenerateJavaClasses task successfully - with Velocity class names`() { - // given - val tempSettingsFile = tempDir.resolve("settings.gradle.kts") - val tempBuildFile = tempDir.resolve("build.gradle.kts") - val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() - val tempVelocityToolClassesDir = tempDir.resolve("src/test/resources/templates").createDirectories() + @Test + fun `plugin executes avroGenerateJavaClasses task successfully - with Velocity class names`() { + // given + val tempSettingsFile = tempDir.resolve("settings.gradle.kts") + val tempBuildFile = tempDir.resolve("build.gradle.kts") + val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() + val tempVelocityToolClassesDir = tempDir.resolve("src/test/resources/templates").createDirectories() - val testAvroFilesDir = Path.of("src/test/avro") - val testAvroOutPutDir = Path.of("generated-sources-avro") - val testVelocityToolClassesDir = Path.of("src/test/resources/templates") + val testAvroFilesDir = Path.of("src/test/avro") + val testAvroOutPutDir = Path.of("generated-sources-avro") + val testVelocityToolClassesDir = Path.of("src/test/resources/templates") - val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") + val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") - testAvroFilesDir.copyToRecursively( - tempAvroSrcDir, - overwrite = true, - followLinks = false - ) + testAvroFilesDir.copyToRecursively(tempAvroSrcDir, overwrite = true, followLinks = false) - testVelocityToolClassesDir.copyToRecursively( - tempVelocityToolClassesDir, - overwrite = true, - followLinks = false - ) + testVelocityToolClassesDir.copyToRecursively( + tempVelocityToolClassesDir, + overwrite = true, + followLinks = false, + ) - tempSettingsFile.writeText("") - tempBuildFile.writeText( - """ + tempSettingsFile.writeText("") + tempBuildFile.writeText( + """ plugins { id("eu.eventloopsoftware.avro-gradle-plugin") } @@ -198,18 +189,21 @@ class CompileAvroSchemaTaskTest { templateDirectory = "${tempDir.resolve(testVelocityToolClassesDir).toString() + "/"}" velocityToolsClassesNames = listOf("java.lang.String") } - """.trimIndent() - ) + """ + .trimIndent() + ) - // when - val result = GradleRunner.create() + // when + val result = + GradleRunner.create() .withProjectDir(tempDir.toFile()) .withArguments("avroGenerateJavaClasses") .withPluginClasspath() .forwardOutput() // to see printLn in code .build() - val expectedFiles = setOf( + val expectedFiles = + setOf( "SchemaPrivacy.java", "SchemaUser.java", "PrivacyImport.java", @@ -217,38 +211,34 @@ class CompileAvroSchemaTaskTest { "PrivacyDirectImport.java", "ProtocolTest.java", "ProtocolPrivacy.java", - "ProtocolUser.java" + "ProtocolUser.java", ) - // then - assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateJavaClasses")?.outcome) - assertFilesExist(testOutPutDirectory, expectedFiles) + // then + assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateJavaClasses")?.outcome) + assertFilesExist(testOutPutDirectory, expectedFiles) - val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() - assertTrue(schemaUserContent.contains("It works!")) - } + val schemaUserContent = testOutPutDirectory.resolve("SchemaUser.java").readText() + assertTrue(schemaUserContent.contains("It works!")) + } - @Test - fun `plugin executes avroGenerateJavaClasses task successfully - custom recordSpecificClass`() { - // given - val tempSettingsFile = tempDir.resolve("settings.gradle.kts") - val tempBuildFile = tempDir.resolve("build.gradle.kts") - val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() + @Test + fun `plugin executes avroGenerateJavaClasses task successfully - custom recordSpecificClass`() { + // given + val tempSettingsFile = tempDir.resolve("settings.gradle.kts") + val tempBuildFile = tempDir.resolve("build.gradle.kts") + val tempAvroSrcDir = tempDir.resolve("src/test/avro").createDirectories() - val testAvroFiles = Path.of("src/test/avro") - val testAvroOutPutDir = Path.of("generated-sources/avro") + val testAvroFiles = Path.of("src/test/avro") + val testAvroOutPutDir = Path.of("generated-sources/avro") - val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") + val testOutPutDirectory = tempDir.resolve("build/$testAvroOutPutDir/test") - testAvroFiles.copyToRecursively( - tempAvroSrcDir, - overwrite = true, - followLinks = false - ) + testAvroFiles.copyToRecursively(tempAvroSrcDir, overwrite = true, followLinks = false) - tempSettingsFile.writeText("") - tempBuildFile.writeText( - """ + tempSettingsFile.writeText("") + tempBuildFile.writeText( + """ plugins { id("eu.eventloopsoftware.avro-gradle-plugin") } @@ -258,42 +248,39 @@ class CompileAvroSchemaTaskTest { outputDirectory = "$testAvroOutPutDir" recordSpecificClass = "org.apache.avro.custom.CustomRecordBase" } - """.trimIndent() - ) + """ + .trimIndent() + ) - // when - val result = GradleRunner.create() + // when + val result = + GradleRunner.create() .withProjectDir(tempDir.toFile()) .withArguments("avroGenerateJavaClasses") .withPluginClasspath() .forwardOutput() // to see printLn in code .build() - // then - assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateJavaClasses")?.outcome) - - val outPutFile = testOutPutDirectory.resolve("SchemaCustom.java") - assertTrue(outPutFile.toFile().exists()) - - val extendsLines = outPutFile.readLines() - .filter { line -> line.contains("class SchemaCustom extends ") } - assertEquals(1, extendsLines.size) + // then + assertEquals(TaskOutcome.SUCCESS, result.task(":avroGenerateJavaClasses")?.outcome) - val extendLine = extendsLines[0] - assertTrue(extendLine.contains("org.apache.avro.custom.CustomRecordBase")) - assertFalse(extendLine.contains("org.apache.avro.specific.SpecificRecordBase")) - } + val outPutFile = testOutPutDirectory.resolve("SchemaCustom.java") + assertTrue(outPutFile.toFile().exists()) + val extendsLines = outPutFile.readLines().filter { line -> line.contains("class SchemaCustom extends ") } + assertEquals(1, extendsLines.size) - private fun assertFilesExist(directory: Path, expectedFiles: Set) { - assertTrue(directory.exists(), "Directory $directory does not exist") - assertTrue(expectedFiles.isNotEmpty()) + val extendLine = extendsLines[0] + assertTrue(extendLine.contains("org.apache.avro.custom.CustomRecordBase")) + assertFalse(extendLine.contains("org.apache.avro.specific.SpecificRecordBase")) + } - val filesInDirectory: Set = directory - .listDirectoryEntries() - .map { it.fileName.toString() }.toSet() + private fun assertFilesExist(directory: Path, expectedFiles: Set) { + assertTrue(directory.exists(), "Directory $directory does not exist") + assertTrue(expectedFiles.isNotEmpty()) - assertEquals(expectedFiles, filesInDirectory) - } + val filesInDirectory: Set = directory.listDirectoryEntries().map { it.fileName.toString() }.toSet() + assertEquals(expectedFiles, filesInDirectory) + } } From 201e4fb2497e16543e06984645e811d21fde8bcf Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 16:31:07 +0100 Subject: [PATCH 82/85] AVRO-4223 Add Spotless config --- lang/java/gradle-plugin/pom.xml | 44 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/lang/java/gradle-plugin/pom.xml b/lang/java/gradle-plugin/pom.xml index c2547e3491b..887689f5df4 100644 --- a/lang/java/gradle-plugin/pom.xml +++ b/lang/java/gradle-plugin/pom.xml @@ -16,8 +16,8 @@ limitations under the License. --> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 @@ -36,8 +36,6 @@ ${project.parent.parent.basedir} 3.3.0 - - true @@ -108,6 +106,44 @@ + + com.diffplug.spotless + spotless-maven-plugin + + + + + src/main/kotlin/**/*.kt + src/test/kotlin/**/*.kt + + + + 120 + + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +*/ + + + + + From 8428a162e6d2a949405efcc81bee2d101a3a09e8 Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 16:33:16 +0100 Subject: [PATCH 83/85] AVRO-4223 Format --- lang/java/gradle-plugin/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/lang/java/gradle-plugin/build.gradle.kts b/lang/java/gradle-plugin/build.gradle.kts index 611123cfdab..8142af5a258 100644 --- a/lang/java/gradle-plugin/build.gradle.kts +++ b/lang/java/gradle-plugin/build.gradle.kts @@ -44,7 +44,6 @@ kotlin { jvmToolchain(21) } - gradlePlugin { plugins { website = "https://avro.apache.org/" From e1d5284ff62f8b993bfcfad70d0ee3df273564bd Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 16:38:47 +0100 Subject: [PATCH 84/85] AVRO-4223 Format --- lang/java/gradle-plugin/build.gradle.kts | 49 +++++++++++------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/lang/java/gradle-plugin/build.gradle.kts b/lang/java/gradle-plugin/build.gradle.kts index 8142af5a258..7f297f719af 100644 --- a/lang/java/gradle-plugin/build.gradle.kts +++ b/lang/java/gradle-plugin/build.gradle.kts @@ -15,45 +15,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - plugins { - kotlin("jvm") version "2.2.10" - `java-gradle-plugin` - id("com.gradle.plugin-publish") version "2.0.0" + kotlin("jvm") version "2.2.10" + `java-gradle-plugin` + id("com.gradle.plugin-publish") version "2.0.0" } group = "eu.eventloopsoftware" + version = "0.1.0" repositories { - mavenCentral() - mavenLocal() + mavenCentral() + mavenLocal() } dependencies { - // TODO: for release use ${version} - implementation("org.apache.avro:avro-compiler:1.12.1") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api:2.3.0") - testImplementation(kotlin("test")) + // TODO: for release use ${version} + implementation("org.apache.avro:avro-compiler:1.12.1") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api:2.3.0") + testImplementation(kotlin("test")) } -tasks.test { - useJUnitPlatform() -} -kotlin { - jvmToolchain(21) -} +tasks.test { useJUnitPlatform() } + +kotlin { jvmToolchain(21) } gradlePlugin { - plugins { - website = "https://avro.apache.org/" - vcsUrl = "https://github.com/apache/avro.git" - register("gradlePlugin") { - id = "eu.eventloopsoftware.avro-gradle-plugin" - displayName = "Avro Gradle Plugin" - description = "Avro Gradle plugin for generating Java code" - tags = listOf("avro", "kotlin", "java", "apache") - implementationClass = "eu.eventloopsoftware.avro.gradle.plugin.AvroGradlePlugin" - } + plugins { + website = "https://avro.apache.org/" + vcsUrl = "https://github.com/apache/avro.git" + register("gradlePlugin") { + id = "eu.eventloopsoftware.avro-gradle-plugin" + displayName = "Avro Gradle Plugin" + description = "Avro Gradle plugin for generating Java code" + tags = listOf("avro", "kotlin", "java", "apache") + implementationClass = "eu.eventloopsoftware.avro.gradle.plugin.AvroGradlePlugin" } + } } From 2db64b135a4a20e2ca61fbff610edf8eb84bbcef Mon Sep 17 00:00:00 2001 From: H de Vries Date: Mon, 2 Feb 2026 16:45:06 +0100 Subject: [PATCH 85/85] AVRO-4223 Format --- lang/java/gradle-plugin/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/java/gradle-plugin/build.gradle.kts b/lang/java/gradle-plugin/build.gradle.kts index 7f297f719af..6e293a4e4cd 100644 --- a/lang/java/gradle-plugin/build.gradle.kts +++ b/lang/java/gradle-plugin/build.gradle.kts @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + plugins { kotlin("jvm") version "2.2.10" `java-gradle-plugin`