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

Reply via email to