Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use separate configuration for extracting included proto from dependencies #366

Merged
Merged
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
voidzcy marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 33 additions & 7 deletions src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -54,6 +56,7 @@ class ProtobufPlugin implements Plugin<Project> {
// any one of these plugins should be sufficient to proceed with applying this plugin
private static final List<String> PREREQ_PLUGIN_OPTIONS = [
'java',
'java-library',
'com.android.application',
'com.android.feature',
'com.android.library',
Expand Down Expand Up @@ -135,7 +138,7 @@ class ProtobufPlugin implements Plugin<Project> {

addSourceSetExtensions()
getSourceSets().all { sourceSet ->
createConfiguration(sourceSet.name)
createConfigurations(sourceSet.name)
}
project.afterEvaluate {
// The Android variants are only available at this point.
Expand All @@ -158,18 +161,41 @@ class ProtobufPlugin implements Plugin<Project> {
}

/**
* 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))
}
}

/**
Expand Down Expand Up @@ -364,7 +390,7 @@ class ProtobufPlugin implements Plugin<Project> {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
voidzcy marked this conversation as resolved.
Show resolved Hide resolved
.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"
Expand Down
31 changes: 31 additions & 0 deletions testProjectDependentApp/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions testProjectDependentApp/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'testProjectDependentApp'
14 changes: 14 additions & 0 deletions testProjectDependentApp/src/main/proto/dependent.proto
Original file line number Diff line number Diff line change
@@ -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;
}
19 changes: 19 additions & 0 deletions testProjectDependentApp/src/test/java/DependentTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
8 changes: 8 additions & 0 deletions testProjectDependentApp/src/test/proto/dependent2.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto3";

// From the 'main' sourceSet
import "dependent.proto";

message TestWrapperMessage {
dependent.WrapperMessage m = 1;
}
5 changes: 5 additions & 0 deletions testProjectJavaLibrary/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
1 change: 1 addition & 0 deletions testProjectJavaLibrary/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'testProjectJavaLibrary'
30 changes: 30 additions & 0 deletions testProjectJavaLibrary/src/main/java/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import com.google.protobuf.MessageLite;

import java.util.ArrayList;
import java.util.List;

public class Foo {
public static List<MessageLite> getDefaultInstances() {
ArrayList<MessageLite> list = new ArrayList<MessageLite>();
// 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;
}
}
25 changes: 25 additions & 0 deletions testProjectJavaLibrary/src/test/java/FooTest.java
Original file line number Diff line number Diff line change
@@ -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));
}
}