Adam Murdoch wrote:
John Murph wrote:
On Fri, Nov 20, 2009 at 3:53 PM, Adam Murdoch <[email protected]
<mailto:[email protected]>> 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)
BTW, I'm not suggesting you do all this work, as you really only need
the equivalent of configure(object, scriptPath). I'm just trying to
figure out a direction for this stuff.
--
Adam Murdoch
Gradle Developer
http://www.gradle.org