diff --git a/.travis.yml b/.travis.yml index 2c309b8b..7c7bc752 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ env: script: - ./gradlew clean assemble test --tests com.google.protobuf.gradle.ProtobufJavaPluginTest --stacktrace + - ./gradlew test --tests com.google.protobuf.gradle.ProtobufKotlinDslPluginTest --stacktrace - ./gradlew test --tests com.google.protobuf.gradle.ProtobufAndroidPluginTest --stacktrace - ./gradlew test --tests com.google.protobuf.gradle.AndroidProjectDetectionTest --stacktrace - ./gradlew codenarcMain || (cat ./build/reports/codenarc/main.txt && false) diff --git a/README.md b/README.md index 08ec75e6..a1096518 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,12 @@ buildscript { ## Examples -A stand-alone example project is located under [exampleProject](https://github.com/google/protobuf-gradle-plugin/tree/master/exampleProject). -Run `../gradlew build` under that directory to test it out. +Stand-alone examples are available for each of gradle's supported languages. + * [Groovy](https://github.com/google/protobuf-gradle-plugin/tree/master/examples/exampleProject) ___(Default)___ + * Run `../../gradlew build` under the example directory to test it out. + * [Kotlin (experimental)](https://github.com/google/protobuf-gradle-plugin/tree/master/examples/exampleKotlinDslProject) + * Run `./gradlew build` under the Kotlin example directory to test it out. This example is set up with Gradle 4.10, the minimum required version. + Directories that start with `testProject` can also serve as usage examples for advanced options, although they cannot be compiled as diff --git a/build.gradle b/build.gradle index a4e5df12..5bf60741 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ apply plugin: 'codenarc' apply plugin: 'idea' apply plugin: 'eclipse' apply plugin: 'groovy' +apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'signing' apply plugin: 'com.jfrog.bintray' @@ -31,11 +32,13 @@ buildscript { classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath "com.gradle.publish:plugin-publish-plugin:0.9.7" classpath "com.github.ben-manes:gradle-versions-plugin:0.12.0" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0" } } repositories { maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://repo.gradle.org/gradle/libs-releases-local/" } google() } @@ -65,6 +68,8 @@ task createClasspathManifest { dependencies { compileOnly gradleApi() compileOnly localGroovy() + compileOnly "org.gradle:gradle-kotlin-dsl:1.0-rc-6" + compile 'com.google.guava:guava:18.0' compile 'com.google.gradle:osdetector-gradle-plugin:1.4.0' compile 'commons-lang:commons-lang:2.6' @@ -222,3 +227,14 @@ if (System.env.TRAVIS == 'true') { } } } + +// Required in order to support building a mixed kotlin/groovy project. With out this, +// we would get a cyclic dependency error, since both compileKotlin and compileGroovy +// depend on compileJava. +// https://discuss.gradle.org/t/kotlin-groovy-and-java-compilation/14903/10 +project.afterEvaluate { + compileGroovy.dependsOn = compileGroovy.taskDependencies.mutableValues - 'compileJava' + compileKotlin.dependsOn compileGroovy + compileKotlin.classpath += files(compileGroovy.destinationDir) + classes.dependsOn compileKotlin +} diff --git a/exampleProject/settings.gradle b/exampleProject/settings.gradle deleted file mode 100644 index 241dafca..00000000 --- a/exampleProject/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "exampleProject" diff --git a/examples/exampleKotlinDslProject/build.gradle.kts b/examples/exampleKotlinDslProject/build.gradle.kts new file mode 100644 index 00000000..eda034d0 --- /dev/null +++ b/examples/exampleKotlinDslProject/build.gradle.kts @@ -0,0 +1,72 @@ +import com.google.protobuf.gradle.* +import org.gradle.kotlin.dsl.provider.gradleKotlinDslOf + +// A minimal example Java project that uses the protobuf plugin. +// To build it: +// $ ../gradlew clean build +plugins { + java + idea + id("com.google.protobuf") version "0.8.7-SNAPSHOT" +} + +repositories { + maven("https://plugins.gradle.org/m2/") +} + +sourceSets{ + create("sample"){ + proto { + srcDir("src/sample/protobuf") + } + } +} + +dependencies { + compile("com.google.protobuf:protobuf-java:3.6.1") + compile("io.grpc:grpc-stub:1.15.1") + compile("io.grpc:grpc-protobuf:1.15.1") + if (JavaVersion.current().isJava9Compatible) { + // Workaround for @javax.annotation.Generated + // see: https://github.com/grpc/grpc-java/issues/3633 + compile("javax.annotation:javax.annotation-api:1.3.1") + } + // Extra proto source files besides the ones residing under + // "src/main". + protobuf(files("lib/protos.tar.gz")) + protobuf(fileTree("ext/")) + + // Adding dependency for configuration from custom sourceSet + "sampleProtobuf"(fileTree("ext/")) + + testCompile("junit:junit:4.12") + // Extra proto source files for test besides the ones residing under + // "src/test". + testProtobuf(files("lib/protos-test.tar.gz")) +} + +protobuf { + protoc { + // The artifact spec for the Protobuf Compiler + artifact = "com.google.protobuf:protoc:3.6.1" + } + plugins { + // Optional: an artifact spec for a protoc plugin, with "grpc" as + // the identifier, which can be referred to in the "plugins" + // container of the "generateProtoTasks" closure. + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:1.15.1" + } + } + generateProtoTasks { + ofSourceSet("main").forEach { + it.plugins { + // Apply the "grpc" plugin whose spec is defined above, without + // options. Note the braces cannot be omitted, otherwise the + // plugin will not be added. This is because of the implicit way + // NamedDomainObjectContainer binds the methods. + id("grpc") + } + } + } +} \ No newline at end of file diff --git a/exampleProject/ext/more.proto b/examples/exampleKotlinDslProject/ext/more.proto similarity index 100% rename from exampleProject/ext/more.proto rename to examples/exampleKotlinDslProject/ext/more.proto diff --git a/exampleProject/ext/test1.proto b/examples/exampleKotlinDslProject/ext/test1.proto similarity index 100% rename from exampleProject/ext/test1.proto rename to examples/exampleKotlinDslProject/ext/test1.proto diff --git a/exampleProject/ext/test2.proto b/examples/exampleKotlinDslProject/ext/test2.proto similarity index 100% rename from exampleProject/ext/test2.proto rename to examples/exampleKotlinDslProject/ext/test2.proto diff --git a/examples/exampleKotlinDslProject/gradle/wrapper/gradle-wrapper.jar b/examples/exampleKotlinDslProject/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..1948b907 Binary files /dev/null and b/examples/exampleKotlinDslProject/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/exampleKotlinDslProject/gradle/wrapper/gradle-wrapper.properties b/examples/exampleKotlinDslProject/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e0b3fb8d --- /dev/null +++ b/examples/exampleKotlinDslProject/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/exampleKotlinDslProject/gradlew b/examples/exampleKotlinDslProject/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/examples/exampleKotlinDslProject/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_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 "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/examples/exampleKotlinDslProject/gradlew.bat b/examples/exampleKotlinDslProject/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/examples/exampleKotlinDslProject/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exampleProject/lib/protos-test.tar.gz b/examples/exampleKotlinDslProject/lib/protos-test.tar.gz similarity index 100% rename from exampleProject/lib/protos-test.tar.gz rename to examples/exampleKotlinDslProject/lib/protos-test.tar.gz diff --git a/exampleProject/lib/protos.tar.gz b/examples/exampleKotlinDslProject/lib/protos.tar.gz similarity index 100% rename from exampleProject/lib/protos.tar.gz rename to examples/exampleKotlinDslProject/lib/protos.tar.gz diff --git a/examples/exampleKotlinDslProject/settings.gradle.kts b/examples/exampleKotlinDslProject/settings.gradle.kts new file mode 100644 index 00000000..abe95933 --- /dev/null +++ b/examples/exampleKotlinDslProject/settings.gradle.kts @@ -0,0 +1,15 @@ +pluginManagement{ + repositories { + mavenLocal() + maven("https://plugins.gradle.org/m2/") + } + resolutionStrategy { + eachPlugin { + if (requested.id.id == "com.google.protobuf") { + useModule("com.google.protobuf:protobuf-gradle-plugin:${requested.version}") + } + } + } +} +rootProject.name = "exampleProject" + diff --git a/exampleProject/src/main/java/Foo.java b/examples/exampleKotlinDslProject/src/main/java/Foo.java similarity index 100% rename from exampleProject/src/main/java/Foo.java rename to examples/exampleKotlinDslProject/src/main/java/Foo.java diff --git a/exampleProject/src/main/proto/com/example/tutorial/sample.proto b/examples/exampleKotlinDslProject/src/main/proto/com/example/tutorial/sample.proto similarity index 100% rename from exampleProject/src/main/proto/com/example/tutorial/sample.proto rename to examples/exampleKotlinDslProject/src/main/proto/com/example/tutorial/sample.proto diff --git a/exampleProject/src/main/proto/io/grpc/testing/integration/empty.proto b/examples/exampleKotlinDslProject/src/main/proto/io/grpc/testing/integration/empty.proto similarity index 100% rename from exampleProject/src/main/proto/io/grpc/testing/integration/empty.proto rename to examples/exampleKotlinDslProject/src/main/proto/io/grpc/testing/integration/empty.proto diff --git a/exampleProject/src/main/proto/io/grpc/testing/integration/messages.proto b/examples/exampleKotlinDslProject/src/main/proto/io/grpc/testing/integration/messages.proto similarity index 100% rename from exampleProject/src/main/proto/io/grpc/testing/integration/messages.proto rename to examples/exampleKotlinDslProject/src/main/proto/io/grpc/testing/integration/messages.proto diff --git a/exampleProject/src/main/proto/io/grpc/testing/integration/test.proto b/examples/exampleKotlinDslProject/src/main/proto/io/grpc/testing/integration/test.proto similarity index 100% rename from exampleProject/src/main/proto/io/grpc/testing/integration/test.proto rename to examples/exampleKotlinDslProject/src/main/proto/io/grpc/testing/integration/test.proto diff --git a/exampleProject/src/main/proto/ws/antonov/protobuf/test/test.proto b/examples/exampleKotlinDslProject/src/main/proto/ws/antonov/protobuf/test/test.proto similarity index 100% rename from exampleProject/src/main/proto/ws/antonov/protobuf/test/test.proto rename to examples/exampleKotlinDslProject/src/main/proto/ws/antonov/protobuf/test/test.proto diff --git a/exampleProject/src/test/java/FooTest.java b/examples/exampleKotlinDslProject/src/test/java/FooTest.java similarity index 100% rename from exampleProject/src/test/java/FooTest.java rename to examples/exampleKotlinDslProject/src/test/java/FooTest.java diff --git a/exampleProject/src/test/proto/test.proto b/examples/exampleKotlinDslProject/src/test/proto/test.proto similarity index 100% rename from exampleProject/src/test/proto/test.proto rename to examples/exampleKotlinDslProject/src/test/proto/test.proto diff --git a/exampleProject/build.gradle b/examples/exampleProject/build.gradle similarity index 100% rename from exampleProject/build.gradle rename to examples/exampleProject/build.gradle diff --git a/examples/exampleProject/ext/more.proto b/examples/exampleProject/ext/more.proto new file mode 100644 index 00000000..97507e41 --- /dev/null +++ b/examples/exampleProject/ext/more.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +message MoreMsg { + string bar = 1; +} + +message Foo { + string stuff = 1; +} diff --git a/examples/exampleProject/ext/test1.proto b/examples/exampleProject/ext/test1.proto new file mode 100644 index 00000000..bdf5d1e8 --- /dev/null +++ b/examples/exampleProject/ext/test1.proto @@ -0,0 +1,12 @@ +/** + * Created with IntelliJ IDEA. + * User: aantonov + * Date: 1/17/13 + * Time: 3:44 PM + * To change this template use File | Settings | File Templates. + */ +syntax = "proto3"; + +message Test1Msg { + string bar = 1; +} diff --git a/examples/exampleProject/ext/test2.proto b/examples/exampleProject/ext/test2.proto new file mode 100644 index 00000000..f0704546 --- /dev/null +++ b/examples/exampleProject/ext/test2.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Test2Msg { + string bar = 1; +} diff --git a/examples/exampleProject/lib/protos-test.tar.gz b/examples/exampleProject/lib/protos-test.tar.gz new file mode 100644 index 00000000..3f294719 Binary files /dev/null and b/examples/exampleProject/lib/protos-test.tar.gz differ diff --git a/examples/exampleProject/lib/protos.tar.gz b/examples/exampleProject/lib/protos.tar.gz new file mode 100644 index 00000000..a6193a20 Binary files /dev/null and b/examples/exampleProject/lib/protos.tar.gz differ diff --git a/examples/exampleProject/settings.gradle b/examples/exampleProject/settings.gradle new file mode 100644 index 00000000..71bdab0c --- /dev/null +++ b/examples/exampleProject/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "exampleProject" \ No newline at end of file diff --git a/examples/exampleProject/src/main/java/Foo.java b/examples/exampleProject/src/main/java/Foo.java new file mode 100644 index 00000000..4835f53b --- /dev/null +++ b/examples/exampleProject/src/main/java/Foo.java @@ -0,0 +1,28 @@ +import com.google.protobuf.MessageLite; + +import java.util.ArrayList; +import java.util.List; + +public class Foo { + public static List getDefaultInstances() { + ArrayList list = new ArrayList(); + // from src/main/proto/test.proto + list.add(ws.antonov.protobuf.test.Test.TestMessage.getDefaultInstance()); + list.add(ws.antonov.protobuf.test.Test.AnotherMessage.getDefaultInstance()); + list.add(ws.antonov.protobuf.test.Test.Item.getDefaultInstance()); + list.add(ws.antonov.protobuf.test.Test.DataMap.getDefaultInstance()); + // from src/main/proto/sample.proto (java_multiple_files == true, thus no outter class) + list.add(com.example.tutorial.Msg.getDefaultInstance()); + list.add(com.example.tutorial.SecondMsg.getDefaultInstance()); + // from lib/protos.tar.gz/stuff.proto + list.add(Stuff.Blah.getDefaultInstance()); + // from ext/more.proto + list.add(More.MoreMsg.getDefaultInstance()); + list.add(More.Foo.getDefaultInstance()); + // from ext/test1.proto + list.add(Test1.Test1Msg.getDefaultInstance()); + // from ext/test2.proto + list.add(Test2.Test2Msg.getDefaultInstance()); + return list; + } +} diff --git a/examples/exampleProject/src/main/proto/com/example/tutorial/sample.proto b/examples/exampleProject/src/main/proto/com/example/tutorial/sample.proto new file mode 100644 index 00000000..457c4fef --- /dev/null +++ b/examples/exampleProject/src/main/proto/com/example/tutorial/sample.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option java_package = "com.example.tutorial"; +option java_outer_classname = "OuterSample"; +option java_multiple_files = true; + + +message Msg { + string foo = 1; + SecondMsg blah = 2; +} + +message SecondMsg { + int32 blah = 1; +} diff --git a/examples/exampleProject/src/main/proto/io/grpc/testing/integration/empty.proto b/examples/exampleProject/src/main/proto/io/grpc/testing/integration/empty.proto new file mode 100644 index 00000000..8f71229b --- /dev/null +++ b/examples/exampleProject/src/main/proto/io/grpc/testing/integration/empty.proto @@ -0,0 +1,46 @@ + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package grpc.testing; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProtos"; + +// An empty message that you can re-use to avoid defining duplicated empty +// messages in your project. A typical example is to use it as argument or the +// return value of a service API. For instance: +// +// service Foo { +// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { }; +// }; +// +message Empty {} \ No newline at end of file diff --git a/examples/exampleProject/src/main/proto/io/grpc/testing/integration/messages.proto b/examples/exampleProject/src/main/proto/io/grpc/testing/integration/messages.proto new file mode 100644 index 00000000..809224c1 --- /dev/null +++ b/examples/exampleProject/src/main/proto/io/grpc/testing/integration/messages.proto @@ -0,0 +1,138 @@ + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Message definitions to be used by integration test service definitions. + +syntax = "proto3"; + +package grpc.testing; + +option java_package = "io.grpc.testing.integration"; + +// The type of payload that should be returned. +enum PayloadType { + // Compressable text format. + COMPRESSABLE = 0; + + // Uncompressable binary format. + UNCOMPRESSABLE = 1; + + // Randomly chosen from all other formats defined in this enum. + RANDOM = 2; +} + +// A block of data, to simply increase gRPC message size. +message Payload { + // The type of data in body. + PayloadType type = 1; + // Primary contents of payload. + bytes body = 2; +} + +// Unary request. +message SimpleRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, server randomly chooses one from other formats. + PayloadType response_type = 1; + + // Desired payload size in the response from the server. + // If response_type is COMPRESSABLE, this denotes the size before compression. + int32 response_size = 2; + + // Optional input payload sent along with the request. + Payload payload = 3; + + // Whether SimpleResponse should include username. + bool fill_username = 4; + + // Whether SimpleResponse should include OAuth scope. + bool fill_oauth_scope = 5; +} + +// Unary response, as configured by the request. +message SimpleResponse { + // Payload to increase message size. + Payload payload = 1; + // The user the request came from, for verifying authentication was + // successful when the client expected it. + string username = 2; + // OAuth scope. + string oauth_scope = 3; +} + +message SimpleContext { + string value = 1; +} + +// Client-streaming request. +message StreamingInputCallRequest { + // Optional input payload sent along with the request. + Payload payload = 1; + + // Not expecting any payload from the response. +} + +// Client-streaming response. +message StreamingInputCallResponse { + // Aggregated size of payloads received from the client. + int32 aggregated_payload_size = 1; +} + +// Configuration for a particular response. +message ResponseParameters { + // Desired payload sizes in responses from the server. + // If response_type is COMPRESSABLE, this denotes the size before compression. + int32 size = 1; + + // Desired interval between consecutive responses in the response stream in + // microseconds. + int32 interval_us = 2; +} + +// Server-streaming request. +message StreamingOutputCallRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, the payload from each response in the stream + // might be of different types. This is to simulate a mixed type of payload + // stream. + PayloadType response_type = 1; + + // Configuration for each expected response message. + repeated ResponseParameters response_parameters = 2; + + // Optional input payload sent along with the request. + Payload payload = 3; +} + +// Server-streaming response, as configured by the request and parameters. +message StreamingOutputCallResponse { + // Payload to increase response size. + Payload payload = 1; +} diff --git a/examples/exampleProject/src/main/proto/io/grpc/testing/integration/test.proto b/examples/exampleProject/src/main/proto/io/grpc/testing/integration/test.proto new file mode 100644 index 00000000..3c37cc61 --- /dev/null +++ b/examples/exampleProject/src/main/proto/io/grpc/testing/integration/test.proto @@ -0,0 +1,73 @@ + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. +syntax = "proto3"; + +import "io/grpc/testing/integration/empty.proto"; +import "io/grpc/testing/integration/messages.proto"; + +package grpc.testing; + +option java_package = "io.grpc.testing.integration"; + +// A simple service to test the various types of RPCs and experiment with +// performance with various types of payload. +service TestService { + // One empty request followed by one empty response. + rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty); + + // One request followed by one response. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // One request followed by a sequence of responses (streamed download). + // The server returns the payload with client desired type and sizes. + rpc StreamingOutputCall(StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by one response (streamed upload). + // The server returns the aggregated size of client payload as the result. + rpc StreamingInputCall(stream StreamingInputCallRequest) + returns (StreamingInputCallResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + rpc FullDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by a sequence of responses. + // The server buffers all the client requests and then serves them in order. A + // stream of responses are returned to the client when the server starts with + // first request. + rpc HalfDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); +} diff --git a/examples/exampleProject/src/main/proto/ws/antonov/protobuf/test/test.proto b/examples/exampleProject/src/main/proto/ws/antonov/protobuf/test/test.proto new file mode 100644 index 00000000..7a43af2a --- /dev/null +++ b/examples/exampleProject/src/main/proto/ws/antonov/protobuf/test/test.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package ws.antonov.protobuf.test; + +import "com/example/tutorial/sample.proto"; + +message TestMessage { + int32 id = 1; + string name = 2; +} + +message AnotherMessage { + repeated string names = 1; + DataPayload data = 2; + + message DataPayload { + string payload = 1; + } +} + +message Item { + string name = 1; + string value = 2; + Msg msg = 3; + SecondMsg msg2 = 4; +} +message DataMap { + repeated Item data_items = 1; +} diff --git a/examples/exampleProject/src/test/java/FooTest.java b/examples/exampleProject/src/test/java/FooTest.java new file mode 100644 index 00000000..aa760be3 --- /dev/null +++ b/examples/exampleProject/src/test/java/FooTest.java @@ -0,0 +1,25 @@ +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class FooTest { + @org.junit.Test + public void testMainProtos() { + assertEquals(11, Foo.getDefaultInstances().size()); + } + + @org.junit.Test + public void testTestProtos() { + // from src/test/proto/test.proto + Test.MsgTest.getDefaultInstance(); + // from lib/protos-test.tar.gz + test.Stuff.BlahTest.getDefaultInstance(); + } + + @org.junit.Test + public void testGrpc() { + // from src/grpc/proto/ + assertTrue(com.google.protobuf.GeneratedMessageV3.class.isAssignableFrom( + io.grpc.testing.integration.Messages.SimpleRequest.class)); + assertTrue(Object.class.isAssignableFrom(io.grpc.testing.integration.TestServiceGrpc.class)); + } +} diff --git a/examples/exampleProject/src/test/proto/test.proto b/examples/exampleProject/src/test/proto/test.proto new file mode 100644 index 00000000..ff8ecee7 --- /dev/null +++ b/examples/exampleProject/src/test/proto/test.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +// From the main sourceSet +import "com/example/tutorial/sample.proto"; + +message MsgTest { + Msg msg = 1; +} diff --git a/src/main/groovy/com/google/protobuf/gradle/GenerateProtoTask.groovy b/src/main/groovy/com/google/protobuf/gradle/GenerateProtoTask.groovy index 38825ff4..7d4445d8 100644 --- a/src/main/groovy/com/google/protobuf/gradle/GenerateProtoTask.groovy +++ b/src/main/groovy/com/google/protobuf/gradle/GenerateProtoTask.groovy @@ -224,8 +224,7 @@ public class GenerateProtoTask extends DefaultTask { throw new IllegalStateException( "requested descriptor path but descriptor generation is off") } - return descriptorSetOptions.path != null - ? descriptorSetOptions.path : "${outputBaseDir}/descriptor_set.desc" + return descriptorSetOptions.path != null ? descriptorSetOptions.path : "${outputBaseDir}/descriptor_set.desc" } public GenerateProtoTask() { diff --git a/src/main/kotlin/com/google/protobuf/gradle/ProtobufConfiguratorExts.kt b/src/main/kotlin/com/google/protobuf/gradle/ProtobufConfiguratorExts.kt new file mode 100644 index 00000000..d8eb891b --- /dev/null +++ b/src/main/kotlin/com/google/protobuf/gradle/ProtobufConfiguratorExts.kt @@ -0,0 +1,87 @@ +package com.google.protobuf.gradle + +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.tasks.SourceSet +import org.gradle.kotlin.dsl.* + + +fun Project.protobuf(action: ProtobufConfigurator.()->Unit) { + project.convention.getPlugin(ProtobufConvention::class.java).protobuf.apply(action) +} + +fun SourceSet.proto(action: ProtobufSourceDirectorySet.() -> Unit) { + (this as? ExtensionAware) + ?.extensions + ?.getByType(ProtobufSourceDirectorySet::class.java) + ?.apply(action) +} + +fun ProtobufConfigurator.protoc(closure: ExecutableLocator.() -> Unit) { + protoc(closureOf(closure)) +} + +fun ProtobufConfigurator.plugins(closure: NamedDomainObjectContainerScope.() -> Unit) { + plugins(closureOf> { + this(closure) + }) +} + +fun NamedDomainObjectContainerScope.id(id: String, closure: (T.() -> Unit)? = null) { + closure?.let { create(id, closureOf(it)) } + ?: create(id) +} + +fun NamedDomainObjectContainerScope.remove(id: String) { + remove(this[id]) +} + +fun ProtobufConfigurator.generateProtoTasks(closure: ProtobufConfigurator.GenerateProtoTaskCollection.()->Unit) { + generateProtoTasks(closureOf(closure)) +} + +fun GenerateProtoTask.builtins(closure: NamedDomainObjectContainerScope.()->Unit) { + builtins(closureOf> { + this(closure) + }) +} + +fun GenerateProtoTask.plugins(closure: NamedDomainObjectContainerScope.()-> Unit) { + plugins(closureOf> { + this(closure) + }) +} + +/** + * The method generatorProtoTasks applies the supplied closure to the + * instance of [ProtobufConfigurator.GenerateProtoTaskCollection]. + * + * Since [ProtobufConfigurator.JavaGenerateProtoTaskCollection] and [ProtobufConfigurator.AndroidGenerateProtoTaskCollection] + * each have unique methods, and only one instance in allocated per project, we need a way to statically resolve the + * available methods. This is a necessity since Kotlin does not have any dynamic method resolution capabilities. + */ + +fun ProtobufConfigurator.GenerateProtoTaskCollection.ofSourceSet(sourceSet: String): Collection = + if (this is ProtobufConfigurator.JavaGenerateProtoTaskCollection) + this.ofSourceSet(sourceSet) else emptyList() + +fun ProtobufConfigurator.GenerateProtoTaskCollection.ofFlavor(flavor: String): Collection = + if (this is ProtobufConfigurator.AndroidGenerateProtoTaskCollection) + this.ofFlavor(flavor) else emptyList() + +fun ProtobufConfigurator.GenerateProtoTaskCollection.ofBuildType(buildType: String): Collection = + if (this is ProtobufConfigurator.AndroidGenerateProtoTaskCollection) + this.ofBuildType(buildType) else emptyList() + +fun ProtobufConfigurator.GenerateProtoTaskCollection.ofVariant(variant: String): Collection = + if (this is ProtobufConfigurator.AndroidGenerateProtoTaskCollection) + this.ofVariant(variant) else emptyList() + +fun ProtobufConfigurator.GenerateProtoTaskCollection.ofNonTest(): Collection = + if (this is ProtobufConfigurator.AndroidGenerateProtoTaskCollection) + this.ofNonTest() else emptyList() + +fun ProtobufConfigurator.GenerateProtoTaskCollection.ofTest(): Collection = + if (this is ProtobufConfigurator.AndroidGenerateProtoTaskCollection) + this.ofTest() else emptyList() diff --git a/src/main/kotlin/com/google/protobuf/gradle/ProtobufDependencyConfiguration.kt b/src/main/kotlin/com/google/protobuf/gradle/ProtobufDependencyConfiguration.kt new file mode 100644 index 00000000..804e4d88 --- /dev/null +++ b/src/main/kotlin/com/google/protobuf/gradle/ProtobufDependencyConfiguration.kt @@ -0,0 +1,77 @@ +package com.google.protobuf.gradle + +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.kotlin.dsl.add +import org.gradle.kotlin.dsl.create +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + + +val ConfigurationContainer.protobuf: Configuration + get() = getByName("protobuf") + +val DependencyHandler.protobuf by ProtobufDependencyHelper + +val ConfigurationContainer.testProtobuf: Configuration + get() = getByName("testProtobuf") + +val DependencyHandler.testProtobuf by ProtobufDependencyHelper + +class ProtobufDependencyHelper( + private val configurationName: String, + private val dependencyHandler: DependencyHandler +) { + + operator fun invoke(dependencyNotation: Any): Dependency? = + dependencyHandler.add(configurationName, dependencyNotation) + + operator fun invoke( + dependencyNotation: String, + dependencyConfiguration: ExternalModuleDependency.() -> Unit + ): ExternalModuleDependency = + dependencyHandler.add(configurationName, dependencyNotation, dependencyConfiguration) + + operator fun invoke( + group: String, + name: String, + version: String? = null, + configuration: String? = null, + classifier: String? = null, + ext: String? = null + ): ExternalModuleDependency = + dependencyHandler.run { + create(group, name, version, configuration, classifier, ext) + .also { add(configurationName, it) } + } + + operator fun invoke( + group: String, + name: String, + version: String? = null, + configuration: String? = null, + classifier: String? = null, + ext: String? = null, + dependencyConfiguration: ExternalModuleDependency.() -> Unit + ): ExternalModuleDependency = + dependencyHandler.run { + val dep = create(group, name, version, configuration, classifier, ext) + add(configurationName, dep, dependencyConfiguration) + } + + operator fun invoke( + dependency: T, + dependencyConfiguration: T.() -> Unit + ): T = + dependencyHandler.add(configurationName, dependency, dependencyConfiguration) + + companion object : ReadOnlyProperty { + + override fun getValue(thisRef: DependencyHandler, property: KProperty<*>): ProtobufDependencyHelper = + ProtobufDependencyHelper(property.name, thisRef) + } +} \ No newline at end of file diff --git a/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy b/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy index 40405c19..0a60bf27 100644 --- a/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy +++ b/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy @@ -73,7 +73,7 @@ class ProtobufJavaPluginTest extends Specification { then: "it succeed" result.task(":build").outcome == TaskOutcome.SUCCESS - verifyProjectDirHelper(projectDir) + ProtobufPluginTestHelper.verifyProjectDir(projectDir) where: gradleVersion << GRADLE_VERSIONS @@ -94,7 +94,7 @@ class ProtobufJavaPluginTest extends Specification { then: "it succeed" result.task(":build").outcome == TaskOutcome.SUCCESS - verifyProjectDirHelper(projectDir) + ProtobufPluginTestHelper.verifyProjectDir(projectDir) where: gradleVersion << GRADLE_VERSIONS @@ -115,7 +115,7 @@ class ProtobufJavaPluginTest extends Specification { then: "it succeed" result.task(":build").outcome == TaskOutcome.SUCCESS - verifyProjectDirHelper(projectDir) + ProtobufPluginTestHelper.verifyProjectDir(projectDir) where: gradleVersion << GRADLE_VERSIONS @@ -323,17 +323,4 @@ class ProtobufJavaPluginTest extends Specification { then: "it returns maximum integer value" limit == Integer.MAX_VALUE } - - private static void verifyProjectDirHelper(File projectDir) { - ['grpc', 'main', 'test'].each { - File generatedSrcDir = new File(projectDir.path, "build/generated/source/proto/$it") - List fileList = [] - generatedSrcDir.eachFileRecurse { file -> - if (file.path.endsWith('.java')) { - fileList.add (file) - } - } - assert fileList.size > 0 - } - } } diff --git a/src/test/groovy/com/google/protobuf/gradle/ProtobufKotlinDslPluginTest.groovy b/src/test/groovy/com/google/protobuf/gradle/ProtobufKotlinDslPluginTest.groovy new file mode 100644 index 00000000..4811e499 --- /dev/null +++ b/src/test/groovy/com/google/protobuf/gradle/ProtobufKotlinDslPluginTest.groovy @@ -0,0 +1,39 @@ +package com.google.protobuf.gradle + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import spock.lang.Specification + +/** + * Unit tests for kotlin dsl extensions. + */ +class ProtobufKotlinDslPluginTest extends Specification { + private static final List GRADLE_VERSIONS = ["4.10"] + + void "testProjectKotlinDsl should be successfully executed (java-only project)"() { + given: "project from testProjectKotlinDslBase" + File projectDir = ProtobufPluginTestHelper.projectBuilder('testProjectKotlinDsl') + .copyDirs('testProjectKotlinDslBase') + .build() + + when: "build is invoked" + BuildResult result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments('build', '--stacktrace') + .withGradleVersion(gradleVersion) + .forwardStdOutput(new OutputStreamWriter(System.out)) + .forwardStdError(new OutputStreamWriter(System.err)) + // Enabling debug causes the test to fail. + // https://github.com/gradle/gradle/issues/6862 + //.withDebug(true) + .build() + + then: "it succeed" + result.task(":build").outcome == TaskOutcome.SUCCESS + ProtobufPluginTestHelper.verifyProjectDir(projectDir) + + where: + gradleVersion << GRADLE_VERSIONS + } +} diff --git a/src/test/groovy/com/google/protobuf/gradle/ProtobufPluginTestHelper.groovy b/src/test/groovy/com/google/protobuf/gradle/ProtobufPluginTestHelper.groovy index ed3e3ea4..8d286ae9 100644 --- a/src/test/groovy/com/google/protobuf/gradle/ProtobufPluginTestHelper.groovy +++ b/src/test/groovy/com/google/protobuf/gradle/ProtobufPluginTestHelper.groovy @@ -12,6 +12,19 @@ final class ProtobufPluginTestHelper { // do not instantiate } + static void verifyProjectDir(File projectDir) { + ['grpc', 'main', 'test'].each { + File generatedSrcDir = new File(projectDir.path, "build/generated/source/proto/$it") + List fileList = [] + generatedSrcDir.eachFileRecurse { file -> + if (file.path.endsWith('.java')) { + fileList.add (file) + } + } + assert fileList.size > 0 + } + } + static void appendPluginClasspath(File buildFile) { URL pluginClasspathResource = ProtobufPluginTestHelper.classLoader.findResource("plugin-classpath.txt") diff --git a/testProjectKotlinDslBase/build.gradle.kts b/testProjectKotlinDslBase/build.gradle.kts new file mode 100644 index 00000000..40d662e2 --- /dev/null +++ b/testProjectKotlinDslBase/build.gradle.kts @@ -0,0 +1,173 @@ +import com.google.protobuf.gradle.* +import org.gradle.api.internal.HasConvention +import org.gradle.kotlin.dsl.provider.gradleKotlinDslOf + + +buildscript { + dependencies { + // We cant add classpath dependencies to the build script via applying an external file. + // So we have to parse the classpath manifest locally. + File("$projectDir/../../createClasspathManifest/plugin-classpath.txt") + .readLines() + .forEach { classpathEntry -> + if("guava" in classpathEntry){ + if (!project.hasProperty("androidPluginVersion") || + !project.findProperty("androidPluginVersion").toString().startsWith("3.")) { + classpath(files(classpathEntry)) + } + } else { + classpath(files(classpathEntry)) + } + } + } +} + +plugins { + java + idea +} + +apply(plugin = "com.google.protobuf") +// This extension is not auto generated when we apply the plugin using +// apply(plugin = "com.google.protobuf") +val Project.protobuf: ProtobufConvention get() = + this.convention.getPlugin(ProtobufConvention::class) + +repositories { + maven("https://plugins.gradle.org/m2/") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 +} + +val grpcCompile by configurations.creating + +the().sourceSets { + + val grpc by creating { + compileClasspath += grpcCompile + } + + "test"{ + compileClasspath += grpc.output + runtimeClasspath += grpc.output + } +} + +val protobufDep = "com.google.protobuf:protobuf-java:3.0.0" + +dependencies { + protobuf(files("lib/protos.tar.gz")) + protobuf(fileTree("ext/")) + testProtobuf(files("lib/protos-test.tar.gz")) + + compile(protobufDep) + testCompile("junit:junit:4.12") + // KotlinFooTest.kt requires reflection utilities + testCompile("org.jetbrains.kotlin:kotlin-reflect:1.2.0") + grpcCompile(protobufDep) + grpcCompile("io.grpc:grpc-stub:1.0.0-pre2") + grpcCompile("io.grpc:grpc-protobuf:1.0.0-pre2") +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.0.0" + } + plugins { + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:1.0.0-pre2" + } + } + generateProtoTasks { + ofSourceSet("grpc").forEach { task -> + task.plugins { + id("grpc") { + outputSubDir = "grpc_output" + } + } + task.generateDescriptorSet = true + } + } +} + +tasks { + + "jar"(Jar::class) { + sourceSets.forEach { sourceSet -> + from(sourceSet.output) + + val compileTaskName = sourceSet.getCompileTaskName("java") + dependsOn(tasks.getByName(compileTaskName)) + } + } + + "test"{ + + doLast{ + val generateProtoTasks = project.protobuf.protobuf.generateProtoTasks + + val generateProtoTaskNames = generateProtoTasks.all().map { it.name }.toSet() + val generateProtoTaskNamesMain = generateProtoTasks.ofSourceSet("main").map { it.name }.toSet() + + assert(setOf("generateProto", "generateGrpcProto", "generateTestProto") == generateProtoTaskNames) + assert(setOf("generateProto") == generateProtoTaskNamesMain) + + assertJavaCompileHasProtoGeneratedDir("main", listOf("java")) + assertJavaCompileHasProtoGeneratedDir("test", listOf("java")) + assertJavaCompileHasProtoGeneratedDir("grpc", listOf("java", "grpc_output")) + + listOf("main", "test").forEach { sourceSet -> + assertFileExists(false, "$buildDir/generated/source/proto/$sourceSet/descriptor_set.desc") + } + assertFileExists(true, "$buildDir/generated/source/proto/grpc/descriptor_set.desc") + } + } +} + +fun assertJavaCompileHasProtoGeneratedDir(sourceSet: String, codegenPlugins: Collection) { + val compileJavaTask = tasks.getByName(sourceSets.getByName(sourceSet).getCompileTaskName("java")) as JavaCompile + assertJavaCompileHasProtoGeneratedDir(project, sourceSet, compileJavaTask, codegenPlugins) +} + +fun assertFileExists(exists: Boolean, path: String) { + if (exists) { + assert(File(path).exists()) + } else { + assert(!File(path).exists()) + } +} + +fun assertJavaCompileHasProtoGeneratedDir( + project: Project, + sourceSet: String, + compileJavaTask: JavaCompile, + codegenPlugins: Collection +) { + val baseDir = File("${project.buildDir}/generated/source/proto/$sourceSet") + // The expected direct subdirectories under baseDir + val expectedDirs = codegenPlugins.map { codegenPlugin -> + File("${project.buildDir}/generated/source/proto/$sourceSet/$codegenPlugin") + }.toSet() + + val actualDirs = mutableSetOf() + compileJavaTask.source.visit { + + // If the visited file is or is under a direct subdirectory of baseDir, add + // that subdirectory to actualDirs. + var file = this@visit.file + while (true) { + if (file.parentFile == baseDir) { + actualDirs.add(file) + } + if (file.parentFile == null) { + break + } + file = file.parentFile + } + } + assert(expectedDirs == actualDirs) +} + diff --git a/testProjectKotlinDslBase/ext/more.proto b/testProjectKotlinDslBase/ext/more.proto new file mode 100644 index 00000000..97507e41 --- /dev/null +++ b/testProjectKotlinDslBase/ext/more.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +message MoreMsg { + string bar = 1; +} + +message Foo { + string stuff = 1; +} diff --git a/testProjectKotlinDslBase/ext/test1.proto b/testProjectKotlinDslBase/ext/test1.proto new file mode 100644 index 00000000..bdf5d1e8 --- /dev/null +++ b/testProjectKotlinDslBase/ext/test1.proto @@ -0,0 +1,12 @@ +/** + * Created with IntelliJ IDEA. + * User: aantonov + * Date: 1/17/13 + * Time: 3:44 PM + * To change this template use File | Settings | File Templates. + */ +syntax = "proto3"; + +message Test1Msg { + string bar = 1; +} diff --git a/testProjectKotlinDslBase/ext/test2.proto b/testProjectKotlinDslBase/ext/test2.proto new file mode 100644 index 00000000..f0704546 --- /dev/null +++ b/testProjectKotlinDslBase/ext/test2.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Test2Msg { + string bar = 1; +} diff --git a/testProjectKotlinDslBase/lib/protos-test.tar.gz b/testProjectKotlinDslBase/lib/protos-test.tar.gz new file mode 100644 index 00000000..3f294719 Binary files /dev/null and b/testProjectKotlinDslBase/lib/protos-test.tar.gz differ diff --git a/testProjectKotlinDslBase/lib/protos.tar.gz b/testProjectKotlinDslBase/lib/protos.tar.gz new file mode 100644 index 00000000..a6193a20 Binary files /dev/null and b/testProjectKotlinDslBase/lib/protos.tar.gz differ diff --git a/testProjectKotlinDslBase/settings.gradle.kts b/testProjectKotlinDslBase/settings.gradle.kts new file mode 100644 index 00000000..2536c278 --- /dev/null +++ b/testProjectKotlinDslBase/settings.gradle.kts @@ -0,0 +1,3 @@ +rootProject.name = "exampleProject" +rootProject.buildFileName = "build.gradle.kts" + diff --git a/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/empty.proto b/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/empty.proto new file mode 100644 index 00000000..8f71229b --- /dev/null +++ b/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/empty.proto @@ -0,0 +1,46 @@ + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package grpc.testing; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProtos"; + +// An empty message that you can re-use to avoid defining duplicated empty +// messages in your project. A typical example is to use it as argument or the +// return value of a service API. For instance: +// +// service Foo { +// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { }; +// }; +// +message Empty {} \ No newline at end of file diff --git a/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/messages.proto b/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/messages.proto new file mode 100644 index 00000000..809224c1 --- /dev/null +++ b/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/messages.proto @@ -0,0 +1,138 @@ + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Message definitions to be used by integration test service definitions. + +syntax = "proto3"; + +package grpc.testing; + +option java_package = "io.grpc.testing.integration"; + +// The type of payload that should be returned. +enum PayloadType { + // Compressable text format. + COMPRESSABLE = 0; + + // Uncompressable binary format. + UNCOMPRESSABLE = 1; + + // Randomly chosen from all other formats defined in this enum. + RANDOM = 2; +} + +// A block of data, to simply increase gRPC message size. +message Payload { + // The type of data in body. + PayloadType type = 1; + // Primary contents of payload. + bytes body = 2; +} + +// Unary request. +message SimpleRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, server randomly chooses one from other formats. + PayloadType response_type = 1; + + // Desired payload size in the response from the server. + // If response_type is COMPRESSABLE, this denotes the size before compression. + int32 response_size = 2; + + // Optional input payload sent along with the request. + Payload payload = 3; + + // Whether SimpleResponse should include username. + bool fill_username = 4; + + // Whether SimpleResponse should include OAuth scope. + bool fill_oauth_scope = 5; +} + +// Unary response, as configured by the request. +message SimpleResponse { + // Payload to increase message size. + Payload payload = 1; + // The user the request came from, for verifying authentication was + // successful when the client expected it. + string username = 2; + // OAuth scope. + string oauth_scope = 3; +} + +message SimpleContext { + string value = 1; +} + +// Client-streaming request. +message StreamingInputCallRequest { + // Optional input payload sent along with the request. + Payload payload = 1; + + // Not expecting any payload from the response. +} + +// Client-streaming response. +message StreamingInputCallResponse { + // Aggregated size of payloads received from the client. + int32 aggregated_payload_size = 1; +} + +// Configuration for a particular response. +message ResponseParameters { + // Desired payload sizes in responses from the server. + // If response_type is COMPRESSABLE, this denotes the size before compression. + int32 size = 1; + + // Desired interval between consecutive responses in the response stream in + // microseconds. + int32 interval_us = 2; +} + +// Server-streaming request. +message StreamingOutputCallRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, the payload from each response in the stream + // might be of different types. This is to simulate a mixed type of payload + // stream. + PayloadType response_type = 1; + + // Configuration for each expected response message. + repeated ResponseParameters response_parameters = 2; + + // Optional input payload sent along with the request. + Payload payload = 3; +} + +// Server-streaming response, as configured by the request and parameters. +message StreamingOutputCallResponse { + // Payload to increase response size. + Payload payload = 1; +} diff --git a/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/test.proto b/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/test.proto new file mode 100644 index 00000000..3c37cc61 --- /dev/null +++ b/testProjectKotlinDslBase/src/grpc/proto/io/grpc/testing/integration/test.proto @@ -0,0 +1,73 @@ + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. +syntax = "proto3"; + +import "io/grpc/testing/integration/empty.proto"; +import "io/grpc/testing/integration/messages.proto"; + +package grpc.testing; + +option java_package = "io.grpc.testing.integration"; + +// A simple service to test the various types of RPCs and experiment with +// performance with various types of payload. +service TestService { + // One empty request followed by one empty response. + rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty); + + // One request followed by one response. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // One request followed by a sequence of responses (streamed download). + // The server returns the payload with client desired type and sizes. + rpc StreamingOutputCall(StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by one response (streamed upload). + // The server returns the aggregated size of client payload as the result. + rpc StreamingInputCall(stream StreamingInputCallRequest) + returns (StreamingInputCallResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + rpc FullDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by a sequence of responses. + // The server buffers all the client requests and then serves them in order. A + // stream of responses are returned to the client when the server starts with + // first request. + rpc HalfDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); +} diff --git a/testProjectKotlinDslBase/src/main/proto/com/example/tutorial/sample.proto b/testProjectKotlinDslBase/src/main/proto/com/example/tutorial/sample.proto new file mode 100644 index 00000000..457c4fef --- /dev/null +++ b/testProjectKotlinDslBase/src/main/proto/com/example/tutorial/sample.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option java_package = "com.example.tutorial"; +option java_outer_classname = "OuterSample"; +option java_multiple_files = true; + + +message Msg { + string foo = 1; + SecondMsg blah = 2; +} + +message SecondMsg { + int32 blah = 1; +} diff --git a/testProjectKotlinDslBase/src/main/proto/ws/antonov/protobuf/test/test.proto b/testProjectKotlinDslBase/src/main/proto/ws/antonov/protobuf/test/test.proto new file mode 100644 index 00000000..7a43af2a --- /dev/null +++ b/testProjectKotlinDslBase/src/main/proto/ws/antonov/protobuf/test/test.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package ws.antonov.protobuf.test; + +import "com/example/tutorial/sample.proto"; + +message TestMessage { + int32 id = 1; + string name = 2; +} + +message AnotherMessage { + repeated string names = 1; + DataPayload data = 2; + + message DataPayload { + string payload = 1; + } +} + +message Item { + string name = 1; + string value = 2; + Msg msg = 3; + SecondMsg msg2 = 4; +} +message DataMap { + repeated Item data_items = 1; +} diff --git a/testProjectKotlinDslBase/src/test/proto/test.proto b/testProjectKotlinDslBase/src/test/proto/test.proto new file mode 100644 index 00000000..ff8ecee7 --- /dev/null +++ b/testProjectKotlinDslBase/src/test/proto/test.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +// From the main sourceSet +import "com/example/tutorial/sample.proto"; + +message MsgTest { + Msg msg = 1; +}