diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e8289e81..086b56fe 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-bin.zip diff --git a/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy b/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy index 036eedd1..c07b62bf 100644 --- a/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy +++ b/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy @@ -35,7 +35,9 @@ import org.gradle.api.GradleException 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 import org.gradle.api.internal.file.DefaultSourceDirectorySet @@ -54,6 +56,7 @@ class ProtobufPlugin implements Plugin { // any one of these plugins should be sufficient to proceed with applying this plugin private static final List PREREQ_PLUGIN_OPTIONS = [ 'java', + 'java-library', 'com.android.application', 'com.android.feature', 'com.android.library', @@ -135,7 +138,7 @@ class ProtobufPlugin implements Plugin { addSourceSetExtensions() getSourceSets().all { sourceSet -> - createConfiguration(sourceSet.name) + createConfigurations(sourceSet.name) } project.afterEvaluate { // The Android variants are only available at this point. @@ -158,18 +161,41 @@ class ProtobufPlugin implements Plugin { } /** - * Creates a configuration if necessary for a source set so that the build + * Creates configurations if necessary for a source set so that the build * author can configure dependencies for it. */ - private void createConfiguration(String sourceSetName) { - String configName = Utils.getConfigName(sourceSetName, 'protobuf') - if (project.configurations.findByName(configName) == null) { - project.configurations.create(configName) { + private void createConfigurations(String sourceSetName) { + String protobufConfigName = Utils.getConfigName(sourceSetName, 'protobuf') + if (project.configurations.findByName(protobufConfigName) == null) { + project.configurations.create(protobufConfigName) { visible = false transitive = true extendsFrom = [] } } + + // Create a 'compileProtoPath' configuration that extends compilation configurations + // as a bucket of dependencies with resources attribute. This works around 'java-library' + // plugin not exposing resources to consumers for compilation. + // Some Android sourceSets (more precisely, variants) do not have compilation configurations, + // they do not contain compilation dependencies, so they would not depend on any upstream + // proto files. + String compileProtoConfigName = Utils.getConfigName(sourceSetName, 'compileProtoPath') + Configuration compileConfig = + project.configurations.findByName(Utils.getConfigName(sourceSetName, 'compileOnly')) + Configuration implementationConfig = + project.configurations.findByName(Utils.getConfigName(sourceSetName, 'implementation')) + if (compileConfig && implementationConfig && + project.configurations.findByName(compileProtoConfigName) == null) { + project.configurations.create(compileProtoConfigName) { + visible = false + transitive = true + extendsFrom = [compileConfig, implementationConfig] + canBeConsumed = false + }.getAttributes().attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + project.getObjects().named(LibraryElements, LibraryElements.RESOURCES)) + } } /** @@ -364,7 +390,7 @@ class ProtobufPlugin implements Plugin { description = "Extracts proto files from compile dependencies for includes" destDir = getExtractedIncludeProtosDir(sourceSetOrVariantName) as File inputFiles.from(compileClasspathConfiguration - ?: project.configurations[Utils.getConfigName(sourceSetOrVariantName, 'compile')]) + ?: project.configurations[Utils.getConfigName(sourceSetOrVariantName, 'compileProtoPath')]) // TL; DR: Make protos in 'test' sourceSet able to import protos from the 'main' // sourceSet. Sub-configurations, e.g., 'testCompile' that extends 'compile', don't diff --git a/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy b/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy index bcfcc818..bf7127b7 100644 --- a/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy +++ b/src/test/groovy/com/google/protobuf/gradle/ProtobufJavaPluginTest.groovy @@ -200,6 +200,62 @@ class ProtobufJavaPluginTest extends Specification { gradleVersion << GRADLE_VERSIONS } + @Unroll + void "testProjectJavaLibrary should be successfully executed (java-only as a library) [gradle #gradleVersion]"() { + given: "project from testProjectJavaLibrary" + File projectDir = ProtobufPluginTestHelper.projectBuilder('testProjectJavaLibrary') + .copyDirs('testProjectBase', 'testProjectJavaLibrary') + .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)) + .withDebug(true) + .build() + + then: "it succeed" + result.task(":build").outcome == TaskOutcome.SUCCESS + ProtobufPluginTestHelper.verifyProjectDir(projectDir) + + where: + gradleVersion << GRADLE_VERSIONS + } + + @Unroll + void "testProjectDependentApp should be successfully executed [gradle #gradleVersion]"() { + given: "project from testProject & testProjectDependent" + File testProjectStaging = ProtobufPluginTestHelper.projectBuilder('testProjectJavaLibrary') + .copyDirs('testProjectBase', 'testProjectJavaLibrary') + .build() + File testProjectDependentStaging = ProtobufPluginTestHelper.projectBuilder('testProjectDependentApp') + .copyDirs('testProjectDependentApp') + .build() + + File mainProjectDir = ProtobufPluginTestHelper.projectBuilder('testProjectDependentAppMain') + .copySubProjects(testProjectStaging, testProjectDependentStaging) + .build() + + when: "build is invoked" + BuildResult result = GradleRunner.create() + .withProjectDir(mainProjectDir) + .withArguments('testProjectDependentApp:build', '--stacktrace') + .withGradleVersion(gradleVersion) + .forwardStdOutput(new OutputStreamWriter(System.out)) + .forwardStdError(new OutputStreamWriter(System.err)) + .withDebug(true) + .build() + + then: "it succeed" + result.task(":testProjectDependentApp:build").outcome == TaskOutcome.SUCCESS + + where: + gradleVersion << GRADLE_VERSIONS + } + @Unroll void "testProjectCustomProtoDir should be successfully executed [gradle #gradleVersion]"() { given: "project from testProjectCustomProtoDir" diff --git a/testProjectDependentApp/build.gradle b/testProjectDependentApp/build.gradle new file mode 100644 index 00000000..0ea9434c --- /dev/null +++ b/testProjectDependentApp/build.gradle @@ -0,0 +1,31 @@ +// A project that depends on another project and a published artifact, both of +// which include proto files. The included proto files are added to the +// --proto_path argument of protoc, so that the protos from this project can +// import them. However, these imported proto files will not be compiled in +// this project, since they have already been compiled in their own projects. + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' + +repositories { + maven { url "https://plugins.gradle.org/m2/" } +} + +dependencies { + implementation 'com.google.protobuf:protobuf-java:3.0.0' + implementation project(':testProjectJavaLibrary') + + testImplementation 'junit:junit:4.12' +} + +protobuf.protoc { + artifact = 'com.google.protobuf:protoc:3.0.0' +} + +test.doLast { + // This project has compiled only one proto file, despite that it imports + // other proto files from dependencies. + def generatedFiles = project.fileTree(protobuf.generatedFilesBaseDir + "/main") + File onlyGeneratedFile = generatedFiles.singleFile + assert 'Dependent.java' == onlyGeneratedFile.name +} diff --git a/testProjectDependentApp/settings.gradle b/testProjectDependentApp/settings.gradle new file mode 100644 index 00000000..55032de8 --- /dev/null +++ b/testProjectDependentApp/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'testProjectDependentApp' diff --git a/testProjectDependentApp/src/main/proto/dependent.proto b/testProjectDependentApp/src/main/proto/dependent.proto new file mode 100644 index 00000000..f6c6317f --- /dev/null +++ b/testProjectDependentApp/src/main/proto/dependent.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package dependent; + +// From testProject/src/main/proto +import "ws/antonov/protobuf/test/test.proto"; + +// From protobuf-java artifact +import "google/protobuf/any.proto"; + +message WrapperMessage { + ws.antonov.protobuf.test.Item item = 1; + google.protobuf.Any any = 2; +} diff --git a/testProjectDependentApp/src/test/java/DependentTest.java b/testProjectDependentApp/src/test/java/DependentTest.java new file mode 100644 index 00000000..5b45aad0 --- /dev/null +++ b/testProjectDependentApp/src/test/java/DependentTest.java @@ -0,0 +1,19 @@ +import static org.junit.Assert.assertSame; + +import org.junit.Test; + +public class DependentTest { + + @Test public void testProtos() { + dependent.Dependent.WrapperMessage message = + dependent.Dependent.WrapperMessage.newBuilder() + .setItem(ws.antonov.protobuf.test.Test.Item.getDefaultInstance()) + .setAny(com.google.protobuf.Any.getDefaultInstance()) + .build(); + assertSame(ws.antonov.protobuf.test.Test.Item.getDefaultInstance(), + message.getItem()); + Dependent2.TestWrapperMessage testMessage = + Dependent2.TestWrapperMessage.newBuilder() + .setM(message).build(); + } +} diff --git a/testProjectDependentApp/src/test/proto/dependent2.proto b/testProjectDependentApp/src/test/proto/dependent2.proto new file mode 100644 index 00000000..4e19d4ba --- /dev/null +++ b/testProjectDependentApp/src/test/proto/dependent2.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +// From the 'main' sourceSet +import "dependent.proto"; + +message TestWrapperMessage { + dependent.WrapperMessage m = 1; +} \ No newline at end of file diff --git a/testProjectJavaLibrary/build.gradle b/testProjectJavaLibrary/build.gradle new file mode 100644 index 00000000..d3cc0005 --- /dev/null +++ b/testProjectJavaLibrary/build.gradle @@ -0,0 +1,5 @@ +// This build is not a complete project, but is used to generate a project. +// See: ProtobufPluginTestHelper.groovy +apply plugin: 'java-library' +apply plugin: 'idea' +apply from: 'build_base.gradle' diff --git a/testProjectJavaLibrary/settings.gradle b/testProjectJavaLibrary/settings.gradle new file mode 100644 index 00000000..cd92cb49 --- /dev/null +++ b/testProjectJavaLibrary/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'testProjectJavaLibrary' diff --git a/testProjectJavaLibrary/src/main/java/Foo.java b/testProjectJavaLibrary/src/main/java/Foo.java new file mode 100644 index 00000000..3a7e2235 --- /dev/null +++ b/testProjectJavaLibrary/src/main/java/Foo.java @@ -0,0 +1,30 @@ +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.Test1.Test1Msg.getDefaultInstance()); + // from ext/ext1/test1.proto + list.add(ext1.Ext1Test1.Ext1Test1Msg.getDefaultInstance()); + // from ext/test2.proto + list.add(test2.Test2.Test2Msg.getDefaultInstance()); + return list; + } +} diff --git a/testProjectJavaLibrary/src/test/java/FooTest.java b/testProjectJavaLibrary/src/test/java/FooTest.java new file mode 100644 index 00000000..ec322869 --- /dev/null +++ b/testProjectJavaLibrary/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(12, 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)); + } +}