John Murph wrote:
On Fri, Nov 20, 2009 at 3:53 PM, Adam Murdoch <a...@rubygrapefruit.net
<mailto:a...@rubygrapefruit.net>> wrote:
First is TestListener. This (if you can recall that far back)
was the reason I did the listener manager changes. We need to
have an init script that can register a listener that gets
testing feedback from JUnit. This will allow us to improve
our Gradle TeamCity runner so that it gets better (and real
time) test results. I'm thinking along the lines of "if a
listener of a certain type is registered with the listener
manager, then give a remoting listener to JUnit that sends
messages to an in-process peer that forwards the messages on
to the registered listener." Do that make sense?
This would be nice to have.
I suspect most of the work has been done already in the NativeTest
task, so that the events are available just below the surface of
the NativeTest task. We'd need to add some methods for adding a
TestListener to NativeTest, with an impl of that used a
ListenerManager to create its broadcaster. Then, the TestListener
events would be available in the init script.
I don't think we should bother with trying to fit this into the
AntTest task as well, and should instead focus on getting the
NativeTest stuff ready.
OK, I'll have a look at the NativeTest stuff and see what I can figure
out. This is the change that is most pressing for us as we currently
don't have a workaround, so hopefully I can figure something out.
If you need something really soon, it might be better to try this with
AntTest. Will be a little while before we make NativeTest the official
test task. It's up to you. We can figure a way to merge the two
implementations if you decide to go with AntTest.
Second is extension support. This is the idea that a script
can ask for some external script to be executed against a
given delegate object. Doing this through Gradle would allow
for all the normal caching stuff to be reused. We need to be
able to run such scripts from the settings.gradle (and from
inside build scripts, but that is obvious). We would use this
feature to define our own custom domain objects that are
configured via external script files. I'm not sure what the
interface to Gradle should be, a method on Gradle called
"executeExtension" that takes a file and a delegate object?
Or maybe a "getExtensionSupport" method that returns an
ExtensionSupport class that provides an execute method? What
stateful information could ExtensionSupport have that
justifies a separate class? Or maybe the justification is
that it provides more than one method, like what? I'm not
sure what you guys might be thinking about this feature as we
have not discussed it much before.
Why do you need to do the configuration using external scripts?
What do they configure? I want to get a feel for the use case
before we discuss a solution.
We showed and explained this to Hans when he was here. It's a bit
difficult to explain in an email, but let me try. We have the concept
of multiple modules being put together to form products, and multiple
products forming installs and multiple installs forming
distributions. Each of these three levels (products, install, dists)
have logic and therefore need configuration of that logic. For
example, what modules does this product need? What is the name of the
product? What native launchers does the product need, and what are
it's settings (Xmx, etc.)? Similarly, installs and distributions have
configurations as well.
To support this, we have three domain objects in our buildSrc that
hold this information. We also have various "xxx_product.gradle" and
"xxx_install.gradle" and "xxx_dist.gradle" files in our project that
are used depending on what is being built. These special .gradle
files are executed on the associated domain object by our
settings.gradle. This allows our developers to only say what's
important to them, and not have to see our giant build.gradle scripts
(or our buildSrc module). What our developers actually think is
Gradle is all these special scripts, they never see "true" Gradle.
The thing is that I want these special Gradle files to be very similar
in treatment to normal Gradle files. All I'm really doing is
extending the concepts that Gradle knows about into my own custom
domain. (This ability is at the heart of what we love so much about
Gradle.) Unfortunately, to do this right now I had to copy-paste some
Gradle implementation code into our buildSrc (the stuff than runs a
script, knows when/where to cache it, knows when to ignore the cache
and rebuild it, knows how to "delegate" to an object, etc.) Every
time you guys touch that stuff it breaks us, because we are using
internal APIs of Gradle. The idea of this feature is to make an easy
to use public API so that we don't get broken so much. It will also
make it easier for others to do this same thing.
As a matter of fact, we've talked to Hans about our idea that large
projects should generally be structured this way. I don't want to go
into details right now (I intended to write up something more complete
eventually), but most large projects have lots of concepts centered
around what they are doing and the way they work. Formalizing those
concepts into "extensions" to Gradle can product a much easier to use
build engine that is designed for them. It's why I've always agreed
that Gradle should be a toolkit. One that works out-of-the-box for
simple projects, and one that lets large projects implement their own
custom build engine on top of it. Part of the power of Gradle is that
it can be both.
Thanks for writing this up. I agree 100% with you on the goal. I think
this is an excellent pattern for large builds, where the 'build scripts'
describe what to build in some high-level build language (custom or
otherwise), and some build logic in the root script/buildSrc/some plugin
takes care of how to build those things.
I have a different suggestion for how we solve this problem. To me, it
feels like we want some unified theory of build logic composition. That
is, these all feel like the same thing:
Things we do now:
- configure a Project using a plugin
- configure a Project using a build script
- configure a Gradle instance using an init script
- configure a Settings instance using a settings script
Things we want to be able to do:
- implement a plugin using a script
- configure a Project using an arbitrary script
- configure a Gradle instance using a plugin
- configure a SourceSet/Configuration/RepositoryContainer/etc using a
plugin/extension
- configure an arbitrary domain object using an arbitrary script
- auto-configure a Project using scripts or plugins or java source in a
well-know location (such as buildSrc)
- ...
So, I think we could get rid of Project.usePlugin(), and replace it with
a general method. Let's call it 'configure' for now. In any script you
would be able to do:
configure(object) { a closure }
- this is a generalisation of Project.configure(), available from any
script
configure(object, 'path/to/a/script.gradle')
- executes the given script with the given object as its delegate
configure(object, SomePluginClass)
- configures the given object using the given plugin, i.e. a
generalisation of Project.usePlugin()
The target object would default to the delegate of the current script,
ie in a build script the target object is a Project:
configure('path/to/a/script.gradle')
configure(SomePluginClass)
We could also add some additional ways to locate the build logic artifact:
configure(object, 'path/to/a/plugin.jar')
- locates the plugin(s) in the provided jar and applies them to the
given object
configure(object, 'http://some.corporate.server/global-config.gradle')
- uses the artifact (script or jar) from the given url
configure(object, 'path/to/some/directory')
- treats the directory as a Gradle project which produces a plugin
jar, ie exactly the same as buildSrc
configure(object) {
dependency 'some.org:sharedConfig:1.4+'
}
- uses the artifacts (scripts or jars) from the given dependency.
Then, our existing behaviours can be implemented in terms of configure()
- init script -> configure(gradle, initScriptPath)
- settings script -> configure(settings, settingsScriptPath)
- build script -> configure(project, buildScriptPath)
- buildSrc -> configure(gradle, "$rootDir/buildSrc")
- usePlugin('name') -> configure(project, getClassForName('name'))
- usePlugin(pluginClass) -> configure(project, pluginClass)
--
Adam Murdoch
Gradle Developer
http://www.gradle.org