John Murph wrote:
On Sun, Aug 2, 2009 at 1:21 AM, Adam Murdoch <[email protected]
<mailto:[email protected]>> wrote:
John Murph wrote:
If an external tool (such as an IDE or CI server) wishes to
integrate with Gradle, they will need some way to add
listeners and perhaps customize other aspects of Gradle. The
approach that Ant uses for this is to pass a command line
option (-listener <classname>) followed by a fully qualified
class name. Ant then uses reflection to instantiate this
class and registers it as a build listener. This approach is
simple, but very limited. I wish to propose a more
complicated, but also more powerful, approach for Gradle.
I think Gradle should provide a new command line option
(--init-script <path-to-script>). Prior to even
finding/loading the settings.gradle file, this script (if
specified) would be executed.
Should we default the init script to something under ~/.gradle,
maybe ~/.gradle/init.gradle or similar?
I had thought you might want such a thing. While I'm not a fan of
these "find a script and run it" ideas, I do understand the
motivation. My intent was for tools to use, in which case they will
want to specify a script to run (say, to point to a script that ships
as part of the tool). I wonder if it might not be good to allow
both. Basically, run a well known init script (~/.gradle/init.gradle
seems best to me), then also run each script specified on the command
line (the tool might add a --init-script option, but the user might
also specify one... both should be run). Because the scripts should
not depend on each other (nor even know about each other) this does
not seem problematic to me. What do you think?
This sounds good.
The script would allow tools to run Gradle and point it to a
custom init script that allows for customized behavior. This
script would be run with a "delegate" (similar to how the
Project class is a delegate for the build script) that
provides some access to Gradle as well as support for a
convenience DSL.
I'd be tempted to delegate to the Build class. Then, an init
script configures a Build instance just like a build script
configures a Project instance. The Build interface would probably
need some tweaks for this to work. I like that in both cases, the
script simply configures a domain object.
The problem is that Build knows a lot about the setup of the project.
This is good in some ways, but I wanted the init script to run before
any other scripts (even before the settings is evaluated). At that
point, some of the Build methods would not be well defined.
This is already the case. The only method we'd add to the list by moving
Build earlier is getRootProject(). Currently, the solution to this
problem is to throw an IllegalStateException from those methods which
don't make sense to call.
Also, this would require restructuring Gradle some since the build
object would need to be instantiated much earlier.
Absolutely. The Build instance represents the build being run, so it
should come into existence right at the start of the whole process. The
fact that it gets instantiated just after the settings file is more due
to the history of the internals than intention. Once the settings file
goes away, the Build instance will effectively be instantiated at the
start of the build.
With all of that said, I do agree that making the script "simply
configure a domain object" is a good goal.
This class (InitScript.groovy?) would provide access to the
startParameters (including allowing them to be modified), and
support a simple DSL for instantiating classes using a custom
class loader.
For this, I would use exactly the exact same mechanism(s) we use
in the build script to do this. That is, provide the equivalent of
the buildscript {} closure, something like:
initscript {
repositories { mavenCentral() }
classpath name: 'some-dependency'
}
I would also make Repository and Configuration containers
available, so that the script can do whatever custom dependency
management and classloading it wants.
Yes, I was thinking syntactically along those lines as well. Are you
suggesting that the init script would have a "two part" compilation
stage, where it is compiled into two classes, one is run to change the
class path, and then the other is run with that modified classpath?
That seems excessive to me.
I'm suggesting that we should use exactly the same mechanism for
declaring external classes in both the init script and the build script.
Currently this is to use a buildscript { } in the build script. This
means we should use an initscript { } block in the init script. I think
consistency is really important. We already have the infrastructure to
do this, we should reuse it for the init script. Why would we do
something different in the different scripts?
Much simpler (to me) seems to be the ability to defined
configuration-like things, and then have a method that creates classes
using them. Like this:
configurations { buildListenerConfig }
dependencies { buildListenerConfig 'some-dependency' }
buildListener = buildListenerConfig.create('my.custom.BuildListener')
addListener(buildListener)
If we were to do something like this, the same technique should also
work in the build script. That is, the configurations available in the
init script should be Configuration implementations, just like they are
in the build script.
I'm not sure what you propose is actually simpler. Here's the same
example using an initscript {} block:
initscript {
configurations { buildListenerConfig }
dependencies { buildListenerConfig 'some-dependency' }
}
buildListener = new my.custom.BuildListener()
addListener(buildListener)
Looks about the same complexity to me, plus I get to use the new
operator, and static methods and fields, and such.
This latter would make it easy to register a custom build
listener, which is the main intended use. However, as other
uses arise, more functionality could be exposed. For
instance, the script might wish to examine the environment to
determine that it is running in a CI server, and
enable/disable specific tasks. To do this, I would assume the
script would set system properties that would then be examined
in the build scripts when the tasks are defined.
Another option is if the script can register code to execute at
various times during the lifecycle of the build:
afterProjectsConfigured {
def isCI = ...
if ( isCI ) {
allprojects {
build.dependsOn uploadArchives
}
}
}
We would allow the init script to receive pretty much any of the
events on BuildListener in the same way.
I like this much better than my "hack system properties" idea. A rich
set of such event handlers would be awesome.
I would like to hear of any real use cases of which you
currently are aware.
Some other potential uses for an init script:
- Information about the user, such as repository/app
server/database authentication information.
- Personal customisations, such as custom task aliases, logging
configuration, additional plugins to apply.
- Information about the environment, such as where JDKs are
installed, dependency cache configuration, overrides to repository
configuration.
1) and 3) seem to call out for a "per-machine" init script like you
suggested above. 2) seems like a 'per-project' script, which might be
better handled via settings.gradle or some such.
For 2), I meant personal customisations, specific to me, not shared with
my team. There might be a similar set of customisations for a build
which are shared with my team, but these would end up in the build script.
Adam