diff --git a/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy b/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy index a824914a..ed576d90 100644 --- a/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy +++ b/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy @@ -38,6 +38,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.LibraryElements import org.gradle.api.file.FileCollection import org.gradle.api.file.SourceDirectorySet @@ -130,8 +131,15 @@ class ProtobufPlugin implements Plugin { project.afterEvaluate { // All custom source sets, configurations created by other plugins, // and Android variants are only available at this point. - getSourceSets().each { sourceSet -> - createCompileProtoPathConfiguration(sourceSet.name) + + // Java projects will extract included protos from a 'compileProtoPath' + // configuration of each source set, while Android projects will + // extract included protos from {@code variant.compileConfiguration} + // of each variant. + if (!Utils.isAndroidProject(project)) { + getSourceSets().each { sourceSet -> + createCompileProtoPathConfiguration(sourceSet.name) + } } addProtoTasks() project.protobuf.runTaskConfigClosures() @@ -173,6 +181,7 @@ class ProtobufPlugin implements Plugin { * The extract-include-protos task of each source set will extract protobuf files from * dependencies in this configuration. * + *

For Java projects only. *

This works around 'java-library' plugin not exposing resources to consumers for compilation. */ private void createCompileProtoPathConfiguration(String sourceSetName) { @@ -267,6 +276,7 @@ class ProtobufPlugin implements Plugin { * Creates Protobuf tasks for a variant in an Android project. */ private void addTasksForVariant(final Object variant, boolean isTestVariant) { + // GenerateProto task, one per variant (compilation unit). Task generateProtoTask = addGenerateProtoTask(variant.name, variant.sourceSets) generateProtoTask.setVariant(variant, isTestVariant) generateProtoTask.flavors = ImmutableList.copyOf(variant.productFlavors.collect { it.name } ) @@ -275,19 +285,37 @@ class ProtobufPlugin implements Plugin { } generateProtoTask.doneInitializing() + // ExtractIncludeProto task, one per variant (compilation unit). + // Proto definitions from an AAR dependencies are in its JAR resources. + Attribute artifactType = Attribute.of("artifactType", String) + FileCollection classPathConfig = variant.compileConfiguration.incoming.artifactView { + attributes { + it.attribute(artifactType, "jar") + } + }.files + FileCollection testClassPathConfig = + variant.hasProperty("testedVariant") ? + variant.testedVariant.compileConfiguration.incoming.artifactView { + attributes { + it.attribute(artifactType, "jar") + } + }.files : null + setupExtractIncludeProtosTask(generateProtoTask, variant.name, classPathConfig, testClassPathConfig) + + // ExtractProto task, one per source set. if (project.android.hasProperty('libraryVariants')) { + // Include source proto files in the compiled archive, so that proto files from + // dependent projects can import them. Task processResourcesTask = variant.getProcessJavaResourcesProvider().get() processResourcesTask.from(generateProtoTask.sourceFiles) { include '**/*.proto' } variant.sourceSets.each { - setupExtractIncludeProtosTask(generateProtoTask, it.name) Task extractTask = setupExtractProtosTask(generateProtoTask, it.name) processResourcesTask.dependsOn(extractTask) } } else { variant.sourceSets.each { - setupExtractIncludeProtosTask(generateProtoTask, it.name) setupExtractProtosTask(generateProtoTask, it.name) } } @@ -384,7 +412,7 @@ class ProtobufPlugin implements Plugin { // dependencies. if (Utils.isTest(sourceSetOrVariantName)) { inputFiles.from getSourceSets()['main'].proto - inputFiles.from testedCompileClasspathConfiguration ?: project.configurations['compile'] + inputFiles.from testedCompileClasspathConfiguration } } else { // In Java projects, the compileClasspath of the 'test' sourceSet includes all the diff --git a/src/test/groovy/com/google/protobuf/gradle/ProtobufAndroidPluginTest.groovy b/src/test/groovy/com/google/protobuf/gradle/ProtobufAndroidPluginTest.groovy index 20f47a09..7ce61573 100644 --- a/src/test/groovy/com/google/protobuf/gradle/ProtobufAndroidPluginTest.groovy +++ b/src/test/groovy/com/google/protobuf/gradle/ProtobufAndroidPluginTest.groovy @@ -110,6 +110,37 @@ class ProtobufAndroidPluginTest extends Specification { gradleVersion << GRADLE_VERSION.takeRight(1) } + @Unroll + void "testProjectAndroidDependent [android #agpVersion, gradle #gradleVersion, kotlin #kotlinVersion]"() { + given: "project from testProjectAndroidLibrary, testProjectAndroid" + File testProjectStaging = ProtobufPluginTestHelper.projectBuilder('testProject') + .copyDirs('testProjectBase', 'testProject') + .build() + File testProjectLibraryStaging = ProtobufPluginTestHelper.projectBuilder('testProjectAndroidLibrary') + .copyDirs('testProjectAndroidLibrary') + .build() + File testProjectAndroidStaging = ProtobufPluginTestHelper.projectBuilder('testProjectAndroid') + .copyDirs('testProjectAndroidDependentBase', 'testProjectAndroid') + .build() + File mainProjectDir = ProtobufPluginTestHelper.projectBuilder('testProjectAndroidDependentMain') + .copySubProjects(testProjectStaging, testProjectLibraryStaging, testProjectAndroidStaging) + .withAndroidPlugin(agpVersion) + .build() + when: "build is invoked" + BuildResult result = buildAndroidProject( + mainProjectDir, + gradleVersion, + "testProjectAndroid:build" + ) + + then: "it succeed" + result.task(":testProjectAndroid:build").outcome == TaskOutcome.SUCCESS + + where: + agpVersion << ANDROID_PLUGIN_VERSION + gradleVersion << GRADLE_VERSION + } + @Unroll void "testProjectAndroidKotlin [android #agpVersion, gradle #gradleVersion, kotlin #kotlinVersion]"() { given: "project from testProject, testProjectLite & testProjectAndroid" diff --git a/testProjectAndroid/src/androidTest/java/io/grpc/helloworldexample/TestLibrary.java b/testProjectAndroid/src/androidTest/java/io/grpc/helloworldexample/TestLibrary.java index 51d02bd6..074a225a 100644 --- a/testProjectAndroid/src/androidTest/java/io/grpc/helloworldexample/TestLibrary.java +++ b/testProjectAndroid/src/androidTest/java/io/grpc/helloworldexample/TestLibrary.java @@ -9,7 +9,7 @@ public class TestLibrary { // From src/main/proto/helloworld.proto Helloworld.HelloRequest request; - // From testProjectLite: src/nano/proto/messages.proto + // From dependency project (testProjectLite/testProjectAndroidLibrary): src/proto/messages.proto io.grpc.testing.Messages.SimpleRequest simpleRequest; // From lib/protos.jar diff --git a/testProjectAndroid/src/test/java/io/grpc/helloworldexample/UnitTest.java b/testProjectAndroid/src/test/java/io/grpc/helloworldexample/UnitTest.java index 3c0c0322..1a3114ef 100644 --- a/testProjectAndroid/src/test/java/io/grpc/helloworldexample/UnitTest.java +++ b/testProjectAndroid/src/test/java/io/grpc/helloworldexample/UnitTest.java @@ -15,7 +15,7 @@ public final class UnitTest { // From src/main/proto/helloworld.proto private Helloworld.HelloRequest request; - // From testProjectLite: src/nano/proto/messages.proto + // From dependency project (testProjectLite/testProjectAndroidLibrary): src/proto/messages.proto private io.grpc.testing.Messages.SimpleRequest simpleRequest; // From lib/protos.jar diff --git a/testProjectAndroidDependentBase/build_base.gradle b/testProjectAndroidDependentBase/build_base.gradle new file mode 100644 index 00000000..fd327a65 --- /dev/null +++ b/testProjectAndroidDependentBase/build_base.gradle @@ -0,0 +1,274 @@ +repositories { + maven { url 'https://maven.google.com' } + maven { url "https://plugins.gradle.org/m2/" } +} + +buildscript { + repositories { + maven { url 'https://maven.google.com' } + maven { url "https://plugins.gradle.org/m2/" } + } +} + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + + defaultConfig { + applicationId "io.grpc.helloworldexample" + minSdkVersion 7 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + + flavorDimensions 'abi', 'version' + + productFlavors { + freeapp { + dimension 'version' + } + retailapp { + dimension 'version' + } + x86 { + dimension 'abi' + } + arm { + dimension 'abi' + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + } + + packagingOptions { + exclude 'io/grpc/testing/integration/empty.proto' + exclude 'io/grpc/testing/integration/test.proto' + exclude 'io/grpc/testing/integration/messages.proto' + exclude 'tmp/stuff.proto' + } + + // https://github.com/square/okio/issues/58 + lintOptions { + warning 'InvalidPackage' + abortOnError false + } + + dexOptions { + javaMaxHeapSize "1g" + threadCount 1 // reduce predex thread count to limit memory usage + } +} + +protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.0.0' + } + plugins { + grpc { + artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0-pre2' + } + javalite { + artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' + } + } + generateProtoTasks { + all()*.plugins { + javalite { } + } + ofNonTest()*.plugins { + grpc { + // Options added to --grpc_out + option 'lite' + } + } + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.squareup.okhttp:okhttp:2.7.5' + compile 'javax.annotation:javax.annotation-api:1.2' + compile 'com.google.protobuf:protobuf-lite:3.0.0' + compile 'io.grpc:grpc-core:1.0.0-pre2' + compile 'io.grpc:grpc-stub:1.0.0-pre2' + compile 'io.grpc:grpc-okhttp:1.0.0-pre2' + compile('io.grpc:grpc-protobuf-lite:1.0.0-pre2') { + // Otherwise Android compile will complain "Multiple dex files define ..." + exclude module: "protobuf-lite" + } + compile(project(':testProjectAndroidLibrary')) { + exclude module: "protobuf-lite" + } + protobuf files('lib/protos.jar') + testCompile 'junit:junit:4.12' +} + +def assertJavaCompileHasProtoGeneratedDir(Object variant, Collection codegenPlugins) { + rootProject.assertJavaCompileHasProtoGeneratedDir(project, variant.name, variant.javaCompileProvider.get(), codegenPlugins) +} + +afterEvaluate { + // 'gradle test' will run the unit tests, which is still experimental in + // Android plugin, and would do nothing with our setup. We make 'test' to + // trigger the "androidTest" Java compile tasks. + android.testVariants.each { testVariant -> + test.dependsOn testVariant.javaCompileProvider + } + + test.doLast { + assert [ + 'generateArmFreeappDebugAndroidTestProto', + 'generateArmFreeappDebugUnitTestProto', + 'generateArmFreeappReleaseUnitTestProto', + 'generateArmFreeappDebugProto', + 'generateArmFreeappReleaseProto', + 'generateArmRetailappDebugAndroidTestProto', + 'generateArmRetailappDebugUnitTestProto', + 'generateArmRetailappReleaseUnitTestProto', + 'generateArmRetailappDebugProto', + 'generateArmRetailappReleaseProto', + 'generateX86FreeappDebugAndroidTestProto', + 'generateX86FreeappDebugUnitTestProto', + 'generateX86FreeappReleaseUnitTestProto', + 'generateX86FreeappDebugProto', + 'generateX86FreeappReleaseProto', + 'generateX86RetailappDebugAndroidTestProto', + 'generateX86RetailappDebugUnitTestProto', + 'generateX86RetailappReleaseUnitTestProto', + 'generateX86RetailappDebugProto', + 'generateX86RetailappReleaseProto', + ] as Set == protobuf.generateProtoTasks.all().collect({ it.name }) as Set + + assert [ + 'generateArmFreeappDebugAndroidTestProto', + 'generateArmFreeappDebugUnitTestProto', + 'generateArmFreeappReleaseUnitTestProto', + 'generateArmRetailappDebugAndroidTestProto', + 'generateArmRetailappDebugUnitTestProto', + 'generateArmRetailappReleaseUnitTestProto', + 'generateX86FreeappDebugAndroidTestProto', + 'generateX86FreeappDebugUnitTestProto', + 'generateX86FreeappReleaseUnitTestProto', + 'generateX86RetailappDebugAndroidTestProto', + 'generateX86RetailappDebugUnitTestProto', + 'generateX86RetailappReleaseUnitTestProto', + ] as Set == protobuf.generateProtoTasks.ofTest().collect({ it.name }) as Set + + assert [ + 'generateArmFreeappDebugProto', + 'generateArmFreeappReleaseProto', + 'generateArmRetailappDebugProto', + 'generateArmRetailappReleaseProto', + 'generateX86FreeappDebugProto', + 'generateX86FreeappReleaseProto', + 'generateX86RetailappDebugProto', + 'generateX86RetailappReleaseProto', + ] as Set == protobuf.generateProtoTasks.ofNonTest().collect({ it.name }) as Set + + assert [ + 'generateArmFreeappDebugAndroidTestProto', + 'generateArmFreeappDebugUnitTestProto', + 'generateArmFreeappReleaseUnitTestProto', + 'generateArmFreeappDebugProto', + 'generateArmFreeappReleaseProto', + 'generateX86FreeappDebugAndroidTestProto', + 'generateX86FreeappDebugUnitTestProto', + 'generateX86FreeappReleaseUnitTestProto', + 'generateX86FreeappDebugProto', + 'generateX86FreeappReleaseProto', + ] as Set == protobuf.generateProtoTasks.ofFlavor('freeapp').collect({ it.name }) as Set + + assert [ + 'generateArmRetailappDebugAndroidTestProto', + 'generateArmRetailappDebugUnitTestProto', + 'generateArmRetailappReleaseUnitTestProto', + 'generateArmRetailappDebugProto', + 'generateArmRetailappReleaseProto', + 'generateX86RetailappDebugAndroidTestProto', + 'generateX86RetailappDebugUnitTestProto', + 'generateX86RetailappReleaseUnitTestProto', + 'generateX86RetailappDebugProto', + 'generateX86RetailappReleaseProto', + ] as Set == protobuf.generateProtoTasks.ofFlavor('retailapp').collect({ it.name }) as Set + + assert [ + 'generateX86FreeappDebugAndroidTestProto', + 'generateX86FreeappDebugUnitTestProto', + 'generateX86FreeappReleaseUnitTestProto', + 'generateX86FreeappDebugProto', + 'generateX86FreeappReleaseProto', + 'generateX86RetailappDebugAndroidTestProto', + 'generateX86RetailappDebugUnitTestProto', + 'generateX86RetailappReleaseUnitTestProto', + 'generateX86RetailappDebugProto', + 'generateX86RetailappReleaseProto', + ] as Set == protobuf.generateProtoTasks.ofFlavor('x86').collect({ it.name }) as Set + + assert [ + 'generateArmFreeappDebugAndroidTestProto', + 'generateArmFreeappDebugUnitTestProto', + 'generateArmFreeappReleaseUnitTestProto', + 'generateArmFreeappDebugProto', + 'generateArmFreeappReleaseProto', + 'generateArmRetailappDebugAndroidTestProto', + 'generateArmRetailappDebugUnitTestProto', + 'generateArmRetailappReleaseUnitTestProto', + 'generateArmRetailappDebugProto', + 'generateArmRetailappReleaseProto', + ] as Set == protobuf.generateProtoTasks.ofFlavor('arm').collect({ it.name }) as Set + + assert [ + 'generateArmFreeappDebugAndroidTestProto', + 'generateArmFreeappDebugUnitTestProto', + 'generateArmFreeappDebugProto', + 'generateArmRetailappDebugAndroidTestProto', + 'generateArmRetailappDebugUnitTestProto', + 'generateArmRetailappDebugProto', + 'generateX86FreeappDebugAndroidTestProto', + 'generateX86FreeappDebugUnitTestProto', + 'generateX86FreeappDebugProto', + 'generateX86RetailappDebugAndroidTestProto', + 'generateX86RetailappDebugUnitTestProto', + 'generateX86RetailappDebugProto' + ] as Set == protobuf.generateProtoTasks.ofBuildType('debug').collect({ it.name }) as Set + + assert [ + 'generateArmFreeappReleaseProto', + 'generateArmFreeappReleaseUnitTestProto', + 'generateArmRetailappReleaseProto', + 'generateArmRetailappReleaseUnitTestProto', + 'generateX86FreeappReleaseProto', + 'generateX86FreeappReleaseUnitTestProto', + 'generateX86RetailappReleaseProto', + 'generateX86RetailappReleaseUnitTestProto', + ] as Set == protobuf.generateProtoTasks.ofBuildType('release').collect({ it.name }) as Set + + assert ['generateX86FreeappDebugAndroidTestProto'] as Set == + protobuf.generateProtoTasks.ofVariant('x86FreeappDebugAndroidTest').collect({ it.name }) as Set + + // "androidTest" sourceSet is not a flavor + assert [] as Set == protobuf.generateProtoTasks.ofFlavor('androidTest').collect({ it.name }) as Set + + // "unitTest" sourceset is not a flavor + assert [] as Set == protobuf.generateProtoTasks.ofFlavor('unitTest').collect({ it.name }) as Set + + android.applicationVariants.each { variant -> + assertJavaCompileHasProtoGeneratedDir(variant, ['javalite', 'grpc']) + } + + android.testVariants.each { variant -> + assertJavaCompileHasProtoGeneratedDir(variant, ['javalite']) + } + } +} diff --git a/testProjectAndroidDependentBase/lib/protos.jar b/testProjectAndroidDependentBase/lib/protos.jar new file mode 100644 index 00000000..18f117db Binary files /dev/null and b/testProjectAndroidDependentBase/lib/protos.jar differ diff --git a/testProjectAndroidDependentBase/proguard-rules.pro b/testProjectAndroidDependentBase/proguard-rules.pro new file mode 100644 index 00000000..c63aefd2 --- /dev/null +++ b/testProjectAndroidDependentBase/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/thagikura/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build_base.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/testProjectAndroidDependentBase/src/androidTest/proto/sample.proto b/testProjectAndroidDependentBase/src/androidTest/proto/sample.proto new file mode 100644 index 00000000..f65c60f2 --- /dev/null +++ b/testProjectAndroidDependentBase/src/androidTest/proto/sample.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_package = "com.example.tutorial"; +option java_outer_classname = "OuterSample"; +option java_multiple_files = true; + +// From the main sourceSet +import "helloworld.proto"; +// From tested variant's dependency testProjectAndroidLibrary: src/proto +import "messages.proto"; + +message Msg { + string foo = 1; + SecondMsg blah = 2; +} + +message SecondMsg { + int32 blah = 1; + // Uses message type from helloworld.proto + helloworld.HelloReply reply = 2; + // Uses message type from messages.proto + grpc.testing.SimpleContext context = 3; +} diff --git a/testProjectAndroidDependentBase/src/main/AndroidManifest.xml b/testProjectAndroidDependentBase/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8c40f116 --- /dev/null +++ b/testProjectAndroidDependentBase/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/testProjectAndroidDependentBase/src/main/proto/helloworld.proto b/testProjectAndroidDependentBase/src/main/proto/helloworld.proto new file mode 100644 index 00000000..ad69b84c --- /dev/null +++ b/testProjectAndroidDependentBase/src/main/proto/helloworld.proto @@ -0,0 +1,55 @@ +// 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 = "proto3"; + +// From testProjectAndroidLibrary: src/proto +import "messages.proto"; + +option java_package = "io.grpc.helloworldexample"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; + // Uses message type from messages.proto + grpc.testing.SimpleContext context = 2; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/testProjectAndroidDependentBase/src/main/res/layout/activity_helloworld.xml b/testProjectAndroidDependentBase/src/main/res/layout/activity_helloworld.xml new file mode 100644 index 00000000..00ca04ce --- /dev/null +++ b/testProjectAndroidDependentBase/src/main/res/layout/activity_helloworld.xml @@ -0,0 +1,54 @@ + + + + + + + + + + +