diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 7c0d6045..4308ca98 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -18,21 +18,21 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'adopt' - java-version: 11 + java-version: 17 cache: gradle - uses: subosito/flutter-action@v2 with: flutter-version: '3.0.5' channel: 'stable' - - uses: gradle/gradle-build-action@v2.4.2 + - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.4.2 + gradle-version: 8.3 arguments: clean build koverMergedXmlReport -p "lib" - uses: codecov/codecov-action@v2 diff --git a/.github/workflows/dokka.yml b/.github/workflows/dokka.yml index 043eb54f..89afdbd0 100644 --- a/.github/workflows/dokka.yml +++ b/.github/workflows/dokka.yml @@ -19,16 +19,16 @@ jobs: timeout-minutes: 60 steps: - name: 'checkout source' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'setup java' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'adopt' - java-version: 11 + java-version: 17 - name: 'setup gradle' - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.4.2 + gradle-version: 8.3 - name: 'setup flutter' uses: subosito/flutter-action@v2 with: diff --git a/.github/workflows/kradle.yml b/.github/workflows/kradle.yml deleted file mode 100644 index ef4aad9f..00000000 --- a/.github/workflows/kradle.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Publish kradle executable. -on: - push: -env: - KLUTTER_PRIVATE_USERNAME: ${{ secrets.KLUTTER_PRIVATE_USERNAME }} - KLUTTER_PRIVATE_PASSWORD: ${{ secrets.KLUTTER_PRIVATE_PASSWORD }} - KLUTTER_PRIVATE_URL: ${{ secrets.KLUTTER_PRIVATE_URL }} - KLUTTER_JETBRAINS_CERTIFICATE_CHAINS: ${{ secrets.KLUTTER_JETBRAINS_CERTIFICATE_CHAINS }} - KLUTTER_JETBRAINS_PRIVATE_KEY: ${{ secrets.KLUTTER_JETBRAINS_PRIVATE_KEY }} - KLUTTER_JETBRAINS_PRIVATE_KEY_PASSWORD: ${{ secrets.KLUTTER_JETBRAINS_PRIVATE_KEY_PASSWORD }} -jobs: - archive-build-artifacts: - strategy: - matrix: - os: [macos-latest] # don't need windows anymore...? windows-latest - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - steps: - - name: 'checkout source' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: 'install java' - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: 11 - cache: gradle - - name: 'build distribution' - uses: gradle/gradle-build-action@v2.4.2 - with: - gradle-version: 7.4.2 - arguments: clean build -p "lib/kradle" - - name: 'copy jar to kradle folder' - run: cp ./lib/kradle/build/libs/kradle-wrapper.jar kradle/lib - - name: 'create zip file' - uses: vimtor/action-zip@v1.1 - with: - files: kradle - recursive: true - dest: kradlew.zip - - name: 'upload kradle folder' - uses: actions/upload-artifact@v2 - with: - name: kradlew - path: ${{ github.workspace }}/kradlew.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index 61682247..3e8b7a82 100644 --- a/.gitignore +++ b/.gitignore @@ -88,7 +88,3 @@ hs_err_pid* /lib-test/rune2e.properties /lib/gradle/src/main/resources/publish.properties /test-ksp/local.properties -/lib/gradle/src/main/resources/kradlew -/lib/gradle/src/main/resources/kradlew.bat -/kradle/kradlew -/kradle/kradlew.bat diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cca623..01baaa49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ # CHANGELOG +## v2024.1.1.beta +- Remove kradle module from project (kradle is the default executable since [kradle](..%2Fklutter-dart%2Fkradle) 3.0.0). +- Add support for protocol buffers. + ## v2023.3.1.beta -- Refactored task module to package in kore. -- Removed task module from BOM e.a. -- Renamed kommand to kradle. +- Refactor task module to package in kore. +- Remove task module from BOM e.a. +- Rename kommand to kradle. ## v2023.2.2.beta - Remove example/integration_test folder on project init from example app. @@ -15,8 +19,6 @@ - Uses [klutter-dart](https://pub.dev/packages/klutter) version 1.0.0. - Uses [klutter-dart-ui](https://pub.dev/packages/klutter_ui) version 1.0.0. -## v2023.3.1.beta - ## v2023.1.1.beta - Added support for request parameters. - Added support for streaming data from platform to ui (using EventChannel). diff --git a/LICENSE b/LICENSE index f7cea8cd..2628c043 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 - 2023 Buijs Software +Copyright (c) 2021 - 2024 Buijs Software Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 004b9117..dec0b409 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ Klutter is a framework which interconnects Flutter and Kotlin Multiplatform. It can be used to create Flutter plugins. - ### Getting started - Start a new project with the - [Intellij](https://buijs.dev/klutter-3/) plugin diff --git a/build.gradle.kts b/build.gradle.kts index 4e59fc73..0c094c7d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.7.10" + kotlin("jvm") version "1.9.10" } buildscript { @@ -13,8 +13,8 @@ buildscript { } dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") - classpath("com.android.tools.build:gradle:7.2.2") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") + classpath("com.android.tools.build:gradle:8.1.4") } } diff --git a/gradle.properties b/gradle.properties index 62f26a22..2e6f8440 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xms3096M -Xmx8192M -Dkotlin.daemon.jvm.options\="-Xmx8192M" kotlin.mpp.stability.nowarn=true -android.disableAutomaticComponentCreation=true kotlin.mpp.enableCInteropCommonization=true -kotlin.native.cacheKind.iosArm64=none \ No newline at end of file +kotlin.native.cacheKind.iosArm64=none +kotlin.stdlib.default.dependency = false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fce..db9a6b82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kradle.yaml b/kradle.yaml deleted file mode 100644 index 4640f066..00000000 --- a/kradle.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bom-version: '2023.3.1.beta' -flutter-version: '3.10.6' \ No newline at end of file diff --git a/kradle/kradlew b/kradle/kradlew deleted file mode 100755 index b97d6a0a..00000000 --- a/kradle/kradlew +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# kradlew start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh kradlew -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and KRADLEW_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}.." && pwd -P ) || exit - -APP_NAME="kradlew" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and KRADLEW_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/lib/kradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || 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 - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - 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 KRADLEW_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 - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $KRADLEW_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - -classpath "$CLASSPATH" \ - dev.buijs.klutter.kradle.MainKt \ - "$@" - -# 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 $KRADLEW_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/kradle/kradlew.bat b/kradle/kradlew.bat deleted file mode 100755 index f508353a..00000000 --- a/kradle/kradlew.bat +++ /dev/null @@ -1,88 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem kradlew 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=. -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 KRADLEW_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\lib\kradle-wrapper.jar - -@rem Execute kradlew -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %KRADLEW_OPTS% -classpath "%CLASSPATH%" dev.buijs.klutter.kradle.MainKt %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable KRADLEW_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%KRADLEW_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/kradlew b/kradlew deleted file mode 100755 index 134a950e..00000000 --- a/kradlew +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# kradlew start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh kradlew -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and KRADLEW_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}.." && pwd -P ) || exit - -APP_NAME="kradlew" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and KRADLEW_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# 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=./kradle/lib/kradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || 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 - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - 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 KRADLEW_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 - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $KRADLEW_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - -classpath "$CLASSPATH" \ - dev.buijs.klutter.kradle.MainKt \ - "$@" - -# 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 $KRADLEW_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/kradlew.bat b/kradlew.bat deleted file mode 100755 index fec2a779..00000000 --- a/kradlew.bat +++ /dev/null @@ -1,88 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem kradlew 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=. -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 KRADLEW_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=\kradle\lib\kradle-wrapper.jar - -@rem Execute kradlew -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %KRADLEW_OPTS% -classpath "%CLASSPATH%" dev.buijs.klutter.kradle.MainKt %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable KRADLEW_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%KRADLEW_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lib-build/build.gradle.kts b/lib-build/build.gradle.kts index 23c2daa9..285c4461 100644 --- a/lib-build/build.gradle.kts +++ b/lib-build/build.gradle.kts @@ -1,8 +1,14 @@ plugins { - kotlin("jvm") version "1.7.10" + kotlin("jvm") version "1.9.10" id("java-gradle-plugin") } +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + gradlePlugin { plugins.register("klutter") { id = "klutter" @@ -18,8 +24,7 @@ buildscript { } dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") - classpath("com.android.tools.build:gradle:7.2.2") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") } } @@ -32,7 +37,7 @@ allprojects { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") implementation("org.gradle.kotlin:gradle-kotlin-dsl-plugins:2.4.0") implementation(gradleApi()) } \ No newline at end of file diff --git a/lib-build/src/main/kotlin/dev/buijs/klutter/KlutterInternalPlugin.kt b/lib-build/src/main/kotlin/dev/buijs/klutter/KlutterInternalPlugin.kt index 74b9a890..58d36206 100644 --- a/lib-build/src/main/kotlin/dev/buijs/klutter/KlutterInternalPlugin.kt +++ b/lib-build/src/main/kotlin/dev/buijs/klutter/KlutterInternalPlugin.kt @@ -38,7 +38,6 @@ object ProjectVersions { val kore = versions("kore.version") val bom = versions("bom.version") val annotations = versions("annotations.version") - val kradle = versions("kradle.version") val gradle = versions("plugin.gradle.version") val jetbrains = versions("plugin.jetbrains.version") val kompose = versions("kompose.version") diff --git a/lib-build/src/main/resources/publish.properties b/lib-build/src/main/resources/publish.properties index 965edf24..bc6f13c9 100644 --- a/lib-build/src/main/resources/publish.properties +++ b/lib-build/src/main/resources/publish.properties @@ -1,9 +1,8 @@ -annotations.version=2023.3.1.beta -bom.version=2023.3.1.beta -compiler.version=2023.3.1.beta -flutter.engine.version=2023.3.1.beta -kompose.version=2023.3.1.beta -kore.version=2023.3.1.beta -kradle.version=2023.3.1.beta -plugin.gradle.version=2023.3.1.beta -plugin.jetbrains.version=2023.3.1.beta \ No newline at end of file +annotations.version=2024.1.1.beta +bom.version=2024.1.1.beta +compiler.version=2024.1.1.beta +flutter.engine.version=2024.1.1.beta +kompose.version=2024.1.1.beta +kore.version=2024.1.1.beta +plugin.gradle.version=2024.1.1.beta +plugin.jetbrains.version=2024.1.1.beta \ No newline at end of file diff --git a/lib-build/src/main/resources/repository.properties b/lib-build/src/main/resources/repository.properties deleted file mode 100644 index 104d05e7..00000000 --- a/lib-build/src/main/resources/repository.properties +++ /dev/null @@ -1,3 +0,0 @@ -repo.username=hi -repo.password=hi -repo.endpoint=hi \ No newline at end of file diff --git a/lib-test/build.gradle.kts b/lib-test/build.gradle.kts index e2ef54a3..5a112a42 100644 --- a/lib-test/build.gradle.kts +++ b/lib-test/build.gradle.kts @@ -8,8 +8,14 @@ plugins { java { withJavadocJar() withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } } sourceSets { @@ -28,13 +34,13 @@ sourceSets { dependencies { //Kotlin - implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.10") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22") implementation("org.jetbrains.kotlin:kotlin-compiler:1.7.10") //Jackson for XML - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.2") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.1") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1") //Logging implementation("org.slf4j:slf4j-api:2.0.7") @@ -45,7 +51,7 @@ dependencies { api(gradleTestKit()) // Spock - api("org.codehaus.groovy:groovy-all:3.0.9") + api("org.codehaus.groovy:groovy-all:3.0.17") api("org.spockframework:spock-core:2.2-M1-groovy-3.0") // Mockingjay diff --git a/lib-test/src/main/groovy/dev/buijs/klutter/kore/test/TestUtil.groovy b/lib-test/src/main/groovy/dev/buijs/klutter/kore/test/TestUtil.groovy index 86542228..c52c966a 100644 --- a/lib-test/src/main/groovy/dev/buijs/klutter/kore/test/TestUtil.groovy +++ b/lib-test/src/main/groovy/dev/buijs/klutter/kore/test/TestUtil.groovy @@ -19,7 +19,6 @@ * SOFTWARE. * */ - package dev.buijs.klutter.kore.test class TestUtil { diff --git a/lib/annotations/annotations.podspec b/lib/annotations/annotations.podspec index 4ca5521d..86723e47 100644 --- a/lib/annotations/annotations.podspec +++ b/lib/annotations/annotations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'annotations' - spec.version = '2023.3.1.beta' + spec.version = '2024.1.1.beta' spec.homepage = 'https://buijs.dev' spec.source = { :http=> ''} spec.authors = '' @@ -22,8 +22,8 @@ Pod::Spec.new do |spec| :execution_position => :before_compile, :shell_path => '/bin/sh', :script => <<-SCRIPT - if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then - echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\"" + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" exit 0 fi set -ev diff --git a/lib/annotations/build.gradle.kts b/lib/annotations/build.gradle.kts index 8f1873fc..849e1cd1 100644 --- a/lib/annotations/build.gradle.kts +++ b/lib/annotations/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("plugin.serialization") version "1.7.10" + kotlin("plugin.serialization") version "1.9.10" kotlin("multiplatform") kotlin("native.cocoapods") id("com.android.library") @@ -10,16 +10,21 @@ plugins { group = "dev.buijs.klutter" version = dev.buijs.klutter.ProjectVersions.annotations +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + kotlin { - android { + androidTarget { publishLibraryVariants("release", "debug") } jvm() iosX64() iosArm64() - iosArm32() iosSimulatorArm64() cocoapods { @@ -35,7 +40,7 @@ kotlin { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-common") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") } } @@ -56,20 +61,14 @@ kotlin { val jvmTest by getting val androidMain by getting - val androidAndroidTestRelease by getting - - val androidTest by getting { - dependsOn(androidAndroidTestRelease) - } + val androidUnitTest by getting - val iosArm32Main by getting val iosX64Main by getting val iosArm64Main by getting val iosSimulatorArm64Main by getting val iosMain by creating { dependsOn(commonMain) iosX64Main.dependsOn(this) - iosArm32Main.dependsOn(this) iosArm64Main.dependsOn(this) iosSimulatorArm64Main.dependsOn(this) } @@ -78,11 +77,12 @@ kotlin { } android { + namespace = "dev.buijs.klutter.annotations" compileSdk = 31 sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 21 - targetSdk = 31 + //targetSdk = 31 } } @@ -110,7 +110,7 @@ publishing { tasks.withType().configureEach { - outputDirectory.set(buildDir.resolve("dokka")) + outputDirectory.set(layout.buildDirectory.dir("dokka").get().asFile) dokkaSourceSets { register("annotations4Jvm") { @@ -139,13 +139,13 @@ tasks.withType().configureEach { tasks.named("iosX64Test", org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest::class.java).configure { // ENV VAR is set in GH where iPhone 14 is not available if(System.getenv("KLUTTER_PRIVATE_URL") == null) { - deviceId = "iPhone 14 Pro Max" + device = "iPhone 14 Pro Max" } } tasks.named("iosSimulatorArm64Test", org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest::class.java).configure { // ENV VAR is set in GH where iPhone 14 is not available if(System.getenv("KLUTTER_PRIVATE_URL") == null) { - deviceId = "iPhone 14 Pro Max" + device = "iPhone 14 Pro Max" } } \ No newline at end of file diff --git a/lib/annotations/module.md b/lib/annotations/module.md index 2fd86a85..d95a1781 100644 --- a/lib/annotations/module.md +++ b/lib/annotations/module.md @@ -104,7 +104,7 @@ Also add the json dependency to the commonMain sourceset: ```kotlin val commonMain by getting { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") } } ``` diff --git a/lib/annotations/src/androidMain/AndroidManifest.xml b/lib/annotations/src/androidMain/AndroidManifest.xml deleted file mode 100644 index a5e77f1f..00000000 --- a/lib/annotations/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 4e086e3c..255493bd 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -2,7 +2,7 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { kotlin("jvm") - id("org.jetbrains.dokka") version "1.6.10" + id("org.jetbrains.dokka") version "1.9.20" id("org.jetbrains.kotlinx.kover") version "0.5.1" id("klutter") } @@ -11,6 +11,12 @@ subprojects { plugins.apply("org.jetbrains.dokka") } +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + kover { // KOVER destroys running with coverage from IDE @@ -33,12 +39,6 @@ kover { // a test-only module ":lib-test", - - // a test-only module - ":test-integration", - - // for KSP testing only - ":test-ksp", ) } @@ -51,7 +51,7 @@ tasks.withType().configureEach { } tasks.dokkaHtmlMultiModule.configure { - outputDirectory.set(layout.buildDirectory.dir("dokkaSite").map { it.asFile }) + outputDirectory.set(layout.buildDirectory.dir("dokkaSite")) } tasks.koverMergedXmlReport { diff --git a/lib/compiler/build.gradle.kts b/lib/compiler/build.gradle.kts index c8fb9889..5d5c5503 100644 --- a/lib/compiler/build.gradle.kts +++ b/lib/compiler/build.gradle.kts @@ -3,15 +3,21 @@ plugins { id("klutter") id("groovy") id("maven-publish") - id("java-library") + //id("java-library") } -java { - withJavadocJar() - withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} +//java { +// withJavadocJar() +// withSourcesJar() +// sourceCompatibility = JavaVersion.VERSION_17 +// targetCompatibility = JavaVersion.VERSION_17 +//} + +//kotlin { +// jvmToolchain { +// languageVersion.set(JavaLanguageVersion.of(17)) +// } +//} sourceSets { main { @@ -37,14 +43,15 @@ dependencies { // KSP for annotation scanning implementation(kotlin("stdlib")) - implementation("com.google.devtools.ksp:symbol-processing-api:1.8.20-1.0.11") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.6.2") // Jackson for XML and YAML - implementation("com.fasterxml.jackson.core:jackson-databind:2.14.2") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.2") + implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.1") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1") // Logging implementation("org.slf4j:slf4j-api:2.0.7") @@ -137,6 +144,6 @@ fun Dependency.getLocalOrProjectVersion() = when(name) { "kore" -> dev.buijs.klutter.ProjectVersions.kore "kotlin-stdlib" -> - "1.7.10" + "1.9.22" else -> version } \ No newline at end of file diff --git a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/Processor.kt b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/Processor.kt index 03abde6c..0cfb7dda 100644 --- a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/Processor.kt +++ b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/Processor.kt @@ -28,14 +28,14 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.symbol.KSAnnotated import dev.buijs.klutter.compiler.scanner.scanForControllers import dev.buijs.klutter.compiler.scanner.scanForResponses +import dev.buijs.klutter.compiler.scanner.scanForResponsesProtobuf import dev.buijs.klutter.compiler.validator.* -import dev.buijs.klutter.compiler.validator.Invalid -import dev.buijs.klutter.compiler.validator.InvalidSquintMessages -import dev.buijs.klutter.compiler.validator.Valid -import dev.buijs.klutter.compiler.validator.ValidSquintMessages import dev.buijs.klutter.compiler.wrapper.KCLogger import dev.buijs.klutter.kore.ast.Controller +import dev.buijs.klutter.kore.ast.CustomType import dev.buijs.klutter.kore.ast.SquintMessageSource +import dev.buijs.klutter.kore.common.EitherNok +import dev.buijs.klutter.kore.common.EitherOk import dev.buijs.klutter.kore.common.ExcludeFromJacocoGeneratedReport import dev.buijs.klutter.kore.common.verifyExists import dev.buijs.klutter.kore.project.* @@ -70,6 +70,13 @@ class Processor( private var messages: List? = null private var controllers: List? = null + /** + * List of FQDN names for each @Response annotated class. + * + * If protobuf is NOT enabled then this list stays empty. + */ + private var responseClassNames: List? = null + private val flutterFolder: FlutterDistributionFolderName get() { val os = currentOperatingSystem @@ -90,9 +97,18 @@ class Processor( return folder } + @ExcludeFromJacocoGeneratedReport override fun process(resolver: Resolver): List { kcLogger = KCLogger(log, options.outputFolder) + kcLogger!!.logCompilerOptions(options) + + responseClassNames = if(options.isProtobufEnabled) { + resolver.findAndValidateResponsesForProtobuf() + } else { + emptyList() + } + messages = resolver.findAndValidateMessages() controllers = resolver.findAndValidateControllers() generateCodeAndInstall() @@ -101,7 +117,7 @@ class Processor( @ExcludeFromJacocoGeneratedReport private fun Resolver.findAndValidateMessages() = - scanForResponses(resolver = this, outputFolder = options.outputFolder) + scanForResponses(resolver = this, outputFolder = options.outputFolder, isProtobufEnabled = options.isProtobufEnabled) .validateResponses().let { when (it) { is ValidSquintMessages -> it.data @@ -112,12 +128,43 @@ class Processor( } } + /** + * Returns the fully qualified class names of @Response annotated classes. + */ + @ExcludeFromJacocoGeneratedReport + private fun Resolver.findAndValidateResponsesForProtobuf() = + scanForResponsesProtobuf(resolver = this, outputFolder = options.outputFolder) + .let { results -> + when { + results.any { result -> result is EitherNok } -> { + val warnings = results + .filterIsInstance>() + .map { it.data } + kcLogger?.logAnnotationScanningWarnings(warnings, "@Response") + null + } + + else -> results + .filterIsInstance>() + .map { it.data } + } + } + @ExcludeFromJacocoGeneratedReport private fun Resolver.findAndValidateControllers() = scanForControllers( resolver = this, outputFolder = options.outputFolder, - responses = messages?.map { it.type }?.toSet() ?: emptySet()) + responses = messages + ?.map { it.type } + ?.toSet() + ?: responseClassNames + ?.map { CustomType( + className = it.substringAfterLast("."), + packageName = it.substringBeforeLast(".")) + } + ?.toSet() + ?: emptySet()) .validateControllers(messages?.map { it.type } ?: emptyList()).let { when (it) { is Valid -> it.data @@ -128,12 +175,19 @@ class Processor( } } + @ExcludeFromJacocoGeneratedReport private fun generateCodeAndInstall() { // If null then there are validation errors and code generation should be skipped - if (messages == null || controllers == null) return + if (messages == null || controllers == null || responseClassNames == null) return + + val responsesCount = if(messages!!.isEmpty()) { + responseClassNames!!.size + } else { + messages!!.size + } - kcLogger?.logSquintInfo(project, messages!!.size, controllers!!.size) + kcLogger?.logDartCodeGenInfo(project, responsesCount, controllers!!.size) val codegenOptions = GenerateCodeOptions( project = project, @@ -141,6 +195,7 @@ class Processor( excludeArmArcFromPodspec = currentArchitecture == Architecture.X64, controllers = controllers!!, messages = messages!!, + responseClassNames = responseClassNames!!, flutterFolder = flutterFolder, log = { str -> kcLogger?.info("Running dart command:\n$str") }) @@ -158,11 +213,23 @@ private fun KCLogger.logAnnotationScanningWarnings(warnings: List, annot } @ExcludeFromJacocoGeneratedReport -private fun KCLogger.logSquintInfo(project: Project, messageCount: Int, controllerCount: Int) { +private fun KCLogger.logDartCodeGenInfo(project: Project, responseCount: Int, controllerCount: Int) { info("=============================================================") info("Generating dart code") info("Root folder: ${project.root.folder.absolutePath}") - info("Response count: $messageCount") + info("Response count: $responseCount") info("Controller count: $controllerCount") info("=============================================================") +} + +@ExcludeFromJacocoGeneratedReport +private fun KCLogger.logCompilerOptions(options: ProcessorOptions) { + info("=============================================================") + info("Compiler Options") + info("Project folder: ${options.projectFolder}") + info("Output folder: ${options.outputFolder}") + info("Flutter version: ${options.flutterVersion}") + info("Adapter codegen isEnabled: ${options.generateAdapters}") + info("Protobuf isEnabled: ${options.isProtobufEnabled}") + info("=============================================================") } \ No newline at end of file diff --git a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/ProcessorOptions.kt b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/ProcessorOptions.kt index f415adc8..407a3ad7 100644 --- a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/ProcessorOptions.kt +++ b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/processor/ProcessorOptions.kt @@ -39,17 +39,18 @@ data class ProcessorOptions( val outputFolder: File, val flutterVersion: String, val generateAdapters: Boolean, + val isProtobufEnabled: Boolean, ) /** - * Possible options that can be set in the ksp DSL to configure - * Klutter code scanning/generation. + * Possible options that can be set in the ksp DSL to configure Klutter code scanning/generation. */ internal enum class ProcessorOption(val value: String) { PROJECT_FOLDER("klutterProjectFolder"), OUTPUT_FOLDER("klutterOutputFolder"), GENERATE_ADAPTERS("klutterGenerateAdapters"), FLUTTER_SDK_VERSION("flutterVersion"), + PROTOBUF_ENABLED("klutterProtobufEnabled") } /** @@ -84,7 +85,8 @@ internal fun processorOptions( projectFolder = options.projectFolder(), outputFolder = outputFolder?.let { File(it) } ?: options.outputFolder(), generateAdapters = skipCodeGen?.let { !it } ?: options.boolean(GENERATE_ADAPTERS), - flutterVersion = flutterOrNull ?: options.flutterVersion() + flutterVersion = flutterOrNull ?: options.flutterVersion(), + isProtobufEnabled = options.boolean(PROTOBUF_ENABLED, defaultValue = false), ).also { kcLogger?.info("Determined Processor Options: $it") } } @@ -142,5 +144,5 @@ private fun Map.flutterVersion(): String { /** * Return option argument as boolean value or default to true if not set. */ -private fun Map.boolean(option: ProcessorOption) = - this[option.value]?.let { it.trim().lowercase() == "true" } ?: true \ No newline at end of file +private fun Map.boolean(option: ProcessorOption, defaultValue: Boolean = true) = + this[option.value]?.let { it.trim().lowercase() == "true" } ?: defaultValue \ No newline at end of file diff --git a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseOutput.kt b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseOutput.kt index 73f1c88e..66e477bc 100644 --- a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseOutput.kt +++ b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseOutput.kt @@ -57,6 +57,7 @@ internal fun Either.writeOutput( is EitherOk -> { val file = folder.resolve("$squintJsonMetadataPrefix${data.type.className.lowercase()}.json") file.createNewFile() + when (data.squintType) { is SquintCustomType -> file.writeText(JSON.encodeToString(data.squintType)) @@ -67,4 +68,33 @@ internal fun Either.writeOutput( } } +} + +/** + * Write FQDN of Response class or error message to [outputFolder]. + */ +internal fun Either.writeResponseFQDN( + outputFolder: File, count: Int +): Either { + + val folder = outputFolder + .resolve("response") + .maybeCreateFolder() + + return when (this) { + is EitherNok -> { + val file = folder.resolve("${count}_invalid.txt") + file.createNewFile() + file.writeText(data) + Either.nok(data) + } + + is EitherOk -> { + val file = folder.resolve("proto_${count}.txt") + file.createNewFile() + file.writeText(data) + Either.ok(data) + } + } + } \ No newline at end of file diff --git a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseScanner.kt b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseScanner.kt index 5d3d92ec..ec248f6c 100644 --- a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseScanner.kt +++ b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/scanner/ResponseScanner.kt @@ -24,12 +24,12 @@ package dev.buijs.klutter.compiler.scanner import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSClassDeclaration import dev.buijs.klutter.compiler.wrapper.* -import dev.buijs.klutter.compiler.wrapper.KCResponse -import dev.buijs.klutter.compiler.wrapper.toKCResponse import dev.buijs.klutter.kore.ast.* import dev.buijs.klutter.kore.common.Either +import kotlinx.serialization.ExperimentalSerializationApi import java.io.File + /** * FQDN for classes annotated with @Response. */ @@ -39,10 +39,9 @@ private const val RESPONSE_ANNOTATION = /** * Get all classes with @Response annotation and convert them to [KCController]. */ -private fun getSymbolsWithResponseAnnotation(resolver: Resolver): List = +private fun getSymbolsWithResponseAnnotation(resolver: Resolver): List = resolver.getSymbolsWithAnnotation(RESPONSE_ANNOTATION) .filterIsInstance() - .map { clazz -> clazz.toKCResponse() } .toList() /** @@ -58,24 +57,59 @@ private fun getSymbolsWithResponseAnnotation(resolver: Resolver): List List = { getSymbolsWithResponseAnnotation(it) }, + isProtobufEnabled: Boolean, + scanner: (resolver: Resolver) -> List = { + getSymbolsWithResponseAnnotation(it).map { clazz -> clazz.toKCResponse() } }, ): List> = scanner.invoke(resolver) - .map { it.toSquintMessageSourceOrFail() } + .map { it.toSquintMessageSourceOrFail(isProtobufEnabled) } .mapIndexed { index, data -> data.writeOutput(outputFolder, index) } .toList() -private fun KCResponse.toSquintMessageSourceOrFail() +@JvmOverloads +internal fun scanForResponsesProtobuf( + outputFolder: File, + resolver: Resolver, + scanner: (resolver: Resolver) -> List = { getSymbolsWithResponseAnnotation(it) }, +): List> = + scanner.invoke(resolver) + .map { it.toSchemaSourceOrFail() } + .mapIndexed { index, data -> data.writeResponseFQDN(outputFolder, index) } + .toList() + +private fun KCResponse.toSquintMessageSourceOrFail(isProtobufEnabled: Boolean) : Either = when(this) { is KCEnumeration -> enumeration() - is KCMessage -> message() + is KCMessage -> message(isProtobufEnabled) +} + +private fun KSClassDeclaration.toSchemaSourceOrFail() +: Either { + + val annotationNames = annotations + .map{ it.shortName.getShortName() } + .toList() + + val isSerializableAnnotated = + annotationNames.contains("Serializable") + + val className = "$this" + val packageName = packageName.asString() + + if(!isSerializableAnnotated) + return Either.nok("Class is missing @Serializable annotation: $packageName.$className") + + if(superTypes.map { it.toString() }.toList().contains("JSON")) + return Either.nok("Class extends JSON which is incompatible with Protobuf (remove JSON interface to fix): $packageName.$className") + + return Either.ok("$packageName.$className") } -private fun KCMessage.message(): Either { +private fun KCMessage.message(isProtobufEnabled: Boolean): Either { if(!isSerializableAnnotated) return missingSerializableAnnotation() - if(!extendsJSON) + if(!extendsJSON && !isProtobufEnabled) return doesNotExtendKlutterJSON() val validTypeMembers = typeMembers diff --git a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/wrapper/KCController.kt b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/wrapper/KCController.kt index a0d73517..fc648608 100644 --- a/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/wrapper/KCController.kt +++ b/lib/compiler/src/main/kotlin/dev/buijs/klutter/compiler/wrapper/KCController.kt @@ -55,4 +55,35 @@ internal fun KSClassDeclaration.toKotlinClassWrapper(responses: Set = getConstructors().toList() + + val controllerType = annotations + .filter { it.shortName.asString() == "Controller" } + .filter { it.arguments.isNotEmpty() } + .map { it.arguments.firstOrNull { arg -> arg.name?.getShortName() == "type" } } + .filterNotNull() + .map { it.value.toString() } + .firstOrNull { it.startsWith("dev.buijs.klutter.annotations.ControllerType.") } + ?.substringAfterLast("dev.buijs.klutter.annotations.ControllerType.") + ?: "Default" + + val publisherOrNull = + superTypes.firstOrNull { it.toString() == "Publisher" } + + return KCController( + hasOneConstructor = constructors.size == 1, + firstConstructorHasNoParameters = constructors.firstOrNull()?.parameters?.isEmpty() ?: false, + events = emptyList(), + eventErrors = emptyList(), + controllerType = controllerType, + isBroadcastController = publisherOrNull != null, + broadcastTypeParameterOrBlank = publisherOrNull?.resolve()?.arguments?.first()?.type?.toString() ?: "", + className ="$this", + packageName = packageName.asString()) } \ No newline at end of file diff --git a/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/processor/ProcessorProviderSpec.groovy b/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/processor/ProcessorProviderSpec.groovy index 91587503..468ebab1 100644 --- a/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/processor/ProcessorProviderSpec.groovy +++ b/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/processor/ProcessorProviderSpec.groovy @@ -96,7 +96,7 @@ class ProcessorProviderSpec extends Specification { and: "default kradle.yaml as generated by klutter-dart" yaml.createNewFile() - yaml.write("""bom-version: '2023.3.1.beta'\nflutter-version: '3.10.6'""") + yaml.write("""bom-version: '2023.3.1.beta'\nflutter-version: '3.10.6'\n""") and: "default kradle.env as generated by klutter-dart" def envFile = projectFolder.toPath().resolve("kradle.env").toFile() @@ -126,7 +126,7 @@ class ProcessorProviderSpec extends Specification { with(processor.options$compiler) { it.flutterVersion == "3.10.6" !it.generateAdapters - //it.intelBasedBuildMachine + !it.protobufEnabled it.outputFolder.absolutePath == outputFolder.absolutePath it.projectFolder.absolutePath == projectFolder.absolutePath } diff --git a/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ControllerScannerSpec.groovy b/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ControllerScannerSpec.groovy index 137634e3..57bec01e 100644 --- a/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ControllerScannerSpec.groovy +++ b/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ControllerScannerSpec.groovy @@ -155,7 +155,7 @@ class ControllerScannerSpec extends Specification { it.data.class.simpleName == classType it.data.className == "SomeClass" it.data.packageName == "some.pack.age" - it.data.functions.size == 1 + it.data.functions.size() == 1 //noinspection GroovyAssignabilityCheck it.data.functions[0] == dummyMethod } @@ -190,7 +190,7 @@ class ControllerScannerSpec extends Specification { it.data.class.simpleName == classType it.data.className == "SomeClass" it.data.packageName == "some.pack.age" - it.data.functions.size == 1 + it.data.functions.size() == 1 //noinspection GroovyAssignabilityCheck it.data.functions[0] == dummyMethod } diff --git a/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ResponseScannerSpec.groovy b/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ResponseScannerSpec.groovy index 019453a3..1ebfcc77 100644 --- a/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ResponseScannerSpec.groovy +++ b/lib/compiler/src/test/groovy/dev/buijs/klutter/compiler/scanner/ResponseScannerSpec.groovy @@ -34,6 +34,7 @@ import dev.buijs.klutter.kore.common.EitherNok import dev.buijs.klutter.kore.common.EitherOk import kotlin.sequences.Sequence import kotlin.sequences.SequencesKt +import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification @@ -80,7 +81,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherNok) { @@ -94,7 +95,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherNok) { @@ -108,7 +109,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherNok) { @@ -122,7 +123,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherNok) { @@ -136,7 +137,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherNok) { @@ -150,7 +151,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherOk) { @@ -175,7 +176,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherNok) { @@ -192,7 +193,7 @@ class ResponseScannerSpec extends Specification { when: def result = ResponseScannerKt - .scanForResponses(outputFolder, resolver, {classes }) + .scanForResponses(outputFolder, resolver, false, {classes }) then: with(result[0] as EitherOk) { @@ -206,6 +207,8 @@ class ResponseScannerSpec extends Specification { } } + // TODO cannot mock a sealed interface + @Ignore def "When no callback is given, then default symbol-processor callback is used" () { given: Resolver resolver = Mock() diff --git a/lib/gradle/build.gradle.kts b/lib/gradle/build.gradle.kts index 72aa0806..836aada4 100644 --- a/lib/gradle/build.gradle.kts +++ b/lib/gradle/build.gradle.kts @@ -1,23 +1,17 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { kotlin("jvm") - id("com.gradle.plugin-publish") version "0.16.0" + id("com.gradle.plugin-publish") version "1.2.1" + id("com.github.johnrengelman.shadow") version "8.1.1" id("java-gradle-plugin") - id("java-library") - id("maven-publish") - // id("groovy") + id("groovy") id("klutter") } group = "dev.buijs.klutter" version = dev.buijs.klutter.ProjectVersions.gradle -java { - withJavadocJar() - withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - sourceSets { main { java { @@ -27,18 +21,21 @@ sourceSets { test { java { - srcDirs("${projectDir.absolutePath}/src/test/kotlin") + srcDirs( + "${projectDir.absolutePath}/src/test/kotlin", + "${projectDir.absolutePath}/src/test/groovy") } } } publishing { repositories { + mavenLocal() maven { url = dev.buijs.klutter.Repository.endpoint credentials { - username = dev.buijs.klutter.Repository.username - password = dev.buijs.klutter.Repository.password + username = dev.buijs.klutter.Repository.username + password = dev.buijs.klutter.Repository.password } } } @@ -48,7 +45,7 @@ publishing { groupId = "dev.buijs.klutter" artifactId = "gradle" version = dev.buijs.klutter.ProjectVersions.gradle - artifact("$projectDir/build/libs/gradle-${dev.buijs.klutter.ProjectVersions.gradle}.jar") + from(components.findByName("java")) pom { name.set("Klutter: Gradle Plugin") @@ -80,14 +77,8 @@ publishing { } } -pluginBundle { - website = "https://buijs.dev/klutter/" - vcsUrl = "https://github.com/buijs-dev/klutter" - tags = listOf("klutter", "flutter", "kotlin", "multiplatform") -} - gradlePlugin { - isAutomatedPublishing = false + isAutomatedPublishing = true plugins { create("klutterGradlePlugin") { id = "dev.buijs.klutter" @@ -103,13 +94,12 @@ gradlePlugin { dependencies { // Project implementation(project(":lib:kore")) - implementation(project(":lib:kradle")) // Kotlin: Required to check if Kotlin Multiplatform plugin is applied - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") // KSP Compiler plugin - implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.8.20-1.0.11") + implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.9.10-1.0.13") // Jackson XML/YAML parsing implementation("com.fasterxml.jackson.core:jackson-databind:2.14.2") @@ -126,7 +116,12 @@ dependencies { } -tasks.named("test") { +tasks.withType { + archiveClassifier.set("") + archiveVersion.set("") +} + +tasks.withType { useJUnitPlatform() } diff --git a/lib/gradle/module.md b/lib/gradle/module.md index 06f20166..abc683b4 100644 --- a/lib/gradle/module.md +++ b/lib/gradle/module.md @@ -7,31 +7,52 @@ Kotlin/Dart/Groovy code needed to make Flutter and KMP work together. Important: For using and/or creating Flutter plugins with Klutter you should use the pub [plugin](https://github.com/buijs-dev/klutter-dart). ## Installation -Preferred way of installing/using this plugin is by using the pub [plugin](https://github.com/buijs-dev/klutter-dart). +Preferred way of installing/using this plugin is by starting a new project with one of: +- [Intellij](https://buijs.dev/klutter-3/) plugin +- [Android Studio](https://buijs.dev/klutter-4/) plugin +- [Kradle](https://buijs.dev/kradle-1/) cli tool ## Tasks -1. [klutterExcludeArchsPlatformPodspec](#Task:%20klutterExcludeArchsPlatformPodspec) -2. [klutterGenerateAdapters](#Task:%20klutterGenerateAdapters) +The gradle plugin defines tasks for compiling the Kotlin Multiplatform module, +generating Dart code and more. Some tasks are executed automatically before (compiler plugin) +and after running a gradle build. -# Task: klutterExcludeArchsPlatformPodspec -A Flutter app using Kotlin Multiplatform code won't run on an iOS simulator. -There's an easy fix for that: Exclude arm64 architecture from the podspec file. -This task does so by adding the following 2 lines of code to the KMP module .podspec file: +1. [klutterCompileProtoSchemas](#Task:%20klutterCompileProtoSchemas) +2. [klutterCopyAarFile](#Task:%20klutterCopyAarFile) +3. [klutterCopyFramework](#Task:%20klutterCopyFramework) +4. [klutterGenerateFlutterLib](#Task:%20klutterGenerateFlutterLib) +5. [klutterGenerateProtoSchemas](#Task:%20klutterGenerateProtoSchemas) +6. [klutterGetDartProtoc](#Task:%20klutterDartProtoc) +7. [klutterGetKradle](#Task:%20klutterGetKradle) +8. [klutterGetProtoc](#Task:%20klutterGetProtoc) -``` - pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }""") - user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }""") -``` +# Task: klutterCompileProtoSchemas +Compile protocol buffer schemas which generates boilerplate code for both Kotlin and Dart. +This task does nothing when the protobuf feature is not enabled. + +# Task: klutterCopyAarFile +Copy the root/platform aar file to root/android folder. + +# Task: klutterCopyFramework +Copy the root/platform iOS Framework to root/ios folder. -# Task: klutterGenerateAdapters -The generate adapters task creates all the boilerplate code needed to make the Dart code in Flutter -communicate with Kotlin in the Multiplatform module. +# Task: klutterGenerateFlutterLib +Generate the Dart library file in the root/lib folder. -The following steps are executed to do so: -- Scan the KMP module for annotations. -- Convert Kotlin classes to Dart. -- Generate a library Dart file in the root/lib folder. -- Generate an adapter Kotlin file in the root/android folder. -- Generate an adapter Swift file in the root/ios folder. +# Task: klutterGenerateProtoSchemas +Generate protocol buffer schemas which can be used by [klutterCompileProtoSchemas](#Task:%20klutterCompileProtoSchemas). +This task does nothing when the protobuf feature is not enabled. -The generated Dart library then gives access to any native code written in Kotlin Multiplatform. \ No newline at end of file +# Task: klutterGetDartProtoc +Download the dart protobuf plugin. + +# Task: klutterGetKradle +Download the kradle cli tool. + +# Task: klutterGetProtoc +Download the protoc executable tool. The distribution url can be specified +in the kradle.env file: + +```properties +protoc.url=https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-osx-universal_binary.zip +``` diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/KlutterGradlePlugin.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/KlutterGradlePlugin.kt index 968be132..e1726479 100644 --- a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/KlutterGradlePlugin.kt +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/KlutterGradlePlugin.kt @@ -24,45 +24,169 @@ package dev.buijs.klutter.gradle import com.google.devtools.ksp.gradle.KspExtension import com.google.devtools.ksp.gradle.KspGradleSubplugin import dev.buijs.klutter.gradle.dsl.KlutterExtension +import dev.buijs.klutter.gradle.tasks.* import dev.buijs.klutter.gradle.tasks.CopyAndroidAarFileGradleTask import dev.buijs.klutter.gradle.tasks.CopyIosFrameworkGradleTask -import dev.buijs.klutter.gradle.tasks.GetKradleTask +import dev.buijs.klutter.gradle.tasks.GetProtocDartGradleTask +import dev.buijs.klutter.gradle.tasks.GetProtocGradleTask +import dev.buijs.klutter.gradle.tasks.KlutterGradleTaskName.* import dev.buijs.klutter.kore.project.kspArgumentKlutterProjectFolder import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.plugins.PluginContainer import org.gradle.api.tasks.TaskContainer +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension /** * Gradle plugin for Klutter Framework. + * + * The plugin has extension [KlutterExtension] which can be used to configure the klutter project. + * See [KlutterGradleTaskName] for all available tasks. Most tasks are run automatically when executing a Gradle build. + * See [bindPostBuildTasks]. + * + * The klutter compiler plugin is applied which handles annotation scanning, code generation and more. */ -class KlutterGradlePlugin: Plugin { +class KlutterGradlePlugin : Plugin { override fun apply(project: Project) { with(project) { - tasks.registerTasks() - plugins.applyKspPlugin() - extensions.add("klutter", KlutterExtension(project)) - - val ext = project.extensions.getByType(KspExtension::class.java) + applyAndConfigureKspExtension() + registerTasks() + addKlutterExtension() project.afterEvaluate { - ext.arg(kspArgumentKlutterProjectFolder, project.rootDir.absolutePath) + configureKotlinMultiplatformExtension { + bindPostBuildTasks() + } } } } } /** - * Apply KSP Gradle Plugin. + * Add the [KlutterExtension] to the Gradle [Project]. + */ +private fun Project.addKlutterExtension() { + extensions.add("klutter", KlutterExtension(project)) +} + +/** + * Apply the [KspGradleSubplugin] and add required arguments for the compiler plugin. + */ +private fun Project.applyAndConfigureKspExtension() { + plugins.apply(KspGradleSubplugin::class.java) + val ksp = project.extensions.getByType(KspExtension::class.java) + ksp.arg(kspArgumentKlutterProjectFolder, project.rootDir.absolutePath) +} + +/** + * Add KSP generated code to the KMP source sets. + */ +private fun Project.configureKotlinMultiplatformExtension( + /** + * Action to invoke if the [KotlinMultiplatformExtension] is found. + * + * The [KlutterGradlePlugin] is applied to the platform module which is a KMP module + * and the android module which is not a KMP module. Some configuration can only be + * done in a KMP module, which can be configured through this closure. + */ + doIfExtensionIsPresent: () -> Unit +) { + // KMP + val kmp = try { + project.extensions.getByType(KotlinMultiplatformExtension::class.java) + } catch (_: Exception) { + null + } + + if (kmp != null) { + kmp.sourceSets + .getByName("commonMain") + .kotlin + .srcDir(layout.buildDirectory.file("generated/ksp/metadata/commonMain/kotlin")) + doIfExtensionIsPresent() + } +} + +/** + * Register Klutter tasks in the Gradle [Project]. */ -private fun PluginContainer.applyKspPlugin() { - apply(KspGradleSubplugin::class.java) +private fun Project.registerTasks() { + tasks.registerTasks() } /** - * Register the custom Klutter tasks. + * Register Klutter tasks in the [TaskContainer]. */ private fun TaskContainer.registerTasks() { - register("klutterCopyAarFile", CopyAndroidAarFileGradleTask::class.java) - register("klutterCopyFramework", CopyIosFrameworkGradleTask::class.java) - register("klutterGetKradle", GetKradleTask::class.java) + for (gradleTaskName in KlutterGradleTaskName.entries) { + when (gradleTaskName) { + GenerateProtoSchemas -> { + // Should be registered directly in build.gradle.kts. + } + + CompileProtoSchemas -> + registerTask(gradleTaskName) + + CopyAndroidAarFile -> + registerTask(gradleTaskName) + + CopyIosFramework -> + registerTask(gradleTaskName) + + GenerateFlutterLib -> + registerTask(gradleTaskName) + + GetProtocDart -> + registerTask(gradleTaskName) + + GetProtoc -> + registerTask(gradleTaskName) + + GetKradle -> + registerTask(gradleTaskName) + } + } +} + +private fun Project.bindPostBuildTasks() { + tasks.bindPostBuildTasks() +} + +private fun TaskContainer.bindPostBuildTasks() { + val assembleXCFrameworkTaskName = "assemblePlatformReleaseXCFramework" + + finalizeTaskBy("build") { + +assembleXCFrameworkTaskName + +GenerateProtoSchemas + +CopyAndroidAarFile + } + + finalizeTaskBy(assembleXCFrameworkTaskName) { + +CopyIosFramework + } + + finalizeTaskBy(GenerateProtoSchemas.taskName) { + +CompileProtoSchemas + } +} + +private fun TaskContainer.finalizeTaskBy( + name: String, + builder: StringListBuilder.() -> Unit +) { + getByName(name).setFinalizedBy(StringListBuilder().also(builder).data) +} + +private class StringListBuilder { + val data: MutableList = mutableListOf() + + operator fun String.unaryPlus() { + data.add(this) + } + + operator fun KlutterGradleTaskName.unaryPlus() { + data.add(this.taskName) + } +} + +private inline fun TaskContainer.registerTask(task: KlutterGradleTaskName) { + register(task.taskName, T::class.java) } \ No newline at end of file diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterExtension.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterExtension.kt index ae7581a1..34cc0c30 100644 --- a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterExtension.kt +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterExtension.kt @@ -22,17 +22,17 @@ @file:Suppress("unused") package dev.buijs.klutter.gradle.dsl +import com.google.devtools.ksp.gradle.KspExtension import dev.buijs.klutter.kore.common.ExcludeFromJacocoGeneratedReport import org.gradle.api.Project import org.gradle.api.tasks.Internal import java.io.File -import java.util.* /** * Glue for the DSL used in a build.gradle(.kts) file and the Klutter tasks. */ @Suppress("MemberVisibilityCanBePrivate") -open class KlutterExtension(project: Project) { +open class KlutterExtension(private val project: Project) { private val handler = KlutterDependencyHandler(project) @@ -43,6 +43,11 @@ open class KlutterExtension(project: Project) { @Internal internal var plugin: Dto? = null + internal val features = mutableSetOf() + + internal val isProtobufEnabled: Boolean + get() = features.contains(KlutterFeature.PROTOBUF) + /** * Configure the Gradle Plugin for a klutter plugin (consumer or producer). */ @@ -50,6 +55,23 @@ open class KlutterExtension(project: Project) { plugin = KlutterPluginBuilder().apply(lambda).build() } + /** + * Enable or disable specific klutter features. + * + * See for feature list: [KlutterFeature]. + */ + fun feature(name: KlutterFeature, enable: Boolean = true) { + if(enable) { + features.add(name) + project.extensions.getByType(KspExtension::class.java) + .arg("klutterProtobufEnabled", "true") + } else { + features.remove(name) + project.extensions.getByType(KspExtension::class.java) + .arg("klutterProtobufEnabled", "false") + } + } + /** * Add klutter implementation dependency to this project. */ diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterFeature.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterFeature.kt new file mode 100644 index 00000000..fe99b2c7 --- /dev/null +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/dsl/KlutterFeature.kt @@ -0,0 +1,10 @@ +package dev.buijs.klutter.gradle.dsl + +enum class KlutterFeature { + /** + * Enable the protocol buffer feature. + * + * This replaces JSON (de)serialization as communication method between Kotlin and Dart. + */ + PROTOBUF +} \ No newline at end of file diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/AbstractTask.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/AbstractTask.kt index c7042df6..58d8b9bc 100644 --- a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/AbstractTask.kt +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/AbstractTask.kt @@ -23,32 +23,59 @@ package dev.buijs.klutter.gradle.tasks import dev.buijs.klutter.gradle.dsl.KlutterExtension import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.common.ExcludeFromJacocoGeneratedReport import dev.buijs.klutter.kore.project.Project import dev.buijs.klutter.kore.project.plugin -import mu.KotlinLogging import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction -private val log = KotlinLogging.logger { } +/** + * Name of a Gradle Task added by Klutter. + */ +enum class KlutterGradleTaskName(private val identifier: String) { + CopyAndroidAarFile("CopyAarFile"), + + CopyIosFramework("CopyFramework"), + + GenerateFlutterLib("GenerateFlutterLib"), + + GenerateProtoSchemas("GenerateProtoSchemas"), + + GetProtocDart("GetProtocDart"), + + GetProtoc("GetProtoc"), -internal typealias project = org.gradle.api.Project + GetKradle("GetKradle"), + + CompileProtoSchemas("CompileProtoSchemas"); + + val taskName: String + get() = "klutter$identifier" + +} /** * Parent of all Gradle Tasks. */ -internal abstract class AbstractTask: DefaultTask() { +internal abstract class AbstractTask : DefaultTask() { - init { group = "klutter" } + init { + group = "klutter" + } internal abstract fun klutterTask(): KlutterTask + @get:Internal + internal abstract val gradleTaskName: KlutterGradleTaskName + + @ExcludeFromJacocoGeneratedReport(reason = "") @TaskAction fun execute() = klutterTask().run() fun project(): Project { val ext = project.klutterExtension() val root = ext.root ?: project.rootProject.projectDir - log.info { "Klutter Gradle configured as plugin." } return root.plugin() } @@ -56,7 +83,9 @@ internal abstract class AbstractTask: DefaultTask() { internal fun org.gradle.api.Project.klutterExtension(): KlutterExtension { return extensions.getByName("klutter").let { - if (it is KlutterExtension) { it } else { + if (it is KlutterExtension) { + it + } else { throw IllegalStateException("klutter extension is not of the correct type") } } diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CodeGenTasks.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CodeGenTasks.kt new file mode 100644 index 00000000..09fe984e --- /dev/null +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CodeGenTasks.kt @@ -0,0 +1,46 @@ +/* Copyright (c) 2021 - 2023 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.gradle.tasks + +import dev.buijs.klutter.gradle.dsl.KlutterVersion +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.tasks.codegen.GenerateFlutterLibTask + +/** + * Run the [GenerateFlutterLibTask] from Gradle. + */ +internal open class GenerateFlutterLibGradleTask: AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.GenerateFlutterLib + + override fun klutterTask(): KlutterTask { + val project = project() + val root = project.root + return GenerateFlutterLibTask( + bomVersion = KlutterVersion.gradle, + pluginName = root.pluginName, + root = root, + srcFolder = root.pathToLibFolder.resolve("src") + ) + } + +} \ No newline at end of file diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CopyTasks.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CopyTasks.kt index aa4acb9d..6980f572 100644 --- a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CopyTasks.kt +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/CopyTasks.kt @@ -27,18 +27,25 @@ import dev.buijs.klutter.kore.tasks.CopyXCFrameworkTask /** * Execute task [CopyAndroidAarFileGradleTask] from Gradle. */ -internal open class CopyAndroidAarFileGradleTask: AbstractTask() { +internal open class CopyAndroidAarFileGradleTask : AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.CopyAndroidAarFile + override fun klutterTask() = CopyAarFileTask( pathToRoot = project.rootDir, pluginName = project.klutterExtension().plugin?.name, ) + } /** * Execute task [CopyXCFrameworkTask] from Gradle. */ -internal open class CopyIosFrameworkGradleTask: AbstractTask() { - override fun klutterTask() = CopyXCFrameworkTask( - pathToRoot = project.rootDir, - ) +internal open class CopyIosFrameworkGradleTask : AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.CopyIosFramework + + override fun klutterTask() = + CopyXCFrameworkTask(project.rootDir) + } \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Get.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/GetKradleGradleTask.kt similarity index 70% rename from lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Get.kt rename to lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/GetKradleGradleTask.kt index ea00ad93..49775a3f 100644 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Get.kt +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/GetKradleGradleTask.kt @@ -1,4 +1,4 @@ -/* Copyright (c) 2021 - 2023 Buijs Software +/* Copyright (c) 2021 - 2024 Buijs Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -19,18 +19,18 @@ * SOFTWARE. * */ -package dev.buijs.klutter.kradle.shared +package dev.buijs.klutter.gradle.tasks -import dev.buijs.klutter.kradle.command.downloadFlutterByCommand +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.tasks.GetKradleTask -fun List.getDependency() { - val mutable = toMutableList() - val first = mutable.removeFirstOrNull() - when { - first == "flutter" -> - mutable.downloadFlutterByCommand() - else -> { - println("I don't know what to get: $this") - } +internal open class GetKradleGradleTask: AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.GetKradle + + override fun klutterTask(): KlutterTask { + val project = super.project() + return GetKradleTask(project.root.resolve("kradle").absolutePath) } + } \ No newline at end of file diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/GetKradleTask.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/GetKradleTask.kt deleted file mode 100644 index 491b5cb7..00000000 --- a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/GetKradleTask.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.gradle.tasks - -import dev.buijs.klutter.gradle.KlutterGradlePlugin -import dev.buijs.klutter.kore.KlutterException -import dev.buijs.klutter.kore.KlutterTask -import dev.buijs.klutter.kore.common.verifyExists -import java.io.File -import java.io.InputStream - -internal val kradleWrapperJar: InputStream - get() = resourceFileOrThrow("kradle-wrapper.jar") - -internal val kradlew: InputStream - get() = resourceFileOrThrow("kradlew") - -internal val kradlewBat: InputStream - get() = resourceFileOrThrow("kradlew.bat") - -/** - * Download Kradle CLI Tool. - */ -internal open class GetKradleTask: AbstractTask() { - override fun klutterTask() = - project.rootProject.projectDir.toGetKradleTask() -} - -fun File.toGetKradleTask() = object: KlutterTask { - val dotKradle = resolve(".kradle") - val dotKradleJar = dotKradle.resolve("kradle-wrapper.jar") - - override fun run() { - dotKradle.mkdir() - if(dotKradleJar.exists()) - dotKradleJar.delete() - - kradleWrapperJar.copyToFolder( - sourceFileName = "kradle-wrapper.jar", - targetFolder = dotKradle - ) - - kradlew.copyToFolder( - sourceFileName = "kradlew", - targetFolder = this@toGetKradleTask - ) { - it.setExecutable(true) - } - - kradlewBat.copyToFolder( - sourceFileName = "kradlew.bat", - targetFolder = this@toGetKradleTask - ) { - it.setExecutable(true) - } - } - -} - -private fun resourceFileOrThrow(name: String): InputStream = - KlutterGradlePlugin::class.java.getResourceAsStream("/$name") - ?: throw KlutterException("Unable to find $name") - -private fun InputStream.copyToFolder( - sourceFileName: String, - targetFolder: File, - postProcessFile: (File) -> Unit = {} -) { - val target = targetFolder.resolve(sourceFileName) - - if(target.exists()) - target.delete() - - use { input -> - target.outputStream().use { output -> - input.copyTo(output) - } - } - - target.verifyExists().let(postProcessFile) -} \ No newline at end of file diff --git a/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/ProtoTasks.kt b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/ProtoTasks.kt new file mode 100644 index 00000000..385ba1b8 --- /dev/null +++ b/lib/gradle/src/main/kotlin/dev/buijs/klutter/gradle/tasks/ProtoTasks.kt @@ -0,0 +1,117 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.gradle.tasks + +import dev.buijs.klutter.kore.KlutterException +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.project.protocHome +import dev.buijs.klutter.kore.tasks.DownloadProtocTask +import dev.buijs.klutter.kore.tasks.GetDartProtocExeTask +import dev.buijs.klutter.kore.tasks.codegen.CompileProtoSchemaTask +import dev.buijs.klutter.kore.tasks.codegen.GenerateProtoSchemasTask +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction + +/** + * Execute task [DownloadProtocTask] from Gradle. + */ +internal open class GetProtocGradleTask: AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.GetProtoc + + override fun klutterTask(): KlutterTask = + DownloadProtocTask( + rootFolder = super.project().root.folder, + overwrite = false, + target = protocHome + ) + +} + +/** + * Execute task [GetDartProtocExeTask] from Gradle. + */ +internal open class GetProtocDartGradleTask: AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.GetProtocDart + + override fun klutterTask(): KlutterTask = GetDartProtocExeTask() +} + +/** + * Run the [GenerateProtoSchemasTask] from Gradle. + * + * This Task should be instantiated in the build.gradle.kts File, + * which ensures all dependencies from the platform project + * are loaded in the Classloader. + */ +abstract class GenerateProtoSchemasGradleTask: DefaultTask() { + + @get:Optional + @get:Input + abstract var classLoader: ClassLoader? + + companion object { + /** + * Name of this task to be registered. + * + * See [KlutterGradleTaskName.GenerateProtoSchemas]. + */ + val taskName: String = KlutterGradleTaskName.GenerateProtoSchemas.taskName + } + + @TaskAction + fun execute() { + if(classLoader == null) { + throw KlutterException("GenerateProtoSchemaGradleTask is missing property value 'classLoader'") + } else { + GenerateProtoSchemasTask(project.rootDir, classLoader!!).run() + } + } + +} + +/** + * Run [CompileProtoSchemaTask] from Gradle. + * + * This Task depends on [GetProtocDartGradleTask] and [GetProtocGradleTask] + * which installs the protoc toolchain for compiling schemas. + * + * This Task is finalized by [GenerateFlutterLibGradleTask], + * because compiling protoc schemas results in new dart classes in the ./lib folder. + * These new dart classes should be exported through the lib file. + */ +internal open class CompileProtoSchemasGradleTask: AbstractTask() { + + override val gradleTaskName = KlutterGradleTaskName.CompileProtoSchemas + + init { + super.dependsOn(KlutterGradleTaskName.GetProtocDart.taskName, KlutterGradleTaskName.GetProtoc.taskName) + super.finalizedBy(KlutterGradleTaskName.GenerateFlutterLib.taskName) + } + + override fun klutterTask(): KlutterTask = + CompileProtoSchemaTask(super.project().root.folder) + +} \ No newline at end of file diff --git a/lib/gradle/src/main/resources/kradlew b/lib/gradle/src/main/resources/kradlew deleted file mode 100755 index 35f8d5d0..00000000 --- a/lib/gradle/src/main/resources/kradlew +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# kradlew start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh kradlew -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and KRADLEW_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}.." && pwd -P ) || exit - -APP_NAME="kradlew" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and KRADLEW_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# 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=./.kradle/kradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || 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 - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - 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 KRADLEW_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 - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $KRADLEW_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - -classpath "$CLASSPATH" \ - dev.buijs.klutter.kradle.MainKt \ - "$@" - -# 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 $KRADLEW_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/lib/gradle/src/main/resources/kradlew.bat b/lib/gradle/src/main/resources/kradlew.bat deleted file mode 100755 index c95f5633..00000000 --- a/lib/gradle/src/main/resources/kradlew.bat +++ /dev/null @@ -1,88 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem kradlew 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=. -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 KRADLEW_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=.kradle\kradle-wrapper.jar - -@rem Execute kradlew -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %KRADLEW_OPTS% -classpath "%CLASSPATH%" dev.buijs.klutter.kradle.MainKt %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable KRADLEW_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%KRADLEW_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lib/gradle/src/main/resources/publish.properties b/lib/gradle/src/main/resources/publish.properties index 8a4cbc85..9e03950c 100644 --- a/lib/gradle/src/main/resources/publish.properties +++ b/lib/gradle/src/main/resources/publish.properties @@ -1,8 +1,8 @@ -annotations.version=2023.3.1.beta -bom.version=2023.3.1.beta -compiler.version=2023.3.1.beta -flutter.engine.version=2023.3.1.beta -kompose.version=2023.3.1.beta -kore.version=2023.3.1.beta -plugin.gradle.version=2023.3.1.beta -plugin.jetbrains.version=2023.3.1.beta \ No newline at end of file +annotations.version=2024.1.1.beta +bom.version=2024.1.1.beta +compiler.version=2024.1.1.beta +flutter.engine.version=2024.1.1.beta +kompose.version=2024.1.1.beta +kore.version=2024.1.1.beta +plugin.gradle.version=2024.1.1.beta +plugin.jetbrains.version=2024.1.1.bata \ No newline at end of file diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterGradlePluginSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterGradlePluginSpec.groovy index a4c718ac..2f076535 100644 --- a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterGradlePluginSpec.groovy +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterGradlePluginSpec.groovy @@ -1,9 +1,20 @@ +//file:noinspection ConfigurationAvoidance package dev.buijs.klutter.gradle -import com.google.devtools.ksp.gradle.KspGradleSubplugin +import com.google.devtools.ksp.gradle.KspExtension +import dev.buijs.klutter.gradle.tasks.AbstractTask +import dev.buijs.klutter.gradle.tasks.AbstractTaskKt +import dev.buijs.klutter.gradle.tasks.KlutterGradleTaskName +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.project.RootKt +import dev.buijs.klutter.kore.test.TestPlugin +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.plugins.ExtensionContainer import org.gradle.testfixtures.ProjectBuilder import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import spock.lang.Specification class KlutterGradlePluginSpec extends Specification { @@ -11,22 +22,14 @@ class KlutterGradlePluginSpec extends Specification { def "Verify the plugin is applied"() { given: - def project = ProjectBuilder.builder().build() - - when: - project.extensions.add("kotlin", KotlinMultiplatformExtension.class) - project.pluginManager.apply("dev.buijs.klutter") - - and: - def plugin = project.plugins.getPlugin(KlutterGradlePlugin.class) + def project = getGradleProjectWithAppliedKlutterPlugin() - then: - plugin != null + expect: + project.plugins.getPlugin(KlutterGradlePlugin.class) != null } - def "Verify klutterGenerateAdapter task is created"() { - + def "Verify klutter tasks are created"() { given: def project = ProjectBuilder.builder().build() @@ -44,6 +47,112 @@ class KlutterGradlePluginSpec extends Specification { and: arr.size() != 0 + } + + def "Verify that ksp plugin is applied and configured"() { + + given: + def project = getGradleProjectWithAppliedKlutterPlugin() + expect: + def ksp = project.extensions.getByType(KspExtension.class) + def args = ksp.getArguments() + args.containsKey(RootKt.kspArgumentKlutterProjectFolder) } + + def "Verify that ksp generated code is added to KMP sourceSets and KMP dependent tasks are added"() { + + given: + def project = ProjectBuilder.builder().build() + + and: + project.extensions.add("kmp", Stub(KotlinMultiplatformExtension) { + it.sourceSets >> Stub(NamedDomainObjectContainer) { + it.getByName("commonMain") >> Stub(KotlinSourceSet) { + it.kotlin >> Stub(SourceDirectorySet) { + it.srcDir(_) + } + } + } + }) + + and: + project.tasks.register("build", DummyGradleTask.class) + project.tasks.register("assemblePlatformReleaseXCFramework", DummyGradleTask.class) + project.tasks.register(KlutterGradleTaskName.GenerateProtoSchemas.taskName, DummyGradleTask.class) + + when: + project.pluginManager.apply("dev.buijs.klutter") + + and: "afterEvaluate is triggered" + project.getTasksByName(KlutterGradleTaskName.CopyAndroidAarFile.taskName, false) + + then: + with(project.tasks.getByName("build") as DummyGradleTask) { + it.finalizers.contains("assemblePlatformReleaseXCFramework") + it.finalizers.contains(KlutterGradleTaskName.GenerateProtoSchemas.taskName) + it.finalizers.contains(KlutterGradleTaskName.CopyAndroidAarFile.taskName) + } + + with(project.tasks.getByName("assemblePlatformReleaseXCFramework") as DummyGradleTask) { + it.finalizers.contains(KlutterGradleTaskName.CopyIosFramework.taskName) + } + + with(project.tasks.getByName(KlutterGradleTaskName.GenerateProtoSchemas.taskName) as DummyGradleTask) { + it.finalizers.contains(KlutterGradleTaskName.CompileProtoSchemas.taskName) + } + } + + def "Verify ISE is thrown if wrong class is found for klutter extension"() { + given: + def container = Stub(ExtensionContainer) { + it.getByName("klutter") >> "Not an extension" + } + + def project = Stub(Project) { + it.extensions >> container + } + + when: + AbstractTaskKt.klutterExtension(project) + + then: + IllegalStateException ise = thrown() + ise.message == "klutter extension is not of the correct type" + } + + static getGradleProjectWithAppliedKlutterPlugin() { + def klutterProject = new TestPlugin() + def project = ProjectBuilder.builder() + .withProjectDir(klutterProject.root) + .build() + project.pluginManager.apply("dev.buijs.klutter") + project.getTasks() + project.getTasksByName(KlutterGradleTaskName.CopyAndroidAarFile.taskName, false) + project + } + + static class DummyGradleTask extends AbstractTask { + + def finalizers = [] + + @Override + KlutterTask klutterTask$gradle() { + return new KlutterTask() { + @Override + void run() { } + } + } + + @Override + void setFinalizedBy(Iterable finalizedByTasks) { + finalizers.addAll(finalizedByTasks) + } + + @Override + KlutterGradleTaskName getGradleTaskName$gradle() { + return null + } + } + } \ No newline at end of file diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/DependencyUtilsSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/DependencyUtilsSpec.groovy index d47659f0..26a853d4 100644 --- a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/DependencyUtilsSpec.groovy +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/DependencyUtilsSpec.groovy @@ -21,7 +21,6 @@ */ package dev.buijs.klutter.gradle.dsl -import dev.buijs.klutter.gradle.dsl.DependencyUtilsKt import spock.lang.Specification import java.nio.file.Files diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterPluginDSLSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/KlutterPluginDSLSpec.groovy similarity index 78% rename from lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterPluginDSLSpec.groovy rename to lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/KlutterPluginDSLSpec.groovy index 1ff8fe83..5851a193 100644 --- a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/KlutterPluginDSLSpec.groovy +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/dsl/KlutterPluginDSLSpec.groovy @@ -1,6 +1,5 @@ -package dev.buijs.klutter.gradle +package dev.buijs.klutter.gradle.dsl -import dev.buijs.klutter.gradle.dsl.KlutterPluginBuilder import spock.lang.Specification class KlutterPluginDSLSpec extends Specification { diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Clean.kt b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/CodeGenTasksSpec.groovy similarity index 68% rename from lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Clean.kt rename to lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/CodeGenTasksSpec.groovy index 52c45136..67bc7a8a 100644 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Clean.kt +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/CodeGenTasksSpec.groovy @@ -1,4 +1,4 @@ -/* Copyright (c) 2021 - 2023 Buijs Software +/* Copyright (c) 2021 - 2024 Buijs Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -19,20 +19,18 @@ * SOFTWARE. * */ -package dev.buijs.klutter.kradle.shared +package dev.buijs.klutter.gradle.tasks -import dev.buijs.klutter.kore.tasks.CleanCacheTask +import dev.buijs.klutter.kore.tasks.codegen.GenerateFlutterLibTask +import spock.lang.Specification -fun MutableList.clean() { - val first = removeFirstOrNull() - when { - first == "cache" -> { - println("The .kradle/cache folder will be cleaned...") - CleanCacheTask().run() - println("Finished .kradle/cache cleaning.") - } - else -> { - println("I don't know what to clean: $this") - } +import static dev.buijs.klutter.gradle.tasks.TaskTestUtil.verifyTask + +class CodeGenTasksSpec extends Specification { + + def "Verify GenerateFlutterLibGradleTask returns CopyAarFileTask"() { + expect: + verifyTask(GenerateFlutterLibGradleTask, GenerateFlutterLibTask) } + } \ No newline at end of file diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/CopyTasksSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/CopyTasksSpec.groovy new file mode 100644 index 00000000..62c606b4 --- /dev/null +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/CopyTasksSpec.groovy @@ -0,0 +1,43 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.gradle.tasks + + +import dev.buijs.klutter.kore.tasks.CopyAarFileTask +import dev.buijs.klutter.kore.tasks.CopyXCFrameworkTask +import spock.lang.Specification + +import static dev.buijs.klutter.gradle.tasks.TaskTestUtil.verifyTask + +class CopyTasksSpec extends Specification { + + def "Verify CopyAndroidAarFileGradleTask returns CopyAarFileTask"() { + expect: + verifyTask(CopyAndroidAarFileGradleTask, CopyAarFileTask) + } + + def "Verify CopyIosFrameworkGradleTask returns CopyXCFrameworkTask"() { + expect: + verifyTask(CopyIosFrameworkGradleTask, CopyXCFrameworkTask) + } + +} \ No newline at end of file diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/GetKradleGradleTaskSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/GetKradleGradleTaskSpec.groovy new file mode 100644 index 00000000..9f357660 --- /dev/null +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/GetKradleGradleTaskSpec.groovy @@ -0,0 +1,43 @@ +/* Copyright (c) 2021 - 2022 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.gradle.tasks + +import spock.lang.Ignore +import spock.lang.Specification + +import static dev.buijs.klutter.gradle.tasks.TaskTestUtil.getTask + +class GetKradleGradleTaskSpec extends Specification { + + // does not work in ci yet ... + @Ignore + def "Verify getKradleTask works"() { + def task = getTask(GetKradleGradleTask) + + when: + task.execute() + + then: "No exception is thrown" + 1 == 1 + } + +} \ No newline at end of file diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/GetKradleTaskSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/GetKradleTaskSpec.groovy deleted file mode 100644 index d042926b..00000000 --- a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/GetKradleTaskSpec.groovy +++ /dev/null @@ -1,30 +0,0 @@ -package dev.buijs.klutter.gradle.tasks - -import spock.lang.Ignore -import spock.lang.Specification - -import java.nio.file.Files - -class GetKradleTaskSpec extends Specification { - - def "Verify kradlew scripts can be retrieved from resources"() { - expect: - GetKradleTaskKt.kradlew.available() - GetKradleTaskKt.kradlewBat.available() - } - - @Ignore // TODO get kradle from url - def "Verify kradlew files can be copied" () { - given: - def root = Files.createTempDirectory("").toFile() - - when: - GetKradleTaskKt.toGetKradleTask(root).run() - - then: - root.toPath().resolve("kradlew").toFile().exists() - root.toPath().resolve("kradlew.bat").toFile().exists() - root.toPath().resolve(".kradle").resolve("kradle-wrapper.jar").toFile().exists() - } - -} \ No newline at end of file diff --git a/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/ProtoTasksSpec.groovy b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/ProtoTasksSpec.groovy new file mode 100644 index 00000000..c4052dae --- /dev/null +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/ProtoTasksSpec.groovy @@ -0,0 +1,72 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.gradle.tasks + + +import dev.buijs.klutter.kore.KlutterException +import dev.buijs.klutter.kore.tasks.DownloadProtocTask +import dev.buijs.klutter.kore.tasks.GetDartProtocExeTask +import dev.buijs.klutter.kore.tasks.codegen.CompileProtoSchemaTask +import spock.lang.Specification + +import static dev.buijs.klutter.gradle.tasks.TaskTestUtil.getTask +import static dev.buijs.klutter.gradle.tasks.TaskTestUtil.verifyTask + +class ProtoTasksSpec extends Specification { + + def "Verify GetProtocGradleTask returns DownloadProtocTask"() { + expect: + verifyTask(GetProtocGradleTask, DownloadProtocTask) + } + + def "Verify GetProtocDartGradleTask returns GetDartProtocExeTask"() { + expect: + verifyTask(GetProtocDartGradleTask, GetDartProtocExeTask) + } + + def "Verify CompileProtoSchemasGradleTask returns GetDartProtocExeTask"() { + expect: + verifyTask(CompileProtoSchemasGradleTask, CompileProtoSchemaTask) + } + + def "Verify GenerateProtoSchemasGradleTask throws KlutterException if classLoader variable is not set"() { + given: + def task = getTask(GenerateProtoSchemasGradleTask) + + when: + task.execute() + + then: + KlutterException e = thrown() + e.message == "GenerateProtoSchemaGradleTask is missing property value 'classLoader'" + + when: + task.setClassLoader(this.class.classLoader) + + and: + task.execute() + + then: "No exception is thrown" + 1 == 1 + } + +} diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/MrArg.kt b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/TaskTestUtil.groovy similarity index 62% rename from lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/MrArg.kt rename to lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/TaskTestUtil.groovy index a3cb760a..1df1b17f 100644 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/MrArg.kt +++ b/lib/gradle/src/test/groovy/dev/buijs/klutter/gradle/tasks/TaskTestUtil.groovy @@ -1,4 +1,4 @@ -/* Copyright (c) 2021 - 2023 Buijs Software +/* Copyright (c) 2021 - 2024 Buijs Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -19,19 +19,21 @@ * SOFTWARE. * */ -package dev.buijs.klutter.kradle.command +package dev.buijs.klutter.gradle.tasks -import kotlinx.cli.ArgParser -import kotlinx.cli.ArgType -import kotlinx.cli.required +import dev.buijs.klutter.gradle.KlutterGradlePluginSpec -internal annotation class Open4Test +class TaskTestUtil { + static getTask(Class type) { + def project = KlutterGradlePluginSpec.gradleProjectWithAppliedKlutterPlugin + //noinspection ConfigurationAvoidance + project.task('dummy', type: type) + } -internal infix fun ArgParser.required(description: String) = - option(ArgType.String, description = description).required() - -internal infix fun ArgParser.optional(description: String) = - option(ArgType.String, description = description) - -internal infix fun ArgParser.optionalBoolean(description: String) = - option(ArgType.Boolean, description = description) \ No newline at end of file + static verifyTask(Class gradleTaskType, Class klutterTaskType) { + def taskInstance = getTask(gradleTaskType) + assert taskInstance.class.superclass == gradleTaskType + assert taskInstance.klutterTask$gradle().class == klutterTaskType + true + } +} diff --git a/lib/gradle/src/test/kotlin/dev/buijs/klutter/gradle/KlutterGradlePluginTest.kt b/lib/gradle/src/test/kotlin/dev/buijs/klutter/gradle/KlutterGradlePluginTest.kt deleted file mode 100644 index 79f92159..00000000 --- a/lib/gradle/src/test/kotlin/dev/buijs/klutter/gradle/KlutterGradlePluginTest.kt +++ /dev/null @@ -1,122 +0,0 @@ -package dev.buijs.klutter.gradle - -import com.google.devtools.ksp.gradle.KspExtension -import dev.buijs.klutter.gradle.dsl.KlutterExtension -import dev.buijs.klutter.gradle.tasks.klutterExtension -import io.kotlintest.shouldBe -import io.kotlintest.shouldNotBe -import io.kotlintest.specs.WordSpec -import org.gradle.api.artifacts.dsl.DependencyHandler -import org.gradle.api.plugins.ExtensionContainer -import org.gradle.api.plugins.PluginContainer -import org.gradle.api.tasks.TaskContainer -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever -import java.io.File -import java.nio.file.Files -import org.gradle.api.Project as GradleProject - -internal class KlutterGradlePluginTest: WordSpec({ - - "Verify adapter method" should { - - "Throws ISE if wrong class is found" { - val container: ExtensionContainer = mock() - val project: GradleProject = mock() - - whenever(container.getByName("klutter")).thenReturn("Not an extension") - whenever(project.extensions).thenReturn(container) - - try { - project.klutterExtension() - } catch(e: Exception) { - e::class.java.simpleName shouldBe "IllegalStateException" - e.message shouldBe "klutter extension is not of the correct type" - } - - } - - "Return extension if correct class is found" { - val extension: KlutterExtension = mock() - val container: ExtensionContainer = mock() - val project: GradleProject = mock() - - whenever(container.getByName("klutter")) - .thenReturn(extension) - - whenever(project.extensions) - .thenReturn(container) - - project.klutterExtension() shouldBe extension - - } - - } - - "Verify KlutterGradleExtension class" should { - val temp = Files.createTempDirectory("").toFile() - val subTemp = temp.resolve("foo") - subTemp.mkdir() - - val project: org.gradle.api.Project = mock { - whenever(it.buildDir).thenAnswer { temp } - whenever(it.projectDir).thenAnswer { subTemp } - } - - val extension = KlutterExtension(project) - - "All values default to null" { - extension.root shouldBe null - extension.plugin shouldBe null - } - - "After using the plugin DSL values should be set" { - - extension.root = File("") - - extension.plugin { - name = "With great power..." - } - - extension.root shouldNotBe null - extension.plugin shouldNotBe null - extension.plugin!!.name shouldBe "With great power..." - } - - } - - - "Verify KSP extension is fed by Klutter extension through Klutter Gradle Plugin" should { - - // fake Gradle project - val project: GradleProject = mock() - val klutterScanFolder = Files.createTempDirectory("x").toFile() - val klutterOutputFolder = Files.createTempDirectory("y").toFile() - whenever(project.buildDir).thenReturn(klutterScanFolder) - whenever(project.projectDir).thenReturn(klutterOutputFolder.resolve("foo")) - whenever(project.logger).thenReturn(mock()) - - // task container which contains the 'build' task - val taskContainer: TaskContainer = mock() - whenever(project.tasks).thenReturn(taskContainer) - whenever(taskContainer.getByName("build")).thenReturn(mock()) - - // plugin container which contains the ksp plugin - val pluginContainer: PluginContainer = mock() - whenever(project.plugins).thenReturn(pluginContainer) - - // dependency handler mock for containing the compiler-plugin dependency - val dependencyHandler: DependencyHandler = mock() - whenever(project.dependencies).thenReturn(dependencyHandler) - - // feeder! - //val klutterExtension = KlutterExtension(project) - - // nomnommer! - val kspExtension = KspExtension() - - val container: ExtensionContainer = mock() - whenever(container.getByType(KspExtension::class.java)).thenReturn(kspExtension) - whenever(project.extensions).thenReturn(container) - } -}) \ No newline at end of file diff --git a/lib/jetbrains/CHANGELOG.md b/lib/jetbrains/CHANGELOG.md index f8cd59aa..5e98f22f 100644 --- a/lib/jetbrains/CHANGELOG.md +++ b/lib/jetbrains/CHANGELOG.md @@ -1,4 +1,12 @@ +## [2024.1.1] + +### Changed +- Uses klutter 2024.1.1.beta. +- Uses klutter (dart) [3.0.0](https://pub.dev/packages/klutter). +- Uses klutter_ui (dart) [1.1.0](https://pub.dev/packages/klutter_ui). +- Add protobuf support. + ## [2023.3.1] ### Changed diff --git a/lib/jetbrains/build.gradle.kts b/lib/jetbrains/build.gradle.kts index 6cd93b10..18349ab1 100644 --- a/lib/jetbrains/build.gradle.kts +++ b/lib/jetbrains/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.changelog.date plugins { - id("org.jetbrains.intellij") version "1.13.3" + id("org.jetbrains.intellij") version "1.17.3" id("org.jetbrains.changelog") version "2.0.0" id("java") id("maven-publish") @@ -23,7 +23,7 @@ group = "dev.buijs.klutter" version = dev.buijs.klutter.ProjectVersions.jetbrains intellij { - version.set("2022.3.2") + version.set("2023.2") type.set("IC") // Intellij Community Edition plugins.set(listOf("java", "com.intellij.gradle","android")) } @@ -51,22 +51,13 @@ changelog { tasks { - withType { - sourceCompatibility = "17" - targetCompatibility = "17" - } - - withType { - kotlinOptions.jvmTarget = "17" - } - withType { useJUnitPlatform() } patchPluginXml { - sinceBuild.set("223") - untilBuild.set("232.*") + sinceBuild.set("232") + //untilBuild.set("232.*") } signPlugin { @@ -122,8 +113,8 @@ dependencies { @Suppress("GradleDependency") // 30-07-2022 newest 3.4.2 throws exceptions testImplementation("io.kotlintest:kotlintest-runner-junit5:3.3.0") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1") // Plugin UI Test testImplementation("com.intellij.remoterobot:remote-robot:$robotVersion") diff --git a/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/KlutterMetadata.kt b/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/KlutterMetadata.kt index 234542b2..4372d051 100644 --- a/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/KlutterMetadata.kt +++ b/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/KlutterMetadata.kt @@ -52,6 +52,6 @@ object KlutterBundle { */ @ExcludeFromJacocoGeneratedReport object KlutterIcons { - val logo16x16: Icon = IconLoader.getIcon("/pluginIcon16x16.png", KlutterIcons::class.java) - val logo20x20: Icon = IconLoader.getIcon("/pluginIcon20x20.png", KlutterIcons::class.java) + val logo16x16: Icon = IconLoader.getIcon("/pluginIcon16x16.svg", KlutterIcons::class.java) + val logo20x20: Icon = IconLoader.getIcon("/pluginIcon20x20.svg", KlutterIcons::class.java) } \ No newline at end of file diff --git a/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectTaskFactory.kt b/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectTaskFactory.kt index db29d618..90ae37c6 100644 --- a/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectTaskFactory.kt +++ b/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectTaskFactory.kt @@ -25,8 +25,8 @@ import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project -import dev.buijs.klutter.gradle.tasks.toGetKradleTask import dev.buijs.klutter.kore.project.* +import dev.buijs.klutter.kore.tasks.execute import dev.buijs.klutter.kore.tasks.executor import dev.buijs.klutter.kore.tasks.project.* import mu.KotlinLogging @@ -61,15 +61,14 @@ private fun createKlutterPluginTask( ): Task.Modal { val rootFolder = options.rootFolder.validRootFolderOrThrow() val task = ProjectBuilderTask(options = options) - val getKradleTask = rootFolder.toGetKradleTask() return createKlutterTask( pathToRoot = rootFolder, project = project, task = { executor = JetbrainsExecutor() task.run() - getKradleTask.run() rootFolder.moveUpFolder(options.pluginName.validPluginNameOrThrow()) + "./gradlew klutterGetKradle -p platform" execute rootFolder }) } diff --git a/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectWizardStep.kt b/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectWizardStep.kt index 3511e268..735253e1 100644 --- a/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectWizardStep.kt +++ b/lib/jetbrains/src/main/kotlin/dev/buijs/klutter/jetbrains/NewProjectWizardStep.kt @@ -45,6 +45,7 @@ import java.awt.Graphics import javax.swing.JPanel private val knownKlutterBOMVersions = setOf( + "2024.1.1.beta", "2023.3.1.beta", "2023.2.2.beta", "2023.2.1.beta", @@ -128,7 +129,7 @@ private fun newProjectPanel(data: NewProjectConfig, disposable: Disposable) = pa indent { row { // KLUTTER BANNER - cell(KlutterBanner).verticalAlign(VerticalAlign.CENTER) + cell(KlutterBanner).align(AlignY.CENTER) } val project = "Project" @@ -138,7 +139,7 @@ private fun newProjectPanel(data: NewProjectConfig, disposable: Disposable) = pa textField() .bindText(data.appNameObservable) .widthGroup(project) - .horizontalAlign(HorizontalAlign.LEFT) + .align(AlignX.LEFT) .bindValidator(disposable) { input -> if(toPluginName(input) is EitherNok) "The Plugin Name is not valid." else null } @@ -149,7 +150,7 @@ private fun newProjectPanel(data: NewProjectConfig, disposable: Disposable) = pa textField() .bindText(data.groupNameObservable) .widthGroup(project) - .horizontalAlign(HorizontalAlign.LEFT) + .align(AlignX.LEFT) .bindValidator(disposable) { input -> if(toGroupName(input) is EitherNok) "The Group Name is not valid." else null } @@ -163,7 +164,7 @@ private fun newProjectPanel(data: NewProjectConfig, disposable: Disposable) = pa .also { it.component.isEditable = true } .widthGroup(dependencies) .bindItem(data.gradleVersionObservable) - .horizontalAlign(HorizontalAlign.LEFT) + .align(AlignX.LEFT) .bindValidator(disposable, isBlocking = false) { input -> if(knownKlutterBOMVersions.none { it == input }) "The BOM Version is unknown." else null } @@ -172,13 +173,13 @@ private fun newProjectPanel(data: NewProjectConfig, disposable: Disposable) = pa comboBox(items = flutterVersionsDescending(currentOperatingSystem).map { "${it.prettyPrintedString}" }) .widthGroup(dependencies) .bindItem(data.flutterVersionObservable) - .horizontalAlign(HorizontalAlign.LEFT) + .align(AlignX.LEFT) } row { checkBox("Get flutter dependencies from git.") .widthGroup(dependencies) .bindSelected(data.useGitForPubDependenciesObservable) - .horizontalAlign(HorizontalAlign.LEFT) + .align(AlignX.LEFT) } row {}.comment("If enabled then all flutter dependencies are pulled from git and not pub.") @@ -270,8 +271,4 @@ private fun observable( // nothing } - override fun afterChange(listener: (T) -> Unit, parentDisposable: Disposable) { - // nothing - } - } \ No newline at end of file diff --git a/lib/kompose/build.gradle.kts b/lib/kompose/build.gradle.kts index 5fb9066b..e6ad7fad 100644 --- a/lib/kompose/build.gradle.kts +++ b/lib/kompose/build.gradle.kts @@ -1,6 +1,5 @@ -@file:Suppress("UNUSED_VARIABLE") plugins { - kotlin("plugin.serialization") version "1.7.0" + kotlin("plugin.serialization") version "1.9.10" kotlin("multiplatform") kotlin("native.cocoapods") id("com.android.library") @@ -11,16 +10,21 @@ plugins { group = "dev.buijs.klutter" version = dev.buijs.klutter.ProjectVersions.kompose +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + kotlin { - android { + androidTarget { publishLibraryVariants("release", "debug") } jvm() iosX64() iosArm64() - iosArm32() iosSimulatorArm64() cocoapods { summary = "Klutter Kompose module" @@ -35,7 +39,7 @@ kotlin { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-common") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation(project(":lib:annotations")) } @@ -46,14 +50,12 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") } } - val iosArm32Main by getting val iosX64Main by getting val iosArm64Main by getting val iosSimulatorArm64Main by getting val iosMain by creating { dependsOn(commonMain) iosX64Main.dependsOn(this) - iosArm32Main.dependsOn(this) iosArm64Main.dependsOn(this) iosSimulatorArm64Main.dependsOn(this) } @@ -62,7 +64,7 @@ kotlin { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("io.github.microutils:kotlin-logging-jvm:3.0.5") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") implementation(project(":lib:kore")) implementation(project(":lib:annotations")) } @@ -77,11 +79,11 @@ kotlin { } android { + namespace = "dev.buijs.klutter.kompose" compileSdk = 31 sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 21 - targetSdk = 31 } } @@ -108,7 +110,7 @@ publishing { } tasks.withType().configureEach { - outputDirectory.set(buildDir.resolve("dokka")) + outputDirectory.set(layout.buildDirectory.dir("dokka").get().asFile) dokkaSourceSets { register("kompose") { diff --git a/lib/kompose/kompose.podspec b/lib/kompose/kompose.podspec index 87c78279..a1edfea2 100644 --- a/lib/kompose/kompose.podspec +++ b/lib/kompose/kompose.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'kompose' - spec.version = '2023.3.1.beta' + spec.version = '2024.1.1.beta' spec.homepage = 'https://buijs.dev' spec.source = { :http=> ''} spec.authors = '' @@ -22,8 +22,8 @@ Pod::Spec.new do |spec| :execution_position => :before_compile, :shell_path => '/bin/sh', :script => <<-SCRIPT - if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then - echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\"" + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" exit 0 fi set -ev diff --git a/lib/kompose/src/androidMain/AndroidManifest.xml b/lib/kompose/src/androidMain/AndroidManifest.xml deleted file mode 100644 index c27a7462..00000000 --- a/lib/kompose/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/lib/kore/build.gradle.kts b/lib/kore/build.gradle.kts index 9f4f5c14..bcf88b96 100644 --- a/lib/kore/build.gradle.kts +++ b/lib/kore/build.gradle.kts @@ -1,17 +1,15 @@ plugins { kotlin("jvm") - kotlin("plugin.serialization") version "1.6.10" + kotlin("plugin.serialization") version "1.9.10" id("klutter") id("groovy") id("maven-publish") - id("java-library") } -java { - withJavadocJar() - withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } } sourceSets { @@ -36,8 +34,9 @@ dependencies { // KSP for annotation scanning implementation(kotlin("stdlib")) - implementation("com.google.devtools.ksp:symbol-processing-api:1.8.20-1.0.11") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.6.2") // Jackson for XML implementation("com.fasterxml.jackson.core:jackson-databind:2.14.2") diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/ast/Type.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/ast/Type.kt index 31ef943d..bebcc72e 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/ast/Type.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/ast/Type.kt @@ -44,6 +44,12 @@ sealed class AbstractType { open val className: String = javaClass.simpleName + + open val packageName: String = + javaClass.packageName + + val fqdn: String + get() = "$packageName.$className" } /** @@ -67,7 +73,7 @@ sealed class AbstractType { */ open class CustomType( override val className: String, - open val packageName: String? = null, + override val packageName: String, val members: List = emptyList() ): AbstractType() { @@ -91,7 +97,7 @@ open class CustomType( override fun hashCode(): Int { var result = className.hashCode() - result = 31 * result + (packageName?.hashCode() ?: 0) + result = 31 * result + packageName.hashCode() result = 31 * result + members.hashCode() return result } @@ -119,7 +125,7 @@ open class CustomType( */ open class EnumType( override val className: String, - open val packageName: String? = null, + override val packageName: String , val values: List = emptyList(), val valuesJSON: List = emptyList(), ): AbstractType() { @@ -143,7 +149,7 @@ data class UndeterminedType( */ class NullableCustomType( className: String, - packageName: String? = null, + packageName: String, fields: List = emptyList() ): CustomType( className = className, @@ -189,13 +195,13 @@ data class TypeMember( private fun CustomType.print() = this.javaClass.simpleName + "(className = $className, " + - "packageName = ${packageName ?: "NOT_FOUND"}, " + + "packageName = $packageName, " + "fields = ${members.map { "${it.name}: ${it.type.typeSimplename()}" }}, " + "nullable = ${this.javaClass.interfaces.map { i -> i.simpleName }.contains(object: Nullable {}.javaClass.simpleName)})" private fun EnumType.print() = this.javaClass.simpleName + "(className = $className, " + - "packageName = ${packageName ?: "NOT_FOUND"}, " + + "packageName = $packageName, " + "values = $values, " + "valuesJSON = $valuesJSON, " + "nullable = ${this.javaClass.interfaces.map { i -> i.simpleName }.contains(object: Nullable {}.javaClass.simpleName)})" diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Config.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Config.kt index f7347927..e058527c 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Config.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Config.kt @@ -34,6 +34,7 @@ private val log = KotlinLogging.logger { } @JsonPropertyOrder( "bom-version", "flutter-version", + "feature-protobuf-enabled", "dependencies") data class Config( @JsonProperty("dependencies") @@ -44,6 +45,9 @@ data class Config( @JsonProperty("flutter-version") val flutterVersion: String? = null, + + @JsonProperty("feature-protobuf-enabled") + val featureProtobufEnabled: Boolean? = null, ) @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Environment.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Environment.kt index a03a76de..83cfedc8 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Environment.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Environment.kt @@ -28,6 +28,9 @@ val Version.prettyPrint: String val isWindows: Boolean get() = currentOperatingSystem == OperatingSystem.WINDOWS +val isMacos: Boolean + get() = currentOperatingSystem == OperatingSystem.MACOS + /** * Get current [OperatingSystem] from System Properties. * @@ -45,7 +48,17 @@ val currentOperatingSystem: OperatingSystem } val currentArchitecture: Architecture - get() = """uname -m""".execute(Path.of("").toFile().absoluteFile).let { str -> - if(str.uppercase().contains("ARM")) Architecture.ARM64 else Architecture.X64 + get() { + val sysctl = "sysctl -n sysctl.proc_translated" execute currentWorkingDirectory + val usesRosetta = sysctl == "1" + val architecture = """uname -m""" execute currentWorkingDirectory + val archContainsARM = architecture.uppercase().contains("ARM") + return when { + usesRosetta -> Architecture.ARM64 + archContainsARM -> Architecture.ARM64 + else -> Architecture.X64 + } } +val currentWorkingDirectory: File + get() = Path.of("").toFile().absoluteFile \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Project.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Project.kt index 43ed0ac7..56a28385 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Project.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Project.kt @@ -29,13 +29,12 @@ import java.io.File /** * A representation of the structure of a project made with the Klutter Framework. * Each property of this object represents a folder containing one or more folders - * and/or files wich are in some way used or needed by Klutter. + * and/or files which are in some way used or needed by Klutter. * * @property root is the top level of the project. * @property ios is the folder containing the iOS frontend code, basically the iOS folder from a standard Flutter project. * @property android is the folder containing the Android frontend code, basically the iOS folder from a standard Flutter project. * @property platform is the folder containing the native backend code, basically a Kotlin Multiplatform library module. - * @author Gillian Buijs */ data class Project( val root: Root, @@ -46,12 +45,23 @@ data class Project( /** * Path to Klutter projects cache folder which is /.kradle. + * + * @Throws [KlutterException] if it does NOT exist. */ val kradleHome: File get() = File(System.getProperty("user.home")) .verifyExists() .resolve(".kradle") +/** + * Path to Klutter projects protoc folder which is [kradleHome]/.cache/protobuf/protoc. + */ +val protocHome: File + get() = kradleHome + .resolve(".cache") + .resolve("protobuf") + .resolve("protoc") + /** * The root/kradle.env File. * diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Properties.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Properties.kt index a4945936..09bbd448 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Properties.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/Properties.kt @@ -92,6 +92,9 @@ fun findSkipCodeGenInKradleEnvOrNull(content: String?) = } } +fun findProtocDownloadURLInKradleEnvOrNull(content: String?) = + content.findPropertyOrNull("protoc.url") + private fun String?.findPropertyInYamlOrNull(key: String) = this?.let { str -> """$key:\s*('|")\s*([^'"]+?)\s*('|")""".toRegex().find(str)?.let { match -> @@ -99,6 +102,13 @@ private fun String?.findPropertyInYamlOrNull(key: String) = } } +private fun String?.findBooleanPropertyInYamlOrNull(key: String) = + this?.let { str -> + """$key:\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\s*""".toRegex().find(str)?.let { match -> + match.groupValues[1] + } + } + private fun String?.findPropertyOrNull(key: String) = this?.let { str -> """$key=([^\n\r]+)""".toRegex().find(str)?.let { match -> diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/PubspecBuilder.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/PubspecBuilder.kt index 41765755..16a62a32 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/PubspecBuilder.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/project/PubspecBuilder.kt @@ -38,17 +38,17 @@ import java.io.File /** * The version of Klutter Gradle Executable Tool. */ -const val klutterBomVersion = "2023.3.1.beta" +const val klutterBomVersion = "2024.1.1.beta" /** * The version of the klutter Pub Plugin. */ -const val klutterPubVersion = "2.0.0" +const val klutterPubVersion = "2.1.0" /** * The version of the klutter-ui Pub Plugin. */ -const val klutterUIPubVersion = "1.0.1" +const val klutterUIPubVersion = "1.1.0" /** * The version of the squint_json Pub Plugin. @@ -66,6 +66,8 @@ val mapper = YAMLFactory.builder() .also { it.registerKotlinModule() it.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + it.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) + it.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) it.setVisibility(it.serializationConfig.defaultVisibilityChecker .withFieldVisibility(JsonAutoDetect.Visibility.ANY) .withGetterVisibility(JsonAutoDetect.Visibility.ANY) @@ -110,7 +112,7 @@ fun Pubspec.iosClassName(orElse: String): String { ios == null -> { orElse } - ios!!.pluginClass == null -> { + ios!!.pluginClass.isNullOrBlank() -> { orElse } else -> { @@ -124,7 +126,7 @@ fun Pubspec.androidClassName(orElse: String): String { android == null -> { orElse } - android!!.pluginClass == null -> { + android!!.pluginClass.isNullOrBlank() -> { orElse } else -> { diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/CleanCacheTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/DownloadProtocTask.kt similarity index 75% rename from lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/CleanCacheTask.kt rename to lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/DownloadProtocTask.kt index 2e81934e..8dabd75f 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/CleanCacheTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/DownloadProtocTask.kt @@ -22,15 +22,19 @@ package dev.buijs.klutter.kore.tasks import dev.buijs.klutter.kore.KlutterTask -import dev.buijs.klutter.kore.project.kradleHome +import dev.buijs.klutter.kore.tasks.project.DownloadProtoc +import java.io.File /** - * Task to clean the Klutter Cache Folder. + * Download a protoc distribution which is required to use + * protocol buffers in a klutter project. */ -class CleanCacheTask: KlutterTask { +class DownloadProtocTask( + private val rootFolder: File, + private val overwrite: Boolean = false, + private val target: File +) : KlutterTask { override fun run() { - kradleHome.resolve(".cache") - .also { it.deleteRecursively() } - .mkdir() + DownloadProtoc(rootFolder, overwrite, target).doAction() } } \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/NewProject.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/GetDartProtocExeTask.kt similarity index 50% rename from lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/NewProject.kt rename to lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/GetDartProtocExeTask.kt index 11098c3a..908cff70 100644 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/NewProject.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/GetDartProtocExeTask.kt @@ -19,31 +19,40 @@ * SOFTWARE. * */ -package dev.buijs.klutter.kradle.shared +package dev.buijs.klutter.kore.tasks -import dev.buijs.klutter.kore.project.Config -import dev.buijs.klutter.kore.project.FlutterDistributionFolderName -import dev.buijs.klutter.kore.tasks.project.ProjectBuilderOptions -import dev.buijs.klutter.kore.tasks.project.ProjectBuilderTask -import dev.buijs.klutter.kore.tasks.project.* +import dev.buijs.klutter.kore.KlutterException +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.common.maybeCreateFolder +import dev.buijs.klutter.kore.common.verifyExists +import dev.buijs.klutter.kore.project.kradleHome -internal fun ProjectBuilderOptions.createNewProject() { - println("Creating a new Klutter project...") - ProjectBuilderTask(this).run() - val name = pluginName.validPluginNameOrThrow() - val root = rootFolder.validRootFolderOrThrow().resolve(name) - listOf("klutterGetKradle").execGradleCommand(root) - println("Finished Klutter project creation.") -} +/** + * Task to extract the dart protoc executable. + */ +class GetDartProtocExeTask: KlutterTask { + + private val pathToExecutableFile = this::class.java.classLoader + .getResourceAsStream("protoc_plugin.exe") + ?: throw KlutterException("File protoc_plugin.exe is missing from Resources!") + + override fun run() { -internal interface NewProjectInput { - val rootFolder: RootFolder + val parentFolder = kradleHome + .resolve(".cache") + .resolve("protobuf") + .maybeCreateFolder() - val groupName: GroupName + val target = parentFolder + .resolve("protoc_plugin.exe") - val pluginName: PluginName + if(!target.exists()) { + pathToExecutableFile.copyTo(target.outputStream()) + target.setExecutable(true) + if(!target.canExecute()) + throw KlutterException("Failed to make File protoc_plugin executable") + } - val flutterDistributionFolderName: FlutterDistributionFolderName + } - val configOrNull: Config? } \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/GetKradleTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/GetKradleTask.kt new file mode 100644 index 00000000..98789e86 --- /dev/null +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/GetKradleTask.kt @@ -0,0 +1,104 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.kore.tasks + +import dev.buijs.klutter.kore.KlutterException +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.common.verifyExists +import dev.buijs.klutter.kore.project.* +import dev.buijs.klutter.kore.tasks.project.DownloadFlutterTask +import java.io.File +import java.nio.file.Files + +class GetKradleTask( + /** + * Absolute path to the kradle executable file. + * + * Example: /foo/bar/my_project/kradle + */ + private val pathToKradleOutput: String, +): KlutterTask { + override fun run() { + val repository = cloneKlutterDartRepositoryToTempOrThrow() + val dart = findDartExecutableOrThrow() + val kradle = repository.resolve("bin/kradle.dart").absolutePath + println("$dart pub get" execute repository) + println("$dart compile exe $kradle -o $pathToKradleOutput" execute repository) + } +} + +private fun cloneKlutterDartRepositoryToTempOrThrow(): File { + val temp = createTempDirectory() + cloneKlutterDartRepoTo(temp) + return temp.resolve("klutter-dart").verifyExists() +} + +private fun createTempDirectory(): File = + Files.createTempDirectory("klutter").toFile().also { it.deleteOnExit() } + +private fun cloneKlutterDartRepoTo(directory: File) { + println("git clone https://github.com/buijs-dev/klutter-dart.git" execute directory) +} + +/** + * Find a dart executable to generate the kradle executable. + * + * @throws [KlutterException] if a dart executable is not found. + */ +private fun findDartExecutableOrThrow(): String { + val cachedDartExecutableName = findDartExecutableInCacheOrNull() + if (cachedDartExecutableName != null) { + return dartExecutable(cachedDartExecutableName).absolutePath + } + + val globalDart = findGlobalDartExecutableOrNull() + if (globalDart != null) { + return globalDart + } + + return dartExecutable(downloadFlutterDistributionOrThrow()).absolutePath + +} + +private fun findDartExecutableInCacheOrNull(): String? { + val versions = compatibleFlutterVersions.keys.map { it.folderNameString.toString() } + val cache = kradleHome.resolve("cache").verifyExists() + val cachedFlutterDistributions = cache.listFiles()?.map { it.name } ?:emptyList() + return versions.firstOrNull { version -> + cachedFlutterDistributions.any { it.contains(version) } + } +} + +private fun findGlobalDartExecutableOrNull(): String? { + val dartOutput = "dart" execute currentWorkingDirectory + val hasGlobalDart = dartOutput.contains("A command-line utility for Dart development.") + return if(hasGlobalDart) "dart" else null +} + +private fun downloadFlutterDistributionOrThrow(): String { + val architecture = currentArchitecture + val version = flutterVersionsDescending(currentOperatingSystem) + .firstOrNull { it.arch == architecture } + ?: throw KlutterException("Could not find compatible flutter distribution") + DownloadFlutterTask(version).run() + return version.folderNameString.toString() +} \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/Zippie.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/Zippie.kt index 8d2751e1..af700ade 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/Zippie.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/Zippie.kt @@ -33,11 +33,6 @@ class Zippie( */ private val zipFile: File, - /** - * Files which should be executable after unzipping. - */ - private val executableFiles: Set, - /** * Size of the buffer to read/write data */ @@ -74,12 +69,6 @@ class Zippie( } } - File(destFilePath).setExecutableIfApplicable() - } - - private fun File.setExecutableIfApplicable() { - if(executableFiles.contains(nameWithoutExtension)) { - setExecutable(true) - } + File(destFilePath).setExecutable(true) } -} +} \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/CompileProtoSchemaTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/CompileProtoSchemaTask.kt new file mode 100644 index 00000000..fda3f27d --- /dev/null +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/CompileProtoSchemaTask.kt @@ -0,0 +1,118 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.kore.tasks.codegen + +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.common.verifyExists +import dev.buijs.klutter.kore.project.kradleHome +import dev.buijs.klutter.kore.tasks.executor +import java.io.File + +fun GenerateCodeOptions.toCompileProtoSchemaTask() = + CompileProtoSchemaTask(project.root.folder) + +/** + * Generate protocol buffer schema. + */ +class CompileProtoSchemaTask( + private val rootFolder: File, +): KlutterTask, GenerateCodeAction { + + override fun run() { + + val pathToBuild = rootFolder.resolve("platform/build") + + val sourceFolder = pathToBuild.resolve("klutter/protoschema") + + if(!sourceFolder.exists()) return + + val protoc = kradleHome + .resolve(".cache") + .resolve("protobuf") + .resolve("protoc") + .resolve("bin") + .resolve("protoc") + .verifyExists() + .absolutePath + + val destination = rootFolder + .resolve("lib/src") + .verifyExists() + .absolutePath + + val dartExecutable = kradleHome + .resolve(".cache") + .resolve("protobuf") + .resolve("protoc_plugin.exe") + .verifyExists() + .absolutePath + + val schemas = sourceFolder + .listFiles() + ?.map { it.absolutePath } + ?.toSet() + ?: emptySet() + + val destinationFile = File(destination) + + for(pathToSchemaFile in schemas) { + executor.execute( + runFrom = rootFolder, + command = "$protoc -I=${sourceFolder.absolutePath} --dart_out=$destination $pathToSchemaFile --plugin protoc-gen-dart=$dartExecutable") + + File(pathToSchemaFile).renameProtoGeneratedFiles(destinationFile) + File(pathToSchemaFile).deleteObsoleteGeneratedFiles(destinationFile) + } + + } + +} + +internal fun File.renameProtoGeneratedFiles(destination: File) = buildList { + val generatedFileName = nameWithoutExtension.substringAfterLast(".") + val generatedFile = destination.resolve("$nameWithoutExtension.pb.dart") + val renamedFile = destination.resolve("${generatedFileName.substringAfterLast(".")}.pb.dart") + generatedFile.copyTo(renamedFile) + generatedFile.delete() + + if(renamedFile.readText().contains("class ")) { + add(renamedFile.verifyExists()) + } else { + renamedFile.delete() + } + + val generatedEnumFile = destination.resolve("$nameWithoutExtension.pbenum.dart") + val renamedEnumFile = destination.resolve("${generatedFileName.substringAfterLast(".")}.pbenum.dart") + generatedEnumFile.copyTo(renamedEnumFile) + generatedEnumFile.delete() + + if(renamedEnumFile.readText().contains("class ")) { + add(renamedEnumFile.verifyExists()) + } else { + renamedEnumFile.delete() + } +} + +internal fun File.deleteObsoleteGeneratedFiles(destination: File) { + destination.resolve("$nameWithoutExtension.pbjson.dart").delete() + destination.resolve("$nameWithoutExtension.pbserver.dart").delete() +} \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateAndroidLibTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateAndroidLibTask.kt index cc8f528c..4c9220d6 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateAndroidLibTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateAndroidLibTask.kt @@ -28,7 +28,7 @@ import dev.buijs.klutter.kore.project.* import dev.buijs.klutter.kore.templates.AndroidAdapter fun GenerateCodeOptions.toGenerateAndroidLibTask() = - GenerateAndroidLibTask(android = project.android, bindings = bindings) + GenerateAndroidLibTask(android = project.android, bindings = bindings, isProtobufEnabled = responseClassNames.isNotEmpty()) /** * Generate the Android code in root/android. @@ -36,6 +36,7 @@ fun GenerateCodeOptions.toGenerateAndroidLibTask() = class GenerateAndroidLibTask( private val android: Android, private val bindings: Map>, + private val isProtobufEnabled: Boolean, ) : KlutterTask, GenerateCodeAction { override fun run() { android.pathToPlugin.maybeCreate().write( @@ -44,6 +45,7 @@ class GenerateAndroidLibTask( pluginPackageName = android.pluginPackageName, methodChannels = bindings.values.flatten().filterIsInstance().map { it.name }.toSet(), eventChannels = bindings.values.flatten().filterIsInstance().map { it.name }.toSet(), - controllers = bindings.keys)) + controllers = bindings.keys, + isProtobufEnabled = isProtobufEnabled)) } } \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeActions.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeActions.kt index de92189d..d69da7f0 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeActions.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeActions.kt @@ -51,6 +51,12 @@ inline fun findGenerateCodeAction( options.toGenerateFlutterMessagesTask() PubGetTask::class.java -> options.toPubGetTask() + GenerateProtoSchemasTask::class.java -> + options.toGenerateProtoSchemaTask() + CompileProtoSchemaTask::class.java -> + options.toCompileProtoSchemaTask() + GenerateProtoExtensionsTask::class.java -> + options.toGenerateProtoExtensionsTask() else -> throw KlutterException("Unknown GenerateCodeAction: ${T::class.java}") } @@ -58,6 +64,6 @@ fun runGenerateCodeActions(vararg action: GenerateCodeAction) { action.toList().forEach { it.run() } } -sealed interface GenerateCodeAction { +interface GenerateCodeAction { fun run() } \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeOptions.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeOptions.kt index c97dd13c..04786c86 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeOptions.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeOptions.kt @@ -35,6 +35,11 @@ data class GenerateCodeOptions( val excludeArmArcFromPodspec: Boolean, val controllers: List, val messages: List, + + /** + * List of FQDN Responses for which protobuf schemas should be generated. + */ + val responseClassNames: List, val log: (String) -> Unit = { }, ) { diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeTask.kt index b9b99961..e6c66b90 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateCodeTask.kt @@ -44,7 +44,8 @@ class GenerateCodeTask( findGenerateCodeAction(options), findGenerateCodeAction(options), findGenerateCodeAction(options), - findGenerateCodeAction(options)) + findGenerateCodeAction(options), + findGenerateCodeAction(options)) } } \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterControllersTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterControllersTask.kt index a2512245..e721b4bd 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterControllersTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterControllersTask.kt @@ -28,7 +28,10 @@ import dev.buijs.klutter.kore.templates.flutter.* import java.io.File fun GenerateCodeOptions.toGenerateControllersTask() = - GenerateFlutterControllersTask(srcFolder = flutterSrcFolder, bindings = bindings) + GenerateFlutterControllersTask( + srcFolder = flutterSrcFolder, + bindings = bindings, + isProtobufEnabled = responseClassNames.isNotEmpty()) /** * Generate the Flutter (dart) code in root/lib folder of the plugin project. @@ -36,6 +39,7 @@ fun GenerateCodeOptions.toGenerateControllersTask() = class GenerateFlutterControllersTask( private val srcFolder: File, private val bindings: Map>, + private val isProtobufEnabled: Boolean, ) : KlutterTask, GenerateCodeAction { override fun run() { @@ -44,12 +48,14 @@ class GenerateFlutterControllersTask( is SimpleController -> srcFolder.writeSimpleController( controller = controller, - channel = channels.firstSyncChannelOrNull()!!) + channel = channels.firstSyncChannelOrNull()!!, + isProtobufEnabled = isProtobufEnabled) is BroadcastController -> srcFolder.writeBroadcastController( controller = controller, asyncChannel = channels.firstAsyncChannelOrNull()!!, - syncChannel = channels.firstSyncChannelOrNull()) + syncChannel = channels.firstSyncChannelOrNull(), + isProtobufEnabled = isProtobufEnabled) } } } @@ -65,7 +71,8 @@ private fun List.firstAsyncChannelOrNull() = private fun File.writeBroadcastController( controller: BroadcastController, asyncChannel: FlutterAsyncChannel, - syncChannel: FlutterSyncChannel? + syncChannel: FlutterSyncChannel?, + isProtobufEnabled: Boolean, ) { val parentFolder = this @@ -81,27 +88,30 @@ private fun File.writeBroadcastController( dataType = controller.response)) controller.functions - .forEach { it.writePublisherWidget(parentFolder, syncChannel!!) } + .forEach { it.writePublisherWidget(parentFolder, syncChannel!!, isProtobufEnabled) } } private fun File.writeSimpleController( controller: SimpleController, - channel: FlutterSyncChannel + channel: FlutterSyncChannel, + isProtobufEnabled: Boolean, ) { val parentFolder = resolve(controller.className.toSnakeCase()) .maybeCreateFolder() controller.functions - .forEach { it.writePublisherWidget(parentFolder, channel) } + .forEach { it.writePublisherWidget(parentFolder, channel, isProtobufEnabled) } } private fun Method.writePublisherWidget( parentFolder: File, - channel: FlutterSyncChannel + channel: FlutterSyncChannel, + isProtobufEnabled: Boolean, ) = parentFolder.resolve("${method.toSnakeCase()}.dart") .also { it.createNewFile() } .write(PublisherWidget( + isProtobufEnabled = isProtobufEnabled, channel = channel, event = FlutterEvent(command), extension = FlutterExtension("${command.replaceFirstChar { it.uppercase() }}Event"), diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterLibTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterLibTask.kt index 38d53859..9d0db1c5 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterLibTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterLibTask.kt @@ -28,7 +28,10 @@ import dev.buijs.klutter.kore.templates.flutter.PackageLib import java.io.File fun GenerateCodeOptions.toGenerateFlutterLibTask() = - GenerateFlutterLibTask(root = project.root, srcFolder = flutterSrcFolder, pluginName = pluginName) + GenerateFlutterLibTask(root = project.root, + srcFolder = flutterSrcFolder, + bomVersion = klutterBomVersion, + pluginName = pluginName) /** * Generate the Flutter (dart) code in root/lib folder of the plugin project. @@ -37,13 +40,21 @@ class GenerateFlutterLibTask( private val root: Root, private val srcFolder: File, private val pluginName: String, + private val bomVersion: String, ) : KlutterTask, GenerateCodeAction { override fun run() { + // Update Klutter Gradle Plugin Version + root.kradleYaml.let { kradleYamlFile -> + kradleYamlFile.toConfigOrNull() + ?.copy(bomVersion = bomVersion) + ?.let { kradleYamlFile.writeText(mapper.writeValueAsString(it)) } + } + root.pathToLibFile.maybeCreate().write( PackageLib( name = pluginName, - exports = srcFolder.walkTopDown() + exports = srcFolder.verifyExists().walkTopDown() .toList() .filter { it.isFile } .map { it.relativeTo(srcFolder) } diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterMessagesTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterMessagesTask.kt index 154b0b78..1176d9be 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterMessagesTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateFlutterMessagesTask.kt @@ -29,12 +29,18 @@ import dev.buijs.klutter.kore.tasks.execute import java.io.File fun GenerateCodeOptions.toGenerateFlutterMessagesTask() = - GenerateFlutterMessagesTask( - root = project.root, - srcFolder = flutterSrcFolder, - flutterFolder = flutterFolder, - messages = messages, - log = log) + if(responseClassNames.isEmpty()) { + GenerateFlutterMessagesTask( + root = project.root, + srcFolder = flutterSrcFolder, + flutterFolder = flutterFolder, + messages = messages, + log = log) + } else { + object: GenerateCodeAction { + override fun run() { } + } + } /** * Generate the Flutter (dart) code in root/lib folder of the plugin project. diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateIosLibTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateIosLibTask.kt index 92c3a528..73b8f71d 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateIosLibTask.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateIosLibTask.kt @@ -28,7 +28,10 @@ import dev.buijs.klutter.kore.project.* import dev.buijs.klutter.kore.templates.IosAdapter fun GenerateCodeOptions.toGenerateIosLibTask() = - GenerateIosLibTask(ios = project.ios, bindings = bindings) + GenerateIosLibTask( + ios = project.ios, + bindings = bindings, + isProtobufEnabled = responseClassNames.isNotEmpty()) /** * Generate the IOS code in root/ios. @@ -36,6 +39,7 @@ fun GenerateCodeOptions.toGenerateIosLibTask() = class GenerateIosLibTask( private val ios: IOS, private val bindings: Map>, + private val isProtobufEnabled: Boolean, ) : KlutterTask, GenerateCodeAction { override fun run() { ios.pathToPlugin.maybeCreate().write( @@ -43,6 +47,7 @@ class GenerateIosLibTask( pluginClassName = ios.pluginClassName, methodChannels = bindings.values.flatten().filterIsInstance().map { it.name }.toSet(), eventChannels = bindings.values.flatten().filterIsInstance().map { it.name }.toSet(), - controllers = bindings.keys)) + controllers = bindings.keys, + isProtobufEnabled = isProtobufEnabled)) } } \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoExtensionsTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoExtensionsTask.kt new file mode 100644 index 00000000..92caf0a7 --- /dev/null +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoExtensionsTask.kt @@ -0,0 +1,65 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.kore.tasks.codegen + +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.common.maybeCreate +import dev.buijs.klutter.kore.common.maybeCreateFolder +import dev.buijs.klutter.kore.common.write +import dev.buijs.klutter.kore.templates.ProtobufExtensions +import java.io.File + +const val protoGenMarker = "ProtocolBufferGenerated" + +fun GenerateCodeOptions.toGenerateProtoExtensionsTask() = + GenerateProtoExtensionsTask(project.platform.folder + .resolve("build") + .resolve("generated") + .resolve("ksp") + .resolve("metadata") + .resolve("commonMain") + .resolve("kotlin") + , responseClassNames) + +class GenerateProtoExtensionsTask( + private val sourceFolder: File, + private val responseClassNames: List, +) : KlutterTask, GenerateCodeAction { + override fun run() { + for(fqdn in responseClassNames) { + val packageName = fqdn + .substringBeforeLast(".") + + val pathToPackage = packageName + .replace(".", "/") + + val className = fqdn.substringAfterLast(".") + + sourceFolder + .resolve(pathToPackage) + .maybeCreateFolder() + .resolve("$protoGenMarker$className.kt") + .maybeCreate() + .write(ProtobufExtensions(packageName, className)) + } + } +} \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoSchemasTask.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoSchemasTask.kt new file mode 100644 index 00000000..4b3452aa --- /dev/null +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoSchemasTask.kt @@ -0,0 +1,119 @@ +/* Copyright (c) 2021 - 2024 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.kore.tasks.codegen + +import dev.buijs.klutter.kore.KlutterException +import dev.buijs.klutter.kore.KlutterTask +import dev.buijs.klutter.kore.common.maybeCreateFolder +import dev.buijs.klutter.kore.common.verifyExists +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator +import java.io.File +import java.net.URL +import java.net.URLClassLoader +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.full.functions + +/** + * Create an instance of [GenerateProtoSchemasTask] if the List + * [GenerateCodeOptions.responseClassNames] is not empty. + */ +fun GenerateCodeOptions.toGenerateProtoSchemaTask() = + if(responseClassNames.isNotEmpty()) { + GenerateProtoSchemasTask(project.root.folder, + gradleBuildInstanceClassLoader = this::class.java.classLoader) + } else { + object: GenerateCodeAction { + override fun run() { } + } + } + + +/** + * Generate the protocol buffer schemas. + */ +class GenerateProtoSchemasTask( + pathToRoot: File, + /** + * Classloader instance which is retried from the Build_gradle class. + * + * Getting the Classloader through this instance is the only way to ensure + * all dependencies from the SourceSet are present. If any dependency is missing, + * then it is not possible to get the SerializationStrategy of the Serializable + * Response classes. + */ + private val gradleBuildInstanceClassLoader: ClassLoader, +): KlutterTask, GenerateCodeAction { + + private val pathToBuild = pathToRoot.resolve("platform/build") + + @OptIn(ExperimentalSerializationApi::class) + override fun run() { + val pathToSource = pathToBuild + .resolve("klutter/response") + + if(!pathToSource.exists()) return + + val pathToOutput = pathToBuild + .resolve("klutter/protoschema") + .maybeCreateFolder(clearIfExists = true) + + val classNames = pathToSource + .listFiles { _, fn -> fn.startsWith("proto_") } + ?.map { it.readText() } + ?.toSet() + ?: emptySet() + + val url = pathToBuild + .resolve("intermediates/aar_main_jar/release/classes.jar") + .verifyExists() + .toURI() + .toURL() + + val classLoader = URLClassLoader(arrayOf(url), gradleBuildInstanceClassLoader) + + for (className in classNames) { + val classToLoad = Class.forName(className, true, classLoader) + + val companionObject = classToLoad.kotlin.companionObject + ?: throw KlutterException("Failed to find Companion") + + val companionInstance = classToLoad.kotlin.companionObjectInstance + ?: throw KlutterException("Failed to find Companion Instance") + + val functionEx = companionObject.functions.firstOrNull { it.name == "serializer" } + ?: throw KlutterException("Method 'serializer' not found in list of methods: ${companionObject.functions.map { it.name }}") + + val result: KSerializer<*> = functionEx.call(companionInstance) as KSerializer<*> + + val schema = ProtoBufSchemaGenerator.generateSchemaText(result.descriptor) + + pathToOutput.resolve("${className.lowercase()}.proto").let { file -> + file.createNewFile() + file.writeText(schema) + } + } + } + +} \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadFlutter.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadFlutter.kt index 686b50bc..10424111 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadFlutter.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadFlutter.kt @@ -31,11 +31,6 @@ import java.nio.file.Files internal var dryRun = false -/** - * Files which should be executable. - */ -private val executableFiles = setOf("flutter", "dart", "impellerc") - internal fun ProjectBuilderOptions.toDowloadFlutterTask() = DownloadFlutter(flutterDistributionString.flutterDistribution) @@ -69,7 +64,7 @@ internal class DownloadFlutter( // Make sure cache exists in the user home. initKradleCache() if(target.exists()) target.deleteRecursively() - Zippie(zip, executableFiles).unzipTo(target.absolutePath) + Zippie(zip).unzipTo(target.absolutePath) zip.deleteRecursively() } diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadProtoc.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadProtoc.kt new file mode 100644 index 00000000..7673cb76 --- /dev/null +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionDownloadProtoc.kt @@ -0,0 +1,64 @@ +/* Copyright (c) 2021 - 2023 Buijs Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package dev.buijs.klutter.kore.tasks.project + +import dev.buijs.klutter.kore.KlutterException +import dev.buijs.klutter.kore.common.verifyExists +import dev.buijs.klutter.kore.project.* +import dev.buijs.klutter.kore.tasks.Zippie +import java.io.* +import java.net.URL +import java.nio.channels.Channels +import java.nio.file.Files + +internal class DownloadProtoc( + private val rootFolder: File, + private val overwrite: Boolean = false, + private val target: File, +) : ProjectBuilderAction { + override fun doAction() { + if(dryRun) return + val path = findProtocDownloadURLInKradleEnvOrNull(rootFolder.resolve("kradle.env").verifyExists().readText()) + if(target.exists() && !overwrite) return + val zip = tempZipFile + + try { + Channels.newChannel(URL(path).openStream()).use { readableByteChannel -> + val fileOutputStream = FileOutputStream(zip) + val fileChannel = fileOutputStream.getChannel() + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE) + } + } catch (e: IOException) { + throw KlutterException("Failed to download protoc", e) + } + + // Make sure cache exists in the user home. + initKradleCache() + if(target.exists()) target.deleteRecursively() + Zippie(zip).unzipTo(target.absolutePath) + zip.deleteRecursively() + } + +} + +private val tempZipFile: File + get() = Files.createTempDirectory("klutter_download").toFile().resolve("protoc.zip") diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionInitKlutter.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionInitKlutter.kt index 55cea9e7..3fe05470 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionInitKlutter.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/tasks/project/ActionInitKlutter.kt @@ -71,20 +71,15 @@ internal class InitKlutter( root.clearLibFolder() root.overwriteReadmeFile() root.copyLocalProperties() - "$flutter pub get" execute root - "$flutter pub get" execute exampleFolder - "$flutter pub run klutter:producer init bom=$bomVersion flutter=$flutterSDK" execute root - "$flutter pub run klutter:consumer init" execute exampleFolder - "$flutter pub run klutter:consumer add lib=$name" execute exampleFolder + println("$flutter pub get" execute root) + println("$flutter pub get" execute exampleFolder) + println("$flutter pub run klutter:kradle init bom=$bomVersion flutter=$flutterSDK" execute root) + println("$flutter pub run klutter:kradle add lib=$name" execute exampleFolder) exampleFolder.deleteIntegrationTestFolder() - // Do not bother setting up iOS on windows - if(isWindows) return - exampleFolder.deleteIosPodfileLock() - exampleFolder.deleteIosPods() - exampleFolder.deleteRunnerXCWorkspace() - "pod install" execute exampleFolder.resolve("ios") - "pod update" execute exampleFolder.resolve("ios") + // Only setup ios on macos. + if(isMacos) + println("$flutter pub run klutter:kradle init ios=13" execute exampleFolder) } /** @@ -114,18 +109,6 @@ internal class InitKlutter( resolve("lib").listFiles()?.forEach { it.deleteRecursively() } } - private fun File.deleteIosPodfileLock() { - resolve("ios").resolve("Podfile.lock").delete() - } - - private fun File.deleteIosPods() { - resolve("ios").resolve("Pods").deleteRecursively() - } - - private fun File.deleteRunnerXCWorkspace() { - resolve("ios").resolve("Runner.xcworkspace").deleteRecursively() - } - /** * Change the README content to explain Klutter plugin development. */ diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/AndroidAdapter.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/AndroidAdapter.kt index 8164df91..c32b6b83 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/AndroidAdapter.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/AndroidAdapter.kt @@ -29,6 +29,7 @@ import dev.buijs.klutter.kore.common.toSnakeCase class AndroidAdapter( private val pluginPackageName: String, private val pluginClassName: String, + private val isProtobufEnabled: Boolean, methodChannels: Set, eventChannels: Set, controllers: Set, @@ -57,7 +58,7 @@ class AndroidAdapter( ) private val importsControllers = controllers - .mapNotNull { controller -> controller.packageName } + .map { controller -> controller.packageName } .map { "import $it.*" } .toSet() @@ -84,7 +85,7 @@ class AndroidAdapter( private val methodChannelHandlerWhenClauses = controllers .filter { it.functions.isNotEmpty() } .flatMap { controller -> - controller.functions.map { it.methodHandlerString(controller.instanceOrConstructor()) } + controller.functions.map { it.methodHandlerString(controller.instanceOrConstructor(), isProtobufEnabled) } }.sorted() private val methodChannelNames = @@ -214,20 +215,21 @@ class AndroidAdapter( } -private fun Method.methodHandlerString(instanceOrConstuctor: String): String { +private fun Method.methodHandlerString(instanceOrConstuctor: String, isProtobufEnabled: Boolean): String { if(requestDataType is CustomType || requestDataType is EnumType) { - return methodHandlerStringWithCustomTypeRequestParameter(instanceOrConstuctor) + return methodHandlerStringWithCustomTypeRequestParameter(instanceOrConstuctor, isProtobufEnabled) } val requestArgumentOrEmpty = if(requestDataType == null) "" else "data as ${this.requestDataType.typeSimplename(asKotlinType = true)}" - val responseDecoderOrEmpty = when(responseDataType) { + val encoder = if(isProtobufEnabled) "encodeBuffer()" else "encode()" + val responseDecoderOrEmpty = when(this.responseDataType) { is StandardType -> "" - is Nullable -> "?.encode()" - else -> ".encode()" + is Nullable -> "?.$encoder" + else -> ".$encoder" } val methodInvocation = "$instanceOrConstuctor.$method($requestArgumentOrEmpty)$responseDecoderOrEmpty" @@ -247,20 +249,27 @@ private fun Method.methodHandlerString(instanceOrConstuctor: String): String { } -private fun Method.methodHandlerStringWithCustomTypeRequestParameter(instanceOrConstuctor: String): String { +private fun Method.methodHandlerStringWithCustomTypeRequestParameter(instanceOrConstuctor: String, isProtobufEnabled: Boolean): String { val requestType = requestDataType.typeSimplename(asKotlinType = true) - + val encoder = if(isProtobufEnabled) "encodeBuffer()" else "encode()" val responseDecoderOrEmpty = when(this.responseDataType) { is StandardType -> "" - is Nullable -> "?.encode()" - else -> ".encode()" + is Nullable -> "?.$encoder" + else -> ".$encoder" } val methodInvocation = "$instanceOrConstuctor.$method(kJson)$responseDecoderOrEmpty" + + val decoder = if(isProtobufEnabled) { + "data.decodeBuffer<$requestType>()" + } else { + "data.decode() as $requestType?" + } + return if(responseDataType is UnitType) { """ | "$command" -> { - | val kJson: $requestType? = data.decode() as $requestType? + | val kJson: $requestType? = $decoder | if(kJson != null) { | $methodInvocation | } @@ -270,7 +279,7 @@ private fun Method.methodHandlerStringWithCustomTypeRequestParameter(instanceOrC } else """ | "$command" -> { - | val kJson: $requestType? = data.decode() as $requestType? + | val kJson: $requestType? = $decoder | if(kJson != null) { | result.success($methodInvocation) | } else { diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/IosAdapter.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/IosAdapter.kt index 808120b8..95643266 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/IosAdapter.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/IosAdapter.kt @@ -29,6 +29,7 @@ import dev.buijs.klutter.kore.common.toSnakeCase class IosAdapter( private val pluginClassName: String, + private val isProtobufEnabled: Boolean, methodChannels: Set, eventChannels: Set, controllers: Set, @@ -68,7 +69,7 @@ class IosAdapter( """ case "${it.className.toSnakeCase()}":""", " ${it.instanceOrConstructor()}.receiveBroadcastIOS().collect(", " onEach: { value in", - " eventSink(${it.response.responseDecoder()})", + " eventSink(${it.response.responseEncoder()})", " },", " onCompletion: { error in", """ eventSink("ERROR: \("error")")""", @@ -156,11 +157,11 @@ class IosAdapter( private fun Method.methodHandlerString(instanceOrConstuctor: String): List { val method = method.replace("(context)", """(context: "")""") - val responseDecoder = - responseDataType.responseDecoder() + val responseEncoder = + responseDataType.responseEncoder() if(this.requestDataType != null) { - return methodHandlerWithArgument(instanceOrConstuctor, responseDecoder) + return methodHandlerWithArgument(instanceOrConstuctor, responseEncoder) } if(this.responseDataType is UnitType) { @@ -171,7 +172,9 @@ class IosAdapter( listOf( """| func ${command}(data: Any?, result: @escaping FlutterResult) { | $instanceOrConstuctor.${method.removeSuffix("()")} { maybeData, error in - | if let value = maybeData { result($responseDecoder) } + | if let value = maybeData { + | $responseEncoder + | } | | if let failure = error { result(failure) } | } @@ -182,7 +185,7 @@ class IosAdapter( listOf( """| func ${command}(data: Any?, result: @escaping FlutterResult) { | let value = $instanceOrConstuctor.$method() - | result($responseDecoder) + | $responseEncoder | } | """.trimMargin()) @@ -212,23 +215,44 @@ class IosAdapter( } } - private fun Method.methodHandlerWithArgument(instanceOrConstuctor: String, responseDecoder: String): List { - val requestDecoder = when(requestDataType) { - is StringType -> "TypeHandlerKt.stringOrNull(data: data)" - is IntType -> "TypeHandlerKt.intOrNull(data: data)" - is DoubleType -> "TypeHandlerKt.intOrNull(data: data)" - is BooleanType -> "TypeHandlerKt.booleanOrNull(data: data)" - is LongType -> "TypeHandlerKt.intOrNull(data: data)" - is ByteArrayType -> "TypeHandlerKt.byteArrayOrNull(data: data)" - is IntArrayType -> "TypeHandlerKt.intArrayOrNull(data: data)" - is LongArrayType -> "TypeHandlerKt.longArrayOrNull(data: data)" - is FloatArrayType -> "TypeHandlerKt.floatArrayOrNull(data: data)" - is DoubleArrayType -> "TypeHandlerKt.doubleArrayOrNull(data: data)" - is ListType -> "TypeHandlerKt.listOrNull(data: data)" - is MapType -> "TypeHandlerKt.mapOrNull(data: data)" - is CustomType -> "TypeHandlerKt.deserialize(data)" - is EnumType -> "TypeHandlerKt.deserialize(data)" - else -> throw KlutterException("Found unsupported request Type: $requestDataType") + private fun Method.methodHandlerWithArgument(instanceOrConstuctor: String, responseEncoder: String): List { + + var requiresBytesDecoding = false + + val requestDecoder = if(isProtobufEnabled) { + when(requestDataType) { + is StringType -> "TypeHandlerKt.stringOrNull(data: data)" + is IntType -> "TypeHandlerKt.intOrNull(data: data)" + is DoubleType -> "TypeHandlerKt.intOrNull(data: data)" + is BooleanType -> "TypeHandlerKt.booleanOrNull(data: data)" + is LongType -> "TypeHandlerKt.intOrNull(data: data)" + is IntArrayType -> "TypeHandlerKt.intArrayOrNull(data: data)" + is LongArrayType -> "TypeHandlerKt.longArrayOrNull(data: data)" + is FloatArrayType -> "TypeHandlerKt.floatArrayOrNull(data: data)" + is DoubleArrayType -> "TypeHandlerKt.doubleArrayOrNull(data: data)" + else -> { + requiresBytesDecoding = true + "TypeHandlerKt.byteArrayOrNull(data: data)" + } + } + } else { + when(requestDataType) { + is StringType -> "TypeHandlerKt.stringOrNull(data: data)" + is IntType -> "TypeHandlerKt.intOrNull(data: data)" + is DoubleType -> "TypeHandlerKt.intOrNull(data: data)" + is BooleanType -> "TypeHandlerKt.booleanOrNull(data: data)" + is LongType -> "TypeHandlerKt.intOrNull(data: data)" + is ByteArrayType -> "TypeHandlerKt.byteArrayOrNull(data: data)" + is IntArrayType -> "TypeHandlerKt.intArrayOrNull(data: data)" + is LongArrayType -> "TypeHandlerKt.longArrayOrNull(data: data)" + is FloatArrayType -> "TypeHandlerKt.floatArrayOrNull(data: data)" + is DoubleArrayType -> "TypeHandlerKt.doubleArrayOrNull(data: data)" + is ListType -> "TypeHandlerKt.listOrNull(data: data)" + is MapType -> "TypeHandlerKt.mapOrNull(data: data)" + is CustomType -> "TypeHandlerKt.deserialize(data)" + is EnumType -> "TypeHandlerKt.deserialize(data)" + else -> throw KlutterException("Found unsupported request Type: $requestDataType") + } } val swiftRequestDataType = when(requestDataType) { @@ -294,7 +318,14 @@ class IosAdapter( lines.add(" } else {") - val dataOrNull = "(dataOrNull $swiftRequestDataType)" + if(requiresBytesDecoding) + lines.add("let data = ProtocolBufferGenerated${requestDataType.className}Kt.decodeByteArrayTo${requestDataType.className}(byteArray: dataOrNull!)") + + val dataOrNull = if(requiresBytesDecoding) { + "(data $swiftRequestDataType)" + } else { + "(dataOrNull $swiftRequestDataType)" + } if(async) { if(responseDataType is UnitType) { @@ -304,7 +335,7 @@ class IosAdapter( lines.add(" }") } else { lines.add(" $instanceOrConstuctor.$method($requestParameterName: $dataOrNull) { maybeData, error in") - lines.add(" if let value = maybeData { result($responseDecoder) }") + lines.add(" if let value = maybeData { $responseEncoder }") lines.add(" if let failure = error { result(failure) }") lines.add(" }") } @@ -327,10 +358,19 @@ class IosAdapter( else -> "${className}()" } - private fun AbstractType.responseDecoder(): String { - return when (this) { - is StandardType -> "value" - else -> "TypeHandlerKt.encode(value)" + private fun AbstractType.responseEncoder(): String { + return when { + this is StandardType -> "result(value)" + isProtobufEnabled -> + """|let bytes = value.encode${className}ToByteArray() + | var array = [UInt8]() + | var iterator = bytes.iterator() + | while iterator.hasNext() { + | array.append(UInt8(iterator.nextByte())) + | } + | result(FlutterStandardTypedData(bytes: Data(array))) + """.trimMargin() + else -> "result(TypeHandlerKt.encode(value))" } } diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/ProtobufExtensions.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/ProtobufExtensions.kt new file mode 100644 index 00000000..1c64ba58 --- /dev/null +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/ProtobufExtensions.kt @@ -0,0 +1,25 @@ +package dev.buijs.klutter.kore.templates + +import dev.buijs.klutter.kore.KlutterPrinter + +class ProtobufExtensions( + private val packageName: String, + private val className: String, +): KlutterPrinter { + + override fun print(): String = """|package $packageName +| +|import kotlinx.serialization.ExperimentalSerializationApi +|import kotlinx.serialization.decodeFromByteArray +|import kotlinx.serialization.encodeToByteArray +|import kotlinx.serialization.protobuf.ProtoBuf +| +|@OptIn(ExperimentalSerializationApi::class) +|fun $className.encode${className}ToByteArray(): ByteArray = +| ProtoBuf.encodeToByteArray(this) +| +|@OptIn(ExperimentalSerializationApi::class) +|fun decodeByteArrayTo$className(byteArray: ByteArray): $className = +| ProtoBuf.decodeFromByteArray(byteArray)""".trimMargin() + +} \ No newline at end of file diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PublisherWidget.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PublisherWidget.kt index ad4f881a..5c3a7470 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PublisherWidget.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PublisherWidget.kt @@ -99,16 +99,30 @@ fun PublisherWidget.createPrinter(): KlutterPrinter { val requiresResponseDecoder = responseType.dataType !is StandardType - val requestEncoderOrBlank = when(requestType?.dataType) { - is CustomType -> { - "encode: (dynamic data) => (data as ${requestType.dataType.className}).toJson," - } + val requestEncoderOrBlank = if(isProtobufEnabled) { + when(requestType?.dataType) { + is CustomType -> { + "encodeBuffer: (dynamic data) => (data as ${requestType.dataType.className}).writeToBuffer()," + } + + is EnumType -> { + "encodeBuffer: (dynamic data) => Uint8List.fromList([(data as ${responseType.dataType.className}).value])," + } - is EnumType -> { - "encode: (dynamic data) => (data as ${requestType.dataType.className}).toJsonValue," + else -> "" } + } else { + when(requestType?.dataType) { + is CustomType -> { + "encode: (dynamic data) => (data as ${requestType.dataType.className}).toJson," + } - else -> "" + is EnumType -> { + "encode: (dynamic data) => (data as ${requestType.dataType.className}).toJsonValue," + } + + else -> "" + } } val template = buildString { @@ -121,21 +135,37 @@ fun PublisherWidget.createPrinter(): KlutterPrinter { ) if(requiresRequestEncoder) { - append( - """ + if(isProtobufEnabled) { + if(requestType?.dataType is EnumType) { + append("import '../${requestType.dartType.toSnakeCase().replace("_", "")}.pbenum.dart';") + } else { + append("import '../${requestType?.dartType?.toSnakeCase()?.replace("_", "")}.pb.dart';") + } + } else { + append( + """ |import '../${requestType?.dartType?.toSnakeCase()}_dataclass.dart'; |import '../${requestType?.dartType?.toSnakeCase()}_extensions.dart'; """ - ) + ) + } } if(requiresResponseDecoder) { - append( - """ + if(isProtobufEnabled) { + if(responseType.dataType is EnumType) { + append("import '../${responseType.dartType.toSnakeCase().replace("_", "")}.pbenum.dart';") + } else { + append("import '../${responseType.dartType.toSnakeCase().replace("_", "")}.pb.dart';") + } + } else { + append( + """ |import '../${responseType.dartType.toSnakeCase()}_dataclass.dart'; |import '../${responseType.dartType.toSnakeCase()}_extensions.dart'; """ - ) + ) + } } append( @@ -172,7 +202,15 @@ fun PublisherWidget.createPrinter(): KlutterPrinter { } if(requiresResponseDecoder) { - append("decode: (String json) => json.to${responseType.dataType.className},") + if(isProtobufEnabled) { + if(responseType.dataType is EnumType) { + append("decodeBuffer: (List buffer) => ${responseType.dataType.className}.valueOf(buffer[0])!,") + } else { + append("decodeBuffer: (List buffer) => ${responseType.dataType.className}.fromBuffer(buffer),") + } + } else { + append("decode: (String json) => json.to${responseType.dataType.className},") + } } append(""" @@ -211,7 +249,15 @@ fun PublisherWidget.createPrinter(): KlutterPrinter { } if(requiresResponseDecoder) { - append("decode: (String json) => json.to${responseType.dataType.className},") + if(isProtobufEnabled) { + if(responseType.dataType is EnumType) { + append("decodeBuffer: (List buffer) => ${responseType.dataType.className}.valueOf(buffer[0])!,") + } else { + append("decodeBuffer: (List buffer) => ${responseType.dataType.className}.fromBuffer(buffer),") + } + } else { + append("decode: (String json) => json.to${responseType.dataType.className},") + } } append(""" @@ -282,6 +328,8 @@ data class PublisherWidget( * ``` */ val method: FlutterMethod, + + val isProtobufEnabled: Boolean, ) /** diff --git a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PubspecYaml.kt b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PubspecYaml.kt index 84de77ce..e8c722db 100644 --- a/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PubspecYaml.kt +++ b/lib/kore/src/main/kotlin/dev/buijs/klutter/kore/templates/flutter/PubspecYaml.kt @@ -69,6 +69,8 @@ fun createRootPubspecYamlWriter( appendLine("") appendDependency(config?.dependencies?.klutterUI ?: klutterUIPubVersion, name = "klutter_ui") appendLine("") + appendLine("") + appendLine(" protobuf: ^3.1.0") appendLine("dev_dependencies:") appendDependency(config?.dependencies?.klutter ?: klutterPubVersion, name = "klutter") appendLine("") diff --git a/lib/kore/src/main/resources/protoc_plugin.exe b/lib/kore/src/main/resources/protoc_plugin.exe new file mode 100755 index 00000000..8dc564dc Binary files /dev/null and b/lib/kore/src/main/resources/protoc_plugin.exe differ diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Build.kt b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/GetDartProtocExeTaskSpec.groovy similarity index 77% rename from lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Build.kt rename to lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/GetDartProtocExeTaskSpec.groovy index f27f571b..8751f4ff 100644 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/Build.kt +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/GetDartProtocExeTaskSpec.groovy @@ -1,4 +1,4 @@ -/* Copyright (c) 2021 - 2023 Buijs Software +/* Copyright (c) 2021 - 2022 Buijs Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -19,12 +19,16 @@ * SOFTWARE. * */ -package dev.buijs.klutter.kradle.shared +package dev.buijs.klutter.kore.tasks -import java.io.File -fun build(currentFolder: File) { - println("Let's build something great!") - listOf("clean", "build", "-p", "platform").execGradleCommand(currentFolder) - println("Finished project build.") +import spock.lang.Specification + +class GetDartProtocExeTaskSpec extends Specification { + + def "Verify protoc_plugin executable can be loaded from resources"() { + expect: + new GetDartProtocExeTask() != null + } + } \ No newline at end of file diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/codegen/CompileProtoSchemaTaskSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/codegen/CompileProtoSchemaTaskSpec.groovy new file mode 100644 index 00000000..4011360a --- /dev/null +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/codegen/CompileProtoSchemaTaskSpec.groovy @@ -0,0 +1,72 @@ +package dev.buijs.klutter.kore.tasks.codegen + +import spock.lang.Specification + +import java.nio.file.Files + +class CompileProtoSchemaTaskSpec extends Specification { + + def "Verify generated proto files are renamed correctly"() { + given: + def destination = Files.createTempDirectory("").toFile() + def schemaFile = destination.toPath().resolve("foo.bar.someclass.proto").toFile() + schemaFile.createNewFile() + def classInput = destination.toPath().resolve("foo.bar.someclass.pb.dart").toFile() + classInput.createNewFile() + classInput.write("class SomeClass") + def enumInput = destination.toPath().resolve("foo.bar.someclass.pbenum.dart").toFile() + enumInput.createNewFile() + enumInput.write("class SomeClass") + + expect: + with(CompileProtoSchemaTaskKt.renameProtoGeneratedFiles(schemaFile, destination)) { + it.size() == 2 + it.any { it.name.contains("someclass.pb.dart")} + it.any { it.name.contains("someclass.pbenum.dart")} + } + } + + def "Verify generated files that have no class definition are deleted"() { + given: + def destination = Files.createTempDirectory("").toFile() + def schemaFile = destination.toPath().resolve("foo.bar.someclass.proto").toFile() + schemaFile.createNewFile() + def classInput = destination.toPath().resolve("foo.bar.someclass.pb.dart").toFile() + classInput.createNewFile() + classInput.write("class SomeClass") + def enumInput = destination.toPath().resolve("foo.bar.someclass.pbenum.dart").toFile() + enumInput.createNewFile() + enumInput.write("// Comments BLA BLA") + + expect: + with(CompileProtoSchemaTaskKt.renameProtoGeneratedFiles(schemaFile, destination)) { + it.size() == 1 + it[0].name.contains("someclass.pb.dart") + } + + } + + def "Verify generated files that are obsolete are deleted"() { + given: + def destination = Files.createTempDirectory("").toFile() + def schemaFile = destination.toPath().resolve("foo.bar.someclass.proto").toFile() + schemaFile.createNewFile() + def pbjsonFile = destination.toPath().resolve("foo.bar.someclass.pbjson.dart").toFile() + pbjsonFile.createNewFile() + def pbserverFile = destination.toPath().resolve("foo.bar.someclass.pbserver.dart").toFile() + pbserverFile.createNewFile() + + and: + pbjsonFile.exists() + pbserverFile.exists() + + when: + CompileProtoSchemaTaskKt.deleteObsoleteGeneratedFiles(schemaFile, destination) + + then: + !pbjsonFile.exists() + !pbserverFile.exists() + + } + +} diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoExtensionsTaskSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoExtensionsTaskSpec.groovy new file mode 100644 index 00000000..19d98c6f --- /dev/null +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/codegen/GenerateProtoExtensionsTaskSpec.groovy @@ -0,0 +1,40 @@ +package dev.buijs.klutter.kore.tasks.codegen + +import spock.lang.Specification + +import java.nio.file.Files + +class GenerateProtoExtensionsTaskSpec extends Specification { + + def "Verify protobuf extension methods are generated correctly"() { + + given: + def source = Files.createTempDirectory("").toFile() + def kotlin = source.toPath().resolve("com/example/my_plugin/platform").toFile() + kotlin.mkdirs() + + when: + new GenerateProtoExtensionsTask(source, ["com.example.my_plugin.platform.MyGreeting"]).run() + + then: + def generatedFile = kotlin.toPath().resolve("${GenerateProtoExtensionsTaskKt.protoGenMarker}MyGreeting.kt").toFile() + generatedFile.exists() + println(generatedFile.text) + generatedFile.text == """package com.example.my_plugin.platform + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.protobuf.ProtoBuf + +@OptIn(ExperimentalSerializationApi::class) +fun MyGreeting.encodeMyGreetingToByteArray(): ByteArray = + ProtoBuf.encodeToByteArray(this) + +@OptIn(ExperimentalSerializationApi::class) +fun decodeByteArrayToMyGreeting(byteArray: ByteArray): MyGreeting = + ProtoBuf.decodeFromByteArray(byteArray)""" + + } + +} diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ActionInitKlutterSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ActionInitKlutterSpec.groovy index 445b9de9..33518b73 100644 --- a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ActionInitKlutterSpec.groovy +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ActionInitKlutterSpec.groovy @@ -76,7 +76,7 @@ class ActionInitKlutterSpec extends Specification { def setupSpec() { validRootFolder = Either.ok(root) validPluginName = Either.ok(pluginName) - config = new Config(new Dependencies(), "2023.x.y", null) + config = new Config(new Dependencies(), "2023.x.y", null, null) klutterYaml = root.toPath().resolve("my_plugin/kradle.yaml").toFile() plugin.mkdirs() example.mkdirs() diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/GenerateCodeTaskSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/GenerateCodeTaskSpec.groovy index 73a11b0a..8d79d629 100644 --- a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/GenerateCodeTaskSpec.groovy +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/GenerateCodeTaskSpec.groovy @@ -21,29 +21,13 @@ */ package dev.buijs.klutter.kore.tasks.project -import dev.buijs.klutter.kore.ast.Controller -import dev.buijs.klutter.kore.ast.CustomType -import dev.buijs.klutter.kore.ast.Method -import dev.buijs.klutter.kore.ast.RequestScopedBroadcastController -import dev.buijs.klutter.kore.ast.RequestScopedSimpleController -import dev.buijs.klutter.kore.ast.SingletonBroadcastController -import dev.buijs.klutter.kore.ast.SingletonSimpleController -import dev.buijs.klutter.kore.ast.SquintCustomType -import dev.buijs.klutter.kore.ast.SquintCustomTypeMember -import dev.buijs.klutter.kore.ast.SquintMessageSource -import dev.buijs.klutter.kore.ast.StringType -import dev.buijs.klutter.kore.ast.TypeMember -import dev.buijs.klutter.kore.ast.UnitType +import dev.buijs.klutter.kore.ast.* import dev.buijs.klutter.kore.common.Either -import dev.buijs.klutter.kore.project.FlutterDistributionFolderName -import dev.buijs.klutter.kore.project.FlutterDistributionKt import dev.buijs.klutter.kore.project.ProjectKt import dev.buijs.klutter.kore.project.PubspecBuilder import dev.buijs.klutter.kore.tasks.ExecutorKt import dev.buijs.klutter.kore.tasks.codegen.GenerateCodeOptions import dev.buijs.klutter.kore.tasks.codegen.GenerateCodeTaskKt -import dev.buijs.klutter.kore.tasks.project.ActionDownloadFlutterKt -import dev.buijs.klutter.kore.tasks.project.ProjectBuilderTask import spock.lang.Shared import spock.lang.Specification @@ -107,13 +91,16 @@ class GenerateCodeTaskSpec extends Specification { def flutterPubGet = flutterExe + " pub get" @Shared - def klutterProducerInit = flutterExe + " pub run klutter:producer init bom=2023.3.1.beta flutter=3.0.5.macos.arm64" + def klutterProducerInit = flutterExe + " pub run klutter:kradle init bom=2024.1.1.beta flutter=3.0.5.macos.arm64" @Shared - def klutterConsumerInit = flutterExe + " pub run klutter:consumer init" + def klutterConsumerInit = flutterExe + " pub run klutter:kradle init" @Shared - def klutterConsumerAdd = flutterExe + " pub run klutter:consumer add lib=my_awesome_plugin" + def klutterConsumerInitIOS = flutterExe + " pub run klutter:kradle init ios=13" + + @Shared + def klutterConsumerAdd = flutterExe + " pub run klutter:kradle add lib=my_awesome_plugin" @Shared def iosPodUpdate = "pod update" @@ -183,6 +170,7 @@ class GenerateCodeTaskSpec extends Specification { executor.putExpectation(pathToPlugin, klutterProducerInit) executor.putExpectation(pathToExample, klutterConsumerInit) executor.putExpectation(pathToExample, klutterConsumerAdd) + executor.putExpectation(pathToExample, klutterConsumerInitIOS) executor.putExpectation(pathToExampleIos, iosPodUpdate) executor.putExpectation(pathToExampleIos, iosPodInstall) sut.run() @@ -224,6 +212,7 @@ class GenerateCodeTaskSpec extends Specification { def project = ProjectKt.plugin(pathToPlugin) def pubspec = PubspecBuilder.toPubspec(rootPubspecYamlFile) def srcFolder = project.root.pathToLibFolder.toPath().resolve("src") + Path.of(pathToPlugin).resolve("platform/src/commonMain").toFile().mkdirs() and: executor.putExpectation(pathToPlugin, flutterExe + " pub get") @@ -244,7 +233,7 @@ class GenerateCodeTaskSpec extends Specification { and: GenerateCodeTaskKt.toGenerateCodeTask(new GenerateCodeOptions( - project, pubspec, "3.0.5.macos.arm64", false, controllers, messages, { println("$it") })).run() + project, pubspec, "3.0.5.macos.arm64", false, controllers, messages, [],{ println("$it") })).run() then: def flutterLib = project.root.pathToLibFile @@ -283,9 +272,9 @@ dependencies: sdk: flutter squint_json: ^0.1.2 - klutter_ui: ^1.0.1 + klutter_ui: ^1.1.0 dev_dependencies: - klutter: ^2.0.0 + klutter: ^2.1.0 flutter: plugin: platforms: @@ -311,13 +300,13 @@ dependencies: my_awesome_plugin: path: ../ - klutter_ui: ^1.0.1 + klutter_ui: ^1.1.0 squint_json: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter - klutter: ^2.0.0 + klutter: ^2.1.0 flutter: uses-material-design: true """ diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ProjectBuilderTaskSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ProjectBuilderTaskSpec.groovy index a0e9ff75..c089c364 100644 --- a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ProjectBuilderTaskSpec.groovy +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/tasks/project/ProjectBuilderTaskSpec.groovy @@ -23,10 +23,8 @@ package dev.buijs.klutter.kore.tasks.project import dev.buijs.klutter.kore.common.Either import dev.buijs.klutter.kore.project.ProjectKt -import dev.buijs.klutter.kore.test.TestUtil -import dev.buijs.klutter.kore.tasks.Executor import dev.buijs.klutter.kore.tasks.ExecutorKt -import dev.buijs.klutter.kore.tasks.project.ProjectBuilderTask +import dev.buijs.klutter.kore.test.TestUtil import spock.lang.Shared import spock.lang.Specification @@ -86,13 +84,16 @@ class ProjectBuilderTaskSpec extends Specification { def flutterPubGet = flutterExe + " pub get" @Shared - def klutterProducerInit = flutterExe + " pub run klutter:producer init bom=2023.3.1.beta flutter=3.0.5.macos.arm64" + def klutterProducerInit = flutterExe + " pub run klutter:kradle init bom=2024.1.1.beta flutter=3.0.5.macos.arm64" + + @Shared + def klutterConsumerInit = flutterExe + " pub run klutter:kradle init" @Shared - def klutterConsumerInit = flutterExe + " pub run klutter:consumer init" + def klutterConsumerInitIOS = flutterExe + " pub run klutter:kradle init ios=13" @Shared - def klutterConsumerAdd = flutterExe + " pub run klutter:consumer add lib=my_awesome_plugin" + def klutterConsumerAdd = flutterExe + " pub run klutter:kradle add lib=my_awesome_plugin" @Shared def iosPodUpdate = "pod update" @@ -128,6 +129,7 @@ class ProjectBuilderTaskSpec extends Specification { executor.putExpectation(pathToExample, flutterPubGet) executor.putExpectation(pathToPlugin, klutterProducerInit) executor.putExpectation(pathToExample, klutterConsumerInit) + executor.putExpectation(pathToExample, klutterConsumerInitIOS) executor.putExpectation(pathToExample, klutterConsumerAdd) executor.putExpectation(pathToExampleIos, iosPodUpdate) executor.putExpectation(pathToExampleIos, iosPodInstall) @@ -208,9 +210,10 @@ class ProjectBuilderTaskSpec extends Specification { sdk: flutter squint_json: ^0.1.2 - klutter_ui: ^1.0.1 + klutter_ui: ^1.1.0 + protobuf: ^3.1.0 dev_dependencies: - klutter: ^2.0.0 + klutter: ^2.1.0 flutter: plugin: platforms: @@ -237,13 +240,13 @@ class ProjectBuilderTaskSpec extends Specification { my_awesome_plugin: path: ../ - klutter_ui: ^1.0.1 + klutter_ui: ^1.1.0 squint_json: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - klutter: ^2.0.0 + klutter: ^2.1.0 flutter: uses-material-design: true """ @@ -264,13 +267,13 @@ class ProjectBuilderTaskSpec extends Specification { my_awesome_plugin: path: ../ - klutter_ui: ^1.0.1 + klutter_ui: ^1.1.0 squint_json: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter - klutter: ^2.0.0 + klutter: ^2.1.0 flutter: uses-material-design: true diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/AndroidAdapterSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/AndroidAdapterSpec.groovy index f23cf25b..e9ce2f52 100644 --- a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/AndroidAdapterSpec.groovy +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/AndroidAdapterSpec.groovy @@ -302,7 +302,7 @@ class SuperPlugin: FlutterPlugin, MethodCallHandler, StreamHandler, ActivityAwar def "Verify emptySet is used when there are no method channels"() { given: - def adapter = new AndroidAdapter("", "", Set.of(), Set.of(), Set.of()) + def adapter = new AndroidAdapter("", "", false, Set.of(), Set.of(), Set.of()) expect: with(adapter.print()) { content -> @@ -315,7 +315,7 @@ class SuperPlugin: FlutterPlugin, MethodCallHandler, StreamHandler, ActivityAwar given: def methodReturningVoid = createMethod(new StringType(), new UnitType()) def controllerWithMethod = new SingletonSimpleController("foo.bar", "MySingleton", [methodReturningVoid]) - def adapter = new AndroidAdapter("", "", Set.of("foo/bar"), Set.of(), Set.of(controllerWithMethod)) + def adapter = new AndroidAdapter("", "", false,Set.of("foo/bar"), Set.of(), Set.of(controllerWithMethod)) expect: with(adapter.print()) { content -> @@ -402,6 +402,7 @@ class SuperPlugin: FlutterPlugin, MethodCallHandler, StreamHandler, ActivityAwar new AndroidAdapter( packageName, pluginName, + false, channels, streams, controllers diff --git a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/flutter/PublisherWidgetSpec.groovy b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/flutter/PublisherWidgetSpec.groovy index 0668e869..356e3dd7 100644 --- a/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/flutter/PublisherWidgetSpec.groovy +++ b/lib/kore/src/test/groovy/dev/buijs/klutter/kore/template/flutter/PublisherWidgetSpec.groovy @@ -38,7 +38,8 @@ class PublisherWidgetSpec extends Specification { new FlutterExtension("SayWhatNow"), null, new FlutterMessageType(new StringType()), - new FlutterMethod("getSayWhat") + new FlutterMethod("getSayWhat"), + false ) expect: @@ -53,7 +54,8 @@ class PublisherWidgetSpec extends Specification { new FlutterExtension("SayWhatNow"), null, new FlutterMessageType(new CustomType("MyType", "foo.example", [])), - new FlutterMethod("getSayWhat") + new FlutterMethod("getSayWhat"), + false ) expect: @@ -68,7 +70,8 @@ class PublisherWidgetSpec extends Specification { new FlutterExtension("SayWhatNow"), new FlutterMessageType(new StringType()), new FlutterMessageType(new StringType()), - new FlutterMethod("getSayWhat") + new FlutterMethod("getSayWhat"), + false ) expect: @@ -83,7 +86,8 @@ class PublisherWidgetSpec extends Specification { new FlutterExtension("SayWhatNow"), new FlutterMessageType(new CustomType("MyType", "foo.example", [])), new FlutterMessageType(new CustomType("MyOtherType", "foo.example", [])), - new FlutterMethod("getSayWhat") + new FlutterMethod("getSayWhat"), + false ) expect: @@ -98,7 +102,8 @@ class PublisherWidgetSpec extends Specification { new FlutterExtension("SayWhatNow"), new FlutterMessageType(new StringType()), new FlutterMessageType(new CustomType("MyType", "foo.example", [])), - new FlutterMethod("getSayWhat") + new FlutterMethod("getSayWhat"), + false ) expect: @@ -113,7 +118,8 @@ class PublisherWidgetSpec extends Specification { new FlutterExtension("SayWhatNow"), new FlutterMessageType(new CustomType("MyType", "foo.example", [])), new FlutterMessageType(new StringType()), - new FlutterMethod("getSayWhat") + new FlutterMethod("getSayWhat"), + false ) expect: diff --git a/lib/kradle/build.gradle.kts b/lib/kradle/build.gradle.kts deleted file mode 100644 index 8cf2db56..00000000 --- a/lib/kradle/build.gradle.kts +++ /dev/null @@ -1,96 +0,0 @@ -@file:Suppress("VulnerableLibrariesLocal") -plugins { - kotlin("jvm") - id("klutter") - id("groovy") - id("org.jetbrains.kotlin.plugin.allopen") version "1.8.21" -} - -group = "dev.buijs.klutter" - -allOpen { - annotation("dev.buijs.klutter.kradle.Open4Test") -} - -java { - withJavadocJar() - withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -sourceSets { - main { - java { - srcDirs("${projectDir.absolutePath}/src/main/kotlin") - } - } - - test { - java { - srcDirs( - "${projectDir.absolutePath}/src/test/kotlin", - "${projectDir.absolutePath}/src/test/groovy" - ) - } - - } -} - -dependencies { - // Klutter - implementation(project(":lib:kore")) - - // CLI - implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.5") - implementation("com.github.kotlin-inquirer:kotlin-inquirer:0.1.0") - - // Logging - implementation("org.slf4j:slf4j-api:2.0.7") - implementation("io.github.microutils:kotlin-logging:3.0.5") - - // Test - testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2") - - // T-t-t-t-testing ! - testImplementation(project(":lib-test")) - testImplementation("org.spockframework:spock-core") -} - -tasks.withType { - useJUnitPlatform() -} - -tasks.withType { - applicationName = "kradlew" -} - -tasks.register("kradleWrapperJar", Jar::class) { - archiveBaseName.set("${project.name}-wrapper") - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - - manifest { - attributes["Main-Class"] = "dev.buijs.klutter.kradle.MainKt" - } - - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) - with(tasks.jar.get() as CopySpec) -} - -tasks.register("copyKradleWrapperJarToRoot", Copy::class) { - from(project.projectDir.resolve("build/libs/kradle-wrapper.jar")) - into(project.rootProject.rootDir.resolve("kradle/lib")) -} - -tasks.register("copyKradleWrapperJarToResources", Copy::class) { - from(project.projectDir.resolve("build/libs/kradle-wrapper.jar")) - into(project.projectDir.resolve("src/main/resources")) -} - -tasks.named("build") { - setFinalizedBy(setOf("kradleWrapperJar")) -} - -tasks.named("kradleWrapperJar") { - setFinalizedBy(setOf("copyKradleWrapperJarToRoot", "copyKradleWrapperJarToResources")) -} \ No newline at end of file diff --git a/lib/kradle/module.md b/lib/kradle/module.md deleted file mode 100644 index 048bcafe..00000000 --- a/lib/kradle/module.md +++ /dev/null @@ -1,138 +0,0 @@ -# Kradle - -Executable tool to create a new independent [Klutter](https://github.com/buijs-dev/klutter) project. -Create a new project with the click of a button without having any frameworks installed (including Flutter). - -Kradle can be used on any platform using the kraldew or kradlew.bat script. -Running your Gradle and/or Flutter commands through the kradle-wrapper -ensures you use the correct versions for these libraries. You can send commands -through the kradle-wrapper directly or use the interactive wizard to configure. - -## Commands -- [kradle](#kradle) -- [gradle](#gradle) -- [flutter](#flutter) - -### Kradle -Kradle commands: -- build -- [clean](#clean) -- [create](#create) -- [get](#get) - -#### Build -Build the iOS and Android artifacts and generate all boilerplate code. - -> Build is always required to run the app on a device when changes are done in the platform module. - -Example: -```shell -./kradlew build -``` - -#### Clean -Options: -- [cache](#cache) - -##### Cache -Remove all files and/or folders from the kradle cache folder. -The cache folder is by default set to user.home/.kradle/cache. -It can be overwritten by setting the cache property in kradle.env. -The kradle.env is stored next to the kradlew scripts. - -Default kradle.env cache setting: -```properties -cache={{system.user.home}}/.kradle/cache/ -``` - -Example command: -```shell -./kradlew clean cache -``` - -#### Create -Create a new Klutter project. - -Required arguments: -- root: The root folder of the project. -- name: The name of the project (and subsequently the Flutter plugin). -- group: The group/organisation name of the project. -- flutter: The Flutter distribution to use. - -Example: -```shell -./kradlew create --root "./" --name "my_plugin" --group "com.example" --flutter "3.10.6.macos.arm64" -``` - -##### Config (Optional) -Path to config yaml. This yaml can be used to configure project versions and dependencies. - -Example yaml: -```yaml -bom-version: "2023.3.1.beta" -flutter-version: "3.10.6" -dependencies: - klutter: "2.0.0" - klutter_ui: "1.0.1" - squint: "0.1.2" - embedded: - - "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" -``` - -Example command: -```shell -./kradlew create --config "./foo/bar/kradle.yaml" --root "./" --name "my_plugin" --group "com.example" --flutter "3.10.6.macos.arm64" -``` - -#### Get -Get project dependencies and store them in the kradle cache. -Options: -- [flutter](#get-flutter) - -##### Get Flutter -Get a Flutter distribution which is compatible with Klutter. -Options: -- [dist](#Dist) -- [overwrite](#Overwrite) - -###### Dist -The distribution to download in format major.minor.patch.platform.architecture. - -Example command: -```shell -./kradlew get flutter --dist "3.10.6.windows.x64" -``` - -###### Overwrite -Overwrite any existing distribution if present. - -Example which overwrites any existing distribution: -```shell -./kradlew get flutter --dist "3.10.6.windows.x64" --overwrite -``` - -### Gradle -Gradle commands can be executed by using the -g argument. - -```shell -# Run gradle clean build in the folder /platform. -./kradlew -g clean build -p "platform" -``` - -### Flutter -Flutter commands can be executed by using the -f argument. - -```shell -# Run flutter doctor command. -./kradlew -f doctor -``` - -## Wizard -When no argument is given then the interactive wizard is started. - -```shell -./kradlew -``` - - -// TODO wizard UI options etc. \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/Main.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/Main.kt deleted file mode 100644 index 92b1d9ef..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/Main.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle - -import dev.buijs.klutter.kore.project.klutterBomVersion -import dev.buijs.klutter.kradle.command.getNewProjectOptions -import dev.buijs.klutter.kradle.shared.* -import dev.buijs.klutter.kradle.shared.createNewProject -import dev.buijs.klutter.kradle.shared.execFlutterCommand -import dev.buijs.klutter.kradle.shared.execGradleCommand -import dev.buijs.klutter.kradle.wizard.mrWizard -import java.nio.file.Paths - -fun main(args: Array) { - println(""" - ════════════════════════════════════════════ - KRADLE (v$klutterBomVersion) - ════════════════════════════════════════════ - """) - - val currentFolder = Paths.get("").toAbsolutePath().toFile() - - val first: (Array) -> MutableList = { - args.toMutableList().also { it.removeFirst() } - } - - when { - args.isEmpty() -> mrWizard.runWizard(currentFolder) - - args.firstOrNull() == "-g" -> - first(args).execGradleCommand(currentFolder) - - args.firstOrNull() == "-f" -> - first(args).execFlutterCommand(currentFolder) - - args.firstOrNull() == "build" -> - build(currentFolder) - - args.firstOrNull() == "clean" -> - first(args).clean() - - args.firstOrNull() == "create" -> - first(args).getNewProjectOptions().createNewProject() - - args.firstOrNull() == "get" -> - first(args).getDependency() - - else -> println("I don't know what to do. Please try again!") - } - -} \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/GetFlutterCommand.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/GetFlutterCommand.kt deleted file mode 100644 index d81e0022..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/GetFlutterCommand.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.command - -import dev.buijs.klutter.kore.project.* -import dev.buijs.klutter.kore.tasks.project.DownloadFlutterTask -import kotlinx.cli.ArgParser - -private const val distributionDescription = "Flutter distribution in format major.minor.patch.platform.architecture (example: 3.0.5.windows.x64)." - -private const val forceDescription = "Set to true to delete existing Flutter distribution." - -internal fun List.downloadFlutterByCommand() = - toTypedArray().downloadFlutterByCommand() - -internal fun Array.downloadFlutterByCommand() = - GetFlutterCommand(parser = ArgParser("klutter"), args = this).downloadOrFail() - -private fun GetFlutterCommand.downloadOrFail() { - val version = flutterDistribution - val overwrite = overwriteExistingDistribution - println("Will download Flutter ${version.prettyPrintedString}") - if(overwrite) { - println("Will delete existing Flutter ${version.prettyPrintedString} if present") - } else { - println("Will abort if Flutter ${version.prettyPrintedString} is present") - } - DownloadFlutterTask(flutterDistribution, overwriteExistingDistribution).run() - println("Finished get command") -} - -private class GetFlutterCommand(parser: ArgParser, args: Array) { - - private val dist by parser required distributionDescription - - private val overwrite by parser optionalBoolean forceDescription - - init { - parser.parse(args) - } - - val flutterDistribution: FlutterDistribution - get() = FlutterDistributionFolderName(dist).flutterDistribution - - val overwriteExistingDistribution: Boolean - get() = overwrite ?: false - -} diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/NewProjectCommand.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/NewProjectCommand.kt deleted file mode 100644 index e525e910..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/command/NewProjectCommand.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.command - -import dev.buijs.klutter.kore.project.* -import dev.buijs.klutter.kore.tasks.project.* -import dev.buijs.klutter.kradle.shared.NewProjectInput -import kotlinx.cli.ArgParser -import java.io.File - -private const val rootDescription = "Path to root folder" - -private const val groupDescription = "Groupname (organisation)" - -private const val nameDescription = "Name of app (plugin)" - -private const val configDescription = "Path to kradle.yaml" - -private const val flutterDescription = "Flutter SDK version (format: major.minor.patch.platform.architecture, example: 3.10.6.macos.arm64)" - -internal fun List.getNewProjectOptions(): ProjectBuilderOptions = - toTypedArray().getNewProjectOptions() - -internal fun Array.getNewProjectOptions(): ProjectBuilderOptions = - NewProjectCommand(parser = ArgParser("klutter"), args = this).toProjectBuilderOptions() - -private fun NewProjectCommand.toProjectBuilderOptions(): ProjectBuilderOptions = - ProjectBuilderOptions( - rootFolder = rootFolder, - groupName = groupName, - pluginName = pluginName, - flutterDistributionString = flutterDistributionFolderName, - config = configOrNull) - -private class NewProjectCommand(parser: ArgParser, args: Array): NewProjectInput { - - private val root by parser required rootDescription - - private val name by parser required nameDescription - - private val group by parser required groupDescription - - private val flutter by parser required flutterDescription - - private val config by parser optional configDescription - - init { - parser.parse(args) - } - - override val rootFolder: RootFolder - get() = toRootFolder(root) - - override val groupName: GroupName - get() = toGroupName(group) - - override val pluginName: PluginName - get() = toPluginName(name) - - override val flutterDistributionFolderName: FlutterDistributionFolderName - get() = FlutterDistributionFolderName(flutter) - - override val configOrNull: Config? - get() = config?.let { File(it).toConfigOrNull() } - -} \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/ExecFlutterCommand.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/ExecFlutterCommand.kt deleted file mode 100644 index b405b077..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/ExecFlutterCommand.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.shared - -import dev.buijs.klutter.kore.KlutterException -import dev.buijs.klutter.kore.project.* -import dev.buijs.klutter.kore.tasks.finish -import java.io.File - -internal fun List.execFlutterCommand(currentFolder: File): String = - toTypedArray().execFlutterCommand(currentFolder) - -internal fun Array.execFlutterCommand(currentFolder: File): String { - val flutter = findFlutterDistributionOrThrow(currentFolder) - return this.let { args -> - ProcessBuilder() - .command(buildList { - if(isWindows) { - add("cmd.exe") - add("/c") - } - - add(flutter) - addAll(args) - }) - .directory(currentFolder) - .inheritIO() - .start() - .finish(30L) - } -} - -private fun findFlutterDistributionOrThrow(currentFolder: File): String { - val version = currentFolder - .resolve("kradle.yaml") - .resolveProjectPropertiesOrThrow() - .resolveSystemPropertiesOrThrow() - .readText() - .let { findFlutterVersionInKradleYamlOrNull(it) } - ?.let { version -> - version.split(".").let { parts -> - Version(parts[0].toInt(), parts[1].toInt(), parts[2].toInt()) - } - } ?: throw KlutterException("Failed to determine Flutter version from kradle.yaml") - - val distribution = FlutterDistributionFolderName("${version.prettyPrint}.${currentOperatingSystem.value}.${currentArchitecture.name}") - return flutterExecutable(distribution).absolutePath -} \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/ExecGradleCommand.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/ExecGradleCommand.kt deleted file mode 100644 index c0b11e90..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/shared/ExecGradleCommand.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.shared - -import dev.buijs.klutter.kore.project.isWindows -import dev.buijs.klutter.kore.tasks.finish -import java.io.File - -internal fun List.execGradleCommand(currentFolder: File): String = - toTypedArray().execGradleCommand(currentFolder) - -internal fun Array.execGradleCommand(currentFolder: File): String = this.let { args -> - ProcessBuilder() - .command(buildList { - if(isWindows) { - add("cmd.exe") - add("/c") - } - - add(currentFolder.resolve("gradlew").absolutePath) - addAll(args) - }) - .directory(currentFolder) - .inheritIO() - .start() - .finish(30L) -} diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/GetFlutterWizard.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/GetFlutterWizard.kt deleted file mode 100644 index 48cf1c94..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/GetFlutterWizard.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.wizard - -import dev.buijs.klutter.kore.common.ExcludeFromJacocoGeneratedReport -import dev.buijs.klutter.kore.project.* -import dev.buijs.klutter.kore.tasks.project.DownloadFlutterTask -import java.io.File -import java.nio.file.Paths -import kotlin.io.path.absolutePathString - -@ExcludeFromJacocoGeneratedReport -internal fun getFlutterWizard() { - val distribution = askForFlutterVersion().let { version -> - PrettyPrintedFlutterDistribution(version).flutterDistribution - } - - val target = askForDownloadFolder() - - val overwrite = mrWizard.promptConfirm( - message = "Delete the existing SDK distribution if it exists?", - default = false) - println("Downloading Flutter SDK...") - DownloadFlutterTask(distribution, overwrite, target).run() - println("Finished!") -} - -private fun askForDownloadFolder(): File? { - val cache = "Default cache" - val other = "Other (specify)" - val cacheOrOther = mrWizard.promptList( - hint = "press Enter to pick", - message = "Where to store the Flutter SDK distribution?", - choices = listOf(cache, other)) - - /** - * Return null if cache is to be used, - * so that DownloadFlutterTask automatically uses its default configuration. - */ - if(cacheOrOther == cache) return null - - var file: File - do { - val path = mrWizard.promptInput( - message = "Please specify a download folder.", - default = Paths.get("").absolutePathString()) - file = File(path) - } while(!file.exists()) - return file -} \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/MrWizard.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/MrWizard.kt deleted file mode 100644 index fa72941d..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/MrWizard.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.wizard - -import com.github.kinquirer.KInquirer -import com.github.kinquirer.components.promptConfirm -import com.github.kinquirer.components.promptInput -import com.github.kinquirer.components.promptList -import dev.buijs.klutter.kore.common.ExcludeFromJacocoGeneratedReport -import dev.buijs.klutter.kradle.command.Open4Test -import dev.buijs.klutter.kradle.shared.build -import dev.buijs.klutter.kradle.shared.clean -import dev.buijs.klutter.kradle.shared.createNewProject -import java.io.File - -internal var mrWizard: Wizard = MrWizard() - -@Open4Test -@ExcludeFromJacocoGeneratedReport( - reason = "KInquirer can't be mocked/stubbed") -internal open class MrWizard(private val inquirer: KInquirer = KInquirer): Wizard { - override fun promptInput(message: String, default: String) = - inquirer.promptInput(message = message, default = default) - - override fun promptConfirm(message: String, default: Boolean) = - inquirer.promptConfirm(message = message, default = default) - - override fun promptList(hint: String, message: String, choices: List) = - inquirer.promptList(hint = hint, message = message, choices = choices) - -} - -internal interface Wizard { - fun promptInput(message: String, default: String): String - - fun promptConfirm(message: String, default: Boolean): Boolean - - fun promptList(hint: String, message: String, choices: List): String - - fun runWizard(currentFolder: File) { - var chosen: WizardAction - do { - chosen = askForWizardAction() - chosen.action(currentFolder) - } while(chosen != WizardAction.EXIT) - } - - fun askForWizardAction(): WizardAction { - val chosen: String = promptList( - hint = "press Enter to pick", - message = "What do you want to do?", - choices = WizardAction.values().map { it.prettyPrinted }) - return WizardAction.values().first { it.prettyPrinted == chosen } - } -} - -internal enum class WizardAction(val prettyPrinted: String, val action: (currentFolder: File) -> Unit) { - NEW_PROJECT( - prettyPrinted = "New: Project", - action = { getNewProjectOptionsByUserInput().confirmNewProjectOptions()?.createNewProject() }), - GET_FLUTTER_SDK( - prettyPrinted = "Download: Flutter SDK", - action = { getFlutterWizard() }), - CLEAN_CACHE( - prettyPrinted = "Clean: Klutter Cache", - action = { mutableListOf("cache").clean() }), - BUILD_APP( - prettyPrinted = "Build: App", - action = { folder -> build(folder) }), - EXIT( - prettyPrinted = "Exit", - action = { }) -} \ No newline at end of file diff --git a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/NewProjectWizard.kt b/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/NewProjectWizard.kt deleted file mode 100644 index 0acc605f..00000000 --- a/lib/kradle/src/main/kotlin/dev/buijs/klutter/kradle/wizard/NewProjectWizard.kt +++ /dev/null @@ -1,192 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle.wizard - -import dev.buijs.klutter.kore.common.EitherNok -import dev.buijs.klutter.kore.common.EitherOk -import dev.buijs.klutter.kore.common.ExcludeFromJacocoGeneratedReport -import dev.buijs.klutter.kore.project.* -import dev.buijs.klutter.kore.tasks.project.* -import dev.buijs.klutter.kradle.shared.NewProjectInput -import java.io.File -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.absolutePathString - -private const val groupMessage = "Enter Groupname (organisation):" - -private const val groupDefault = "com.example" - -private const val nameMessage = "Enter Plugin name:" - -private const val nameDefault = "my_plugin" - -private const val createProjectInCurrentFolderMessage = "Create project in current folder?" - -private const val createProjectInCurrentFolderDefault = true - -private const val projectFolderPathMessage = "Enter path where to create the project:" - -private const val projectFolderPathDefault = "" - -@ExcludeFromJacocoGeneratedReport -internal fun getNewProjectOptionsByUserInput(): ProjectBuilderOptions = - NewProjectWizard().toProjectBuilderOptions() - -internal fun ProjectBuilderOptions.confirmNewProjectOptions(): ProjectBuilderOptions? { - println(" Confirm project details") - println(" - Plugin Name: ${pluginName.validPluginNameOrThrow()}") - println(" - Group Name: ${groupName.validPluginNameOrThrow()}") - println(" - Flutter: $flutterDistributionString") - println(" - Dependencies:") - println(" - klutter: ${config?.dependencies?.klutter ?: klutterPubVersion}") - println(" - klutter_ui: ${config?.dependencies?.klutterUI ?: klutterUIPubVersion}") - println(" - squint_json: ${config?.dependencies?.squint ?: squintPubVersion}") - println(" - bom: ${config?.bomVersion ?: klutterBomVersion}") - println("") - val confirmed = mrWizard.promptConfirm(message = "Create project?", default = true) - return if(confirmed) this else null -} - -internal class NewProjectWizard( - override val rootFolder: RootFolder = - RootFolderQuestion().ask(), - - override val groupName: GroupName = - toGroupName(askForGroupName()), - - override val pluginName: PluginName = - toPluginName(askForPluginName()), - - private val prettyPrintedFlutterDistribution: String = - askForFlutterVersion(), - - override val configOrNull: Config? = - askForConfig(), - - ) : NewProjectInput { - override val flutterDistributionFolderName: FlutterDistributionFolderName - get() = PrettyPrintedFlutterDistribution(prettyPrintedFlutterDistribution).flutterDistribution.folderNameString -} - -private fun NewProjectWizard.toProjectBuilderOptions(): ProjectBuilderOptions = - ProjectBuilderOptions( - rootFolder = rootFolder, - groupName = groupName, - pluginName = pluginName, - flutterDistributionString = flutterDistributionFolderName, - config = configOrNull) - -private fun askForGroupName(): String = - mrWizard.promptInput(message = groupMessage, default = groupDefault) - -private fun askForPluginName(): String = - mrWizard.promptInput(message = nameMessage, default = nameDefault) - -private class RootFolderQuestion { - private var rootFolder: RootFolder? = null - - fun ask(): RootFolder { - rootFolder = toRootFolder(askForRootFolder()) - if(rootFolder is EitherOk) - return rootFolder!! - - if(rootFolder is EitherNok) - println((rootFolder as EitherNok).data) - - return ask() - } -} - -private fun askForRootFolder(): String { - val useCurrentFolder = mrWizard.promptConfirm( - message = createProjectInCurrentFolderMessage, - default = createProjectInCurrentFolderDefault - ) - - if(useCurrentFolder) - return Path.of("").absolutePathString() - - return mrWizard.promptInput( - message = projectFolderPathMessage, - default = Paths.get(projectFolderPathDefault).absolutePathString()) -} - -private fun askForConfig(): Config? { - val useConfig = mrWizard.promptConfirm( - message = "Configure dependencies?", - default = false) - - if(!useConfig) return null - - val klutter = askForSource( - name = "klutter", - stableVersion = klutterPubVersion, - gitUrl = "https://github.com/buijs-dev/klutter-dart.git@develop") - - val klutterUI = askForSource( - name = "klutter_ui", - stableVersion = klutterUIPubVersion, - gitUrl = "https://github.com/buijs-dev/klutter-dart-ui.git@develop") - - val squint = askForSource( - name = "squint_json", - stableVersion = squintPubVersion, - gitUrl = "https://github.com/buijs-dev/squint.git@develop") - - val bomVersion = askForKlutterGradleBomVersion() - - return Config( - bomVersion = bomVersion, - dependencies = Dependencies( - klutter = klutter, - klutterUI = klutterUI, - squint = squint)) -} - -internal fun askForFlutterVersion(): String = - mrWizard.promptList( - hint = "press Enter to pick", - message = "Select Flutter SDK version:", - choices = flutterVersionsDescending(currentOperatingSystem).map { "${it.prettyPrintedString}" }) - -private fun askForSource(name: String, stableVersion: String, gitUrl: String): String { - val git = "Git@Develop" - val pub = "Pub@^$stableVersion" - val chosen = mrWizard.promptList( - hint = "press Enter to pick", - message = "Get $name source from:", - choices = listOf(git, pub, "Local")) - return when { - chosen.startsWith(git) -> gitUrl - chosen.startsWith("Local") -> "local@${askForPathToLocal(name = name)}" - else -> stableVersion - } -} - -private fun askForPathToLocal(name: String): String = mrWizard.promptInput( - message = "Enter path to $name (dart) library:", - default = Paths.get("").absolutePathString()) - -private fun askForKlutterGradleBomVersion(): String = mrWizard.promptInput( - message = "Enter bill-of-materials version:", - default = klutterBomVersion) \ No newline at end of file diff --git a/lib/kradle/src/test/groovy/dev/buijs/klutter/kradle/ByCommandSpec.groovy b/lib/kradle/src/test/groovy/dev/buijs/klutter/kradle/ByCommandSpec.groovy deleted file mode 100644 index a23b0b5d..00000000 --- a/lib/kradle/src/test/groovy/dev/buijs/klutter/kradle/ByCommandSpec.groovy +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package dev.buijs.klutter.kradle - -import dev.buijs.klutter.kradle.command.NewProjectCommandKt -import spock.lang.Shared -import spock.lang.Specification - -import java.nio.file.Files - -class ByCommandSpec extends Specification { - - @Shared - File rootFolder = Files.createTempDirectory("").toFile() - - @Shared - File flutterSDK = rootFolder.toPath().resolve("flutter").toFile() - - @Shared - File configYaml = rootFolder.toPath().resolve("kradle.yaml").toFile() - - @Shared - String overrideBomVersion = "9999.1.1.zeta" - - def setupSpec() { - flutterSDK.mkdir() - configYaml.createNewFile() - configYaml.write(""" -bom-version: $overrideBomVersion -""") - } - - def "Verify specifying only required args returns ProjectBuilderOptions"() { - given: - def command = [ - "--root", rootFolder.path, - "--group", "com.example", - "--name", "my_awesome_plugin", - "--flutter", "3.0.5.windows.X64", - ] - - when: - def options = NewProjectCommandKt.getNewProjectOptions(command) - - then: - options.pluginName.data == "my_awesome_plugin" - options.groupName.data == "com.example" - options.rootFolder.data == rootFolder - options.config == null - } - - def "When config is specified and the File exists then ProjectBuilderOptions contains config"() { - given: - def command = [ - "--root", rootFolder.path, "" + - "--group", "com.example", - "--name", "my_awesome_plugin", - "--flutter", "3.0.5.windows.X64", - "--config", configYaml.absolutePath - ] - - when: - def options = NewProjectCommandKt.getNewProjectOptions(command) - - then: - options.pluginName.data == "my_awesome_plugin" - options.groupName.data == "com.example" - options.rootFolder.data == rootFolder - options.config != null - options.config.bomVersion == overrideBomVersion - } - -} diff --git a/lib/kradle/src/test/groovy/dev/buijs/klutter/kradle/NewProjectWizardSpec.groovy b/lib/kradle/src/test/groovy/dev/buijs/klutter/kradle/NewProjectWizardSpec.groovy deleted file mode 100644 index d0e1b024..00000000 --- a/lib/kradle/src/test/groovy/dev/buijs/klutter/kradle/NewProjectWizardSpec.groovy +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright (c) 2021 - 2023 Buijs Software - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -//file:noinspection GroovyAssignabilityCheck -package dev.buijs.klutter.kradle - -import dev.buijs.klutter.kore.project.PubspecBuilder -import dev.buijs.klutter.kradle.wizard.MrWizard -import dev.buijs.klutter.kradle.wizard.MrWizardKt -import dev.buijs.klutter.kradle.wizard.NewProjectWizard -import dev.buijs.klutter.kradle.wizard.NewProjectWizardKt -import dev.buijs.klutter.kradle.wizard.Wizard -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Stepwise - -import java.nio.file.Files - -@Stepwise -class NewProjectWizardSpec extends Specification { - - @Shared - File folder = Files.createTempDirectory("").toFile() - - def "Verify wizard asks for all required fields"() { - given: - def mrWizard = Stub(Wizard) - MrWizardKt.mrWizard = mrWizard - - and: "ask for rootFolder" - mrWizard.promptConfirm("Create project in current folder?", _) >> false - mrWizard.promptInput("Enter path where to create the project:", _) >> folder.path - - and: "ask for groupName" - mrWizard.promptInput("Enter Groupname (organisation):", _) >> "foo.boo.yay" - - and: "ask for pluginName" - mrWizard.promptInput("Enter Plugin name:", _) >> "dummy_plugin" - - and: "ask for flutterPath" - mrWizard.promptList( - "press Enter to pick", - "Select Flutter SDK version:", - _ - ) >> "3.0.5 (macos ARM64)" - - and: "ask for configOrNull" - mrWizard.promptConfirm("Configure dependencies?", _) >> false - - and: "ask for confirmation" - mrWizard.promptConfirm("Create project?", _) >> true - - when: - def options = NewProjectWizardKt.toProjectBuilderOptions(new NewProjectWizard()) - - then: - options != null - } - - def "When specified RootFolder does NOT exist then MrWizard asks again"() { - given: - def index = 0 - def folders = ["doesNotExist", folder.path] - def mrWizard = Mock(MrWizard) { - 2 * it.promptInput("Enter path where to create the project:", _) >> { - def rootFolder = folders[index] - index+=1 - rootFolder - } - } - - and: "ask for groupName" - mrWizard.promptInput("Enter Groupname (organisation):", _) >> "foo.boo.yay" - - and: "ask for pluginName" - mrWizard.promptInput("Enter Plugin name:", _) >> "dummy_plugin" - - and: "ask for flutterPath" - mrWizard.promptList( - "press Enter to pick", - "Select Flutter SDK version:", - _ - ) >> "3.0.5 (macos ARM64)" - - and: "ask for configOrNull" - mrWizard.promptConfirm("Configure dependencies?", _) >> false - - and: - MrWizardKt.mrWizard = mrWizard - - and: "ask for rootFolder" - mrWizard.promptConfirm("Create project in current folder?", _) >> false - - when: - def options = NewProjectWizardKt.toProjectBuilderOptions(new NewProjectWizard()) - - then: - options != null - } - - def "Verify wizard asks for all dependencies"() { - given: - def mrWizard = Mock(MrWizard) - MrWizardKt.mrWizard = mrWizard - - and: "ask for rootFolder" - mrWizard.promptConfirm("Create project in current folder?", _) >> false - mrWizard.promptInput("Enter path where to create the project:", _) >> folder.path - - and: "ask for groupName" - mrWizard.promptInput("Enter Groupname (organisation):", _) >> "foo.boo.yay" - - and: "ask for pluginName" - mrWizard.promptInput("Enter Plugin name:", _) >> "dummy_plugin" - - and: "ask for flutterPath" - mrWizard.promptList( - "press Enter to pick", - "Select Flutter SDK version:", - _ - ) >> "3.0.5 (macos ARM64)" - - and: "ask for configOrNull" - mrWizard.promptConfirm("Configure dependencies?", _) >> true - - and: "ask for squint_json" - mrWizard.promptList( - "press Enter to pick", - "Get squint_json source from:", - _ - ) >> "Git@Develop" - - and: "ask for klutter_ui" - mrWizard.promptList( - "press Enter to pick", - "Get klutter_ui source from:", - _ - ) >> "Local" - - and: "ask for klutter_ui local path" - mrWizard.promptInput("Enter path to klutter_ui (dart) library:", _) >> null - - and: "ask for klutter" - mrWizard.promptList( - "press Enter to pick", - "Get klutter source from:", - _ - ) >> "Pub@^${PubspecBuilder.klutterPubVersion}" - - and: "ask for bom-version" - mrWizard.promptInput("Enter bill-of-materials version:", _) >> "9999.x.y.z" - - when: - def options = NewProjectWizardKt.toProjectBuilderOptions(new NewProjectWizard()) - - then: - options != null - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 15cc610c..cee3413d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,9 +5,8 @@ include(":lib:compiler") include(":lib:gradle") include(":lib:jetbrains") include(":lib:kompose") -include(":lib:kradle") include(":lib:kore") // Internal Testing library include(":lib-test") // Internal build properties -includeBuild("lib-build") +includeBuild("lib-build") \ No newline at end of file diff --git a/tools/build_publish_kore.compiler.gradle.sh b/tools/build_publish_kore.compiler.gradle.sh new file mode 100644 index 00000000..2eaba630 --- /dev/null +++ b/tools/build_publish_kore.compiler.gradle.sh @@ -0,0 +1,21 @@ +#!/bin/sh +#stop script on failure +set -e + +cd ".." + +echo " ____________ +< Publishing Klutter modules > + ------------ + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || ||" + +./gradlew clean build -p "lib/kore" +./gradlew clean build -p "lib/compiler" +./gradlew clean build -p "lib/gradle" +./gradlew publishToMavenLocal -p "lib/kore" +./gradlew publishToMavenLocal -p "lib/compiler" +./gradlew publishToMavenLocal -p "lib/gradle" diff --git a/tools/kommand_build.sh b/tools/kommand_build.sh deleted file mode 100644 index b7d38f7b..00000000 --- a/tools/kommand_build.sh +++ /dev/null @@ -1 +0,0 @@ -./gradlew clean distZip -p "./../lib/kommand" \ No newline at end of file diff --git a/tools/kommand_build_run.sh b/tools/kommand_build_run.sh deleted file mode 100644 index 142739b3..00000000 --- a/tools/kommand_build_run.sh +++ /dev/null @@ -1,3 +0,0 @@ -./gradlew clean distZip -p "./../lib/kradle" -unzip ./../lib/kradle/build/distributions/kradle.zip -d ./build -./build/kradle/bin/kradlew \ No newline at end of file