-
Notifications
You must be signed in to change notification settings - Fork 274
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
Kotlin dsl support #262
Kotlin dsl support #262
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here (e.g. What to do if you already signed the CLAIndividual signers
Corporate signers
|
I signed it! |
dummy comment to try and unwedge CLA bot |
I like this idea of adding extensions functions, and then changing the groovy API only if extensions can not solve the problem. Taking a compile only dependency on kotlin is fine. Would it be possible to add a test case that shows how a kotlin DSL build file looks? https://github.com/google/protobuf-gradle-plugin/pull/205/files may be a possible starting point. |
exampleProject/build.gradle.kts
Outdated
// Extra proto source files besides the ones residing under | ||
// "src/main". | ||
"protobuf"(files("lib/protos.tar.gz")) | ||
"protobuf"(fileTree("ext/")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using string configuration access but i should probably look into adding an extension scoped to the dependency handler
build.gradle
Outdated
// Temporary work around to get compilation working during development | ||
// Considering moving to a separate module | ||
// https://discuss.gradle.org/t/kotlin-groovy-and-java-compilation/14903/10 | ||
compileGroovy.dependsOn = compileGroovy.taskDependencies.mutableValues - 'compileJava' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really ugly so Im hoping to move it to a seperate module. Im just not sure where it should live in the project.
@zpencer I added an example of a kotlin dsl script based on the original example. It is still in the example directory while I try to figure out how to wire up a test project for this that would work with the build process. Im open to suggestions on how to approach it. |
This is a huge improvement over the currently required syntax! I'm not sure if this question is out of scope (i.e. too complex of a change to the underlying plugin). I only bring it up since the PR requested feedback: Is there a way to reduce the amount of boilerplate? All the developer is really trying to convey here is, "I want to use protobuf with grpc and javalite" which is probably an extremely common usecase for
or maybe:
|
Performed some clean up and pushed the updated example project. I think there might be a way to implement the shorter syntax for options in the task.plugin config block. ie
Im just not sure having options configurable in the protobuf.plugins block is a good idea since the assumed config would be implicitly applied to all source sets. |
CLAs look good, thanks! |
@zpencer Im not sure if my build is failing in travis to due master failing? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RE failing CI: it looks like this test is failing, but in master it is passing: ./gradlew test --tests com.google.protobuf.gradle.ProtobufAndroidPluginTest --stacktrace"
I'm still learning Kotlin, so please excuse my basic questions about syntax that is hard to learn about by Googling :)
|
||
fun ProtobufConfigurator.plugins(closure: NamedDomainObjectContainerScope<ExecutableLocator>.() -> Unit) { | ||
plugins(delegateClosureOf<NamedDomainObjectContainer<ExecutableLocator>> { | ||
this{ this.closure() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this { ... }
mean in Kotlin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah looks gross and after doing some additional digging I updated with a cleaner usage. This implementation was a port from the existing example provided here #219 (comment)
Back to your original question. There are a couple of Kotlin features at work here that make up this statement.
- Lambda with Receiver
- The
closureOf
method takes a lambda and thethis
property in that lambda is the argument passed in.
- The
- Invoke Operator Overloading
- The gradle kotlin dsl provides and extension function overloading the invoke operator for objects with type
NamedDomainObjectContainer
- This function allows us to apply our configuration to the receiver
- The gradle kotlin dsl provides and extension function overloading the invoke operator for objects with type
- Lambda as Last Param
- If the last argument to a function is a lambda then the parenthesis can be omitted.
In the latest commit, this code has been refactored to something thats a little more readable, after looking into what exactly the method needed to achieve.
fun GenerateProtoTask.plugins(closure: NamedDomainObjectContainerScope<GenerateProtoTask.PluginOptions>.()-> Unit) {
plugins(closureOf<NamedDomainObjectContainer<GenerateProtoTask.PluginOptions>> {
this(closure)
})
}
/** | ||
* The 'testProtobuf' configuration. | ||
*/ | ||
val ConfigurationContainer.testProtobuf: Configuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be more general? It looks like this gives special treatment to the main sourceset (protobuf
) and the test sourceset (testProtobuf
) but won't handle user created sourcesets like this:
sourceSets {
main {
}
foo {
java {
srcDirs 'src/foo/'
}
}
}
dependencies {
fooProtobuf files("/tmp/protos-test.tar.gz")
}
createConfiguration
is where the plugin creates the xxxxProtobuf
configurations in the project:
https://github.com/google/protobuf-gradle-plugin/blob/master/src/main/groovy/com/google/protobuf/gradle/ProtobufPlugin.groovy#L154
Maybe there is some way this extension can plug into them in a general way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I cant dynamically provide the dependency configuration methods, but I can try providing a nicer mechanism.
You can try it out in the latest commit.
dependencies{
protobuf["sample"]("com.google.protobuf:protobuf-java:3.6.1")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This solution has some pros and cons so I wanted to discuss them with you when possible. Ill add a full explanation in a few.
} | ||
|
||
fun ProtobufConfigurator.protoc(closure: ExecutableLocator.() -> Unit) { | ||
protoc(delegateClosureOf(closure)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still trying to wrap my head around delegateClosureOf
vs closureOf
(https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Closures.md). Is delegateClosureOf
meant for closures that make use of the closure delegate from groovy ? As far as I can tell this plugin does not use this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is a by-product of my ignorance of groovy. I updated to use closureOf
}) | ||
} | ||
|
||
fun ProtobufConfigurator.GenerateProtoTaskCollection.ofSourceSet(sourceSet: String): Collection<GenerateProtoTask> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't the groovy version of these functions be called directly? Maybe a comment would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a comment to the source but Ill post it here as well.
The method generatorProtoTasks
applies the supplied closure to an
instance of GenerateProtoTaskCollection
.
Since JavaGenerateProtoTaskCollection
and 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so the problem is the dynamic typing of GenerateProtoTaskCollection
, since we don't know whether it is JavaGenerateProtoTaskCollection
or AndroidGenerateProtoTaskCollection
, kotlin's static typing means we can call neither's methods.
What do you think about instead adding two extension methods to GenerateProtoTaskCollection
like asJavaTasks
returning the same object casted as JavaGenerateProtoTaskCollection
(or an empty collection if not applicable), and asAndroidTasks
casting as AndroidGenerateProtoTaskCollection
or an emtpy collection. That way, we minimize the amount of extension code that must be kept in sync with the groovy DSL. Since we expect kotlin DSL users to be in an IDE, this should be relatively discoverable code auto completion.
Awesome Ill clean up ProtobufDependencyConfiguration.kt. In regards to writing up tests, Im still trying to get a test project working with the existing test classes. There isnt much available in terms of resources for testing kotlin dsl projects using the gradle runner so its been a slow process. Just wanted to give a heads up. |
Also there are extensions built into the kotlin gradle dsl that clean up the custom source set syntax a little further
|
|
||
// This extension is not auto generated when we apply the plugin using | ||
// apply(plugin = "com.google.protobuf") | ||
val Project.protobuf: ProtobufConvention get() = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is another extension that relies on the plugin being applied in the plugins block, should we add it to ProtobufConfiguratorExts.kt
as well? It would normalize different plugin usages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify what this block is doing? Also it looks like HasConvention is not a part of the public gradle API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This extension is used to get the plugin convention from the project, which was needed for Line 106. I didnt realize that HasConvention
isnt part of the public api. The code itself I copied from the dynamically generated code file that gradle creates when you use the plugins block. I can see if there's a way around casting to HasConvention
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleaned up the usage to the following
val Project.protobuf: ProtobufConvention get() =
this.convention.getPlugin(ProtobufConvention::class)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can this workaround be moved nearer to apply(plugin = "com.google.protobuf")
?
Finally got tests added. PR is ready for a full review whenever you get a chance. |
|
||
// This extension is not auto generated when we apply the plugin using | ||
// apply(plugin = "com.google.protobuf") | ||
val Project.protobuf: ProtobufConvention get() = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify what this block is doing? Also it looks like HasConvention is not a part of the public gradle API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for contributing this PR! I added a few small remaining concerns.
@@ -1,5 +1,5 @@ | |||
distributionBase=GRADLE_USER_HOME | |||
distributionPath=wrapper/dists | |||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undo this change
|
||
// This extension is not auto generated when we apply the plugin using | ||
// apply(plugin = "com.google.protobuf") | ||
val Project.protobuf: ProtobufConvention get() = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can this workaround be moved nearer to apply(plugin = "com.google.protobuf")
?
@zhangkun83 would you like to take a look before I merge? |
I took a glance and the DSL looks pretty good. I trust @zpencer's judgement. Good job guys. |
Great! Let's merge it so our users can start using this experimental feature. |
Really happy to see this get merged. @zpencer Thanks again for all your help. When I get some free time Im hoping to add more documentation to the new code and possibly some additional tests. |
This is an experimental feature. See `examples/exampleKotlinDslProject` for an example.
0.8.7 is released! It's already live on plugins.gradle.org. It may take a few hours for it to show up in Maven Central. |
This is not ready to merge and is still a work in progress but I wanted to open the pr a little early to get some feedback on the proposed approach to resolving #219.