Showing posts with label gradle. Show all posts
Showing posts with label gradle. Show all posts

Tuesday, May 21, 2013

Call all the things!

At Gradle's core is a really nice set of Collection classes, which I wish were exposed on their own. The central feature is the idea around the all(Closure action) method, which says "run this Closure on every object", including ones added later. This is defined in DomainObjectCollection and it's leveraged by a few other methods. Specifically withType(Class<S> type, Closure configureClosure)and it's generalization matching(Closure spec). While they're very similar to Groovy's (and other languages) findAll or each, the extra temporal dimension makes it much more power for an API designer. Typically an API would provide a Collection which delegates to another collection while putting some triggers in place when something is added. With DomainObjectCollection, the user could create the collection and even prep it before passing it into an API call. It also allows multiple people to essentially register listeners, sorta like a property listener. In the following example, I configure other parts of the model based on what they all, for any value added or which will be added.

Loading ....
Another useful generic Collection is NamedDomainObjectCollection<T> (with its supporting implementations). These allow a collection to be built up with the assumption that each has a unique name, and it's really not too far from a Map with the key being the name. The magic comes from being able to build up an instance with methodMissing calls. The resulting calls look really slick and screams "DSL", I wish I had it for every Groovy DSL I write. As you can see in the next sample is a minimal way to create a list, which also has the ability to be configured for each item.
Loading ....

Tuesday, April 30, 2013

beforeEvaluate

In Gradle, I've come upon a development pattern. It usually follows this pattern:
  1. Write plugin
  2. Write Extension, so that the user can customize the behavior of the plugin
  3. Wish I could use Convention Mappings, but then resist since they're not public
  4. Realize that I can't look at the Extension until afterEvaluate, which is after the user's code has had a chance to set values on the Extension
  5. Put all my plugins logic in project.afterEvaluate
  6. Swear at any @DeferredConfigurable plugins
  7. Fight the ordering of my plugins
When I say @DeferredConfigurable, I'm pretty specifically looking at the PublishingPlugin's DefaultPublishingExtension, which will throw an exception if you try to configure it after it's been configured. Honestly this make sense, but it doesn't make it any easier to use. The publish plugin adds it's own afterEvaluate which forces the extension to be configured, since it needs to create tasks before the task graph is built. 

This means that if I have a Plugin that uses an Extension which is going alter a Software Component, it HAS to run before the publish plugin is applied, so that my afterEvaluate is run before the PublishingExtension's afterEvaluate is, keeping in mind that afterEvaluate is evaluated in insertion order. But that's impossible since I can't configure the PublishingExtension until after the PublishingPlugin has added the PublishingExtension to the project. I think I've also seen issues where the IvyPublication is resolved before I can add in my generated/imported dependencies, which is another reason I need to run before the publish plugin's afterEvaluate.

Technically the publish plugin is @Incubating, so I'm not faulting the Gradle guys and don't even expect this to be a bug in its own right, since they're still figuring it out. In my cause, I require the ability to modify the module descriptor, and that's only really possible with the publish plugin, so I'll live with it. Though I'm starting to get the feeling that there's some fundamental ordering issues cropping up, which will eventually grow out of control.

My solution is implement a beforeAfterEvaluate method that lets me jump to the front of the dispatcher LinkedHashMap. Caveat, there's lot of internal classes being used, so I know that it can break in future versions of Gradle.


Loading ....

This doesn't answer the general ordering issue, but it can unblock a stalemate in the order of plugins. It's worth mentioning that Convention Mappings aren't the solution either, they're just an opportunistic lazy evaluation that "typically" works in your favor.

Tuesday, April 23, 2013

Don't dare prompt me


At Netflix, we've been trying to come up with an effective way to distribute the Gradle wrapper (more on that in another post), and the solution we're going for is to check it into a git repo, which can then be cloned locally or referenced as a submodule for CI builds. So, when we're constructing our wrapper and pointing it to a custom distribution of Gradle we need to perform git commands. Luckily Andrew Oberstar created a eponymous Gradle plugin called gradle-git, which wraps JGit. We already started to use it to publish javadocs to gh-pages in the Netflix OSS projects. In this case, I started to use it to check the new wrapper into a repo, only to find out that I was being prompted for my passphrase.


It's easy to forget that one's SSH private key has a passphrase on it, because most apps indirectly ask ssh-agent or Pageant for the key. (Note: I almost forgot my passphrase, please go and check that you still remember yours.) When I say most apps, I mean every possible app except JGit.  When JGit was written it appears that it was abstracting out the underlying implementation of SSH, but generally uses JSch to keep everything in the JVM. The problem is the JGit seems to use some defaults that bring a Swing UI to prompt a user for their passphrase every time it instantiates a session. This might make sense in an IDE, but in a CI server that is ridiculous.


A quick Google search shows that the creators of JSch figured this out and wrote jsch-agent-proxy. Great, all I have to do is wire that in and prest-o change-o, I'll no longer be prompted. Nope. I searched high and low and couldn't find an example of someone configuring jsch-agent-proxy with JGit. Some more research showed that JGit in all it's lovely abstraction layers hides the parts of JSch that I need to plug in jsch-agent-proxy.

Here's my implementation that connects the JSch under JGit to use jsch-agent-proxy:

Loading ....

Most of this code comes from jsch-agent-proxy's JSchWithAgentProxy example. jsch-agent-proxy has an impressive set of implementations. It can use Unix Sockets or JNA, or talk to ssh-agent or Pageant. Given how easy the JGit API is to use, I expect some interesting Java-based utilities to come out in the next year.