Over the past week, I've had a bunch of ideas for enhancing
 the Gradle's command line.

 My goals are as follows:


    [1]  Allow directory and/or file tasks to be specified naturally on 
         the command line, without any weird clutter.   They should't
         be second-class citizens in a world where Gradle one day 
         handles C++ builds.

    [2]  Make it easy to specify a list of tasks within different 
         namespaces (subprojects), and which tasks in the list
         to execute (or exclude) recursively.   

    [3]  Provide a simple rule for resolving conflicts in whether to 
         run a task or not in a convenient, expressive, and 
         well-defined way.

    [4]  Allow tasks to be specified (or skipped) by regex.
        
    [5]  Provide a syntax for skipping any task that depends upon 
         a given task (or task regex) directly or indirectly.

    [6]  Provide a way to tunnel a set of args to a task via the 
         command line.


   This email is pretty long, but I think it goes a long way
   to addressing goals [1]-[6].   Hopefully, others will think
   the goals themselves matter, but I'd really like feedback
   on both the goals and the ideas I've presented below for
   addressing them.

                Cheers,
                -Jon


------------------------------------------------------------------
  Gory Details:
------------------------------------------------------------------

The easy one first...

    [1]  Directory (or file) tasks names should just be equal 
         to their own path name.  Thus, to run the 'a/b/c' 
         task on all subprjects, just type:

                % gradle a/b/c

         Note:  This one isn't much of a proposal, but I wanted to
                include explicit support for file/dir tasks without 
                weird name mangling rules.  File/dir task names should
                be first class citizens!

------------------------------------------------------------------


    [2]  To get fine-grained control over what tasks are recursive
         or non-recursive, adding new options to the following syntax
         isn't quite powerful enough:

                % gradle [options] [tasks]

         Thus, exploring a "decorator" syntax for tasks seems like 
         the way to go.  Thinking analogously for a moment:

             o   In shells (and in ant), something like:  '**/*.c'   
                 means '*.c in all subdirs, recursively'.  
                 
             o   In many languages, the colon (':') or double colon 
                 ('::') is associated with namespaces.  
         
         Let's see where it leads if we:

            o  Denote recursion in subprojects via the token  '::'
               (note that subprojects are a sort of namespace).

            o  Denote non-recursion via ':'

            o  Make the  last namespace given on the command line remain 
               in effect until another namespace specifier is encountered;
               this lets us avoid giving the same long subproject path
               name over and over again if we've got a list of tasks.

        Trivial examples
        ----------------
            If the default namespace is the empty string followed by '::',
            and if we make whitespace between a namespace and the task(s) 
            within it insignifigant, then the following commands are identical:
        
                % gradle foo            # normal shorthand form 
                % gradle ::foo          # '<the-empty-string>::foo'  == 'foo'
                % gradle :: foo         # whitespace after :: does not matter

            If we had a list of tasks ('foo' and 'bar') we can extend this
            trivial example a bit further:

                % gradle foo bar        # normal shorhand form
                % gradle ::foo ::bar    # longhand form
                % gradle ::foo bar      # :: isn't overriden, so bar == ::bar
                % gradle :: foo bar     # prettier formatting for a list 


            Typically though, we'd just say:
                % gradle foo bar        # no different from what we do today!

            So far, so what, right?  In the typical case, it just looks like 
            the same old command line!  Well, let's move on to some fancier 
            examples & see where this takes us.


        
        Fancier examples
        ----------------
            Let's say you wanted to execute the following tasks:

                o   The foo task (starting from the current project)

                o   The 'moo', 'cow', and 'egg' tasks recursively 
                    within the relatively-specified subproject x/y 

                o   The 'c' and 'd' tasks non-recursively
                    within the absolutely-specified project /a/b

            To do that with the proposed syntax you could say:

                % gradle  foo   x/y:: moo cow egg    /a/b: c d

            Note that the (recursive) 'x/y::' namespace remains in effect
            until overridden by the (non-recursive) 'a/b:' namespace.  Thus,
            the "longhand" version of this would be:


                # This is just for illustration purposes!
                % gradle ::foo    x/y::moo x/y::cow    a/b:c a/b:d 

            
            If task skipping is denoted by a '-' before the task name, then

                % gradle /a/b:: moo -cow  

            would mean: 

                o  Recursively run the moo task within the 
                   absolutely-specified  /a/b subproject.

                o  If running moo anywhere implies running
                   a cow task anywhere, skip the cow task.

            Taking this further:

                % gradle /a/b:: moo  /a/b/c: -cow
        
            would mean: 

                o  Recursively run the moo task within the 
                   absolutely-specified  /a/b subproject.

                o  If running moo anywhere implies running
                   the cow task within /a/b/c, skip that cow task.  
                   If the cow task is implied elsewhere, then it 
                   won't be skipped.  Notice that  '/a/b/c: -cow'  
                   isn't a recursive skip because of the ':'.


        One very nice thing about representing namepaces as 
        slash-seperated dirs (with a terminating ':' or '::') 
        is that it lets you use tab completion!


------------------------------------------------------------------

    [3]  The decision rule when two contradictory instructions
         are given should be "the last rule wins".   Thus:

                % gradle  foo  a/b:: -bar  a/b/d:: bar

            would mean:

                o  Recursively run the foo task

                o  Recursively prohibit the bar task from running
                   within the a/b subproject

                o  Recursively override the prohibition against
                   running the bar task in the subproject a/b/c


            Thus, bar would not run in a project like a/b/c or a/b/e 
            because of the 'a/b::-bar' goal, but it would run in a/b/d,
            a/b/d/xyz, a/b/d/foo/bar (and so on) because of the later  
            p'a/b/d::bar' recursive override.

            There are a few different alternatives to "last rule wins",
            but in considering them, it seems like end up being confusing, 
            not very expressive, or ambiguous.  This becomes clearer
            when you consider task regexes (see the next section).


------------------------------------------------------------------


    [4]  Allowing tasks to be specified (or skipped) by some sort 
         of regex or globbing syntax would be great.

         Several issues spring to mind:

                o  Glob vs regex:
                   Glob is usually easier/better for file patterns,
                   but could potentially be annoyingly limited when 
                   it comes to task name patterns.

                o  If we use "real" regexes, do we use find, match,
                   or leading substring pattern semantics?  

                o  Scope of regex/glob:  task vs namespace vs global

                o  Interactions with other task decorators like + or -
                   and delimiting the end of decorators with the
                   start of the pattern itself.

          My current view:

                o  Because subproject paths correspond to filesystem 
                   paths, some kind of globbing syntax seems natural.
                   for specifying namespaces.   There are many flavors
                   of globbing out there, but I'm thinking of something
                   fairly limited like:

                    * The '*' character for zero or more characters

                    * The '?' character for any single character

                    * The character class ([...]) where you can have a specify
                      individual characters (via a hyphen).  For example, 
                      '[aeiou3-7]' represents the set of all vowels plus the 
                      characters '3', '4', '5', '6', and '7'.

                    * Alternation:  '(...|...|...)'
                      For example, the glob:  'moo(xxx|yyy)cow'
                      is matched by both 'mooxxxcow' and 'mooyyycow'.
                      Note: you'd probably need to quote globs 
                            to avoid interactions with your command
                            line shell.

                o  By default use globbing on task names as well.
                   Consider the following command:

                        % gradle  'hello*xyz'

                   I think that should mean to run any task that starts with 
                   the string 'hello' and ends with 'xyz'.   If the default 
were 
                   a regex gramamr, then you'd need to say something more like:

                        % gradle  '^hello.*xyz$'            # Yuck.

                   Full regexes are powerful, but they're fairly ugly;
                   most of the time, you'd never need all the power they
                   provide anyway.  Thus I favor treating tasks on the 
                   command line as glob patterns, not regexes.


        Still, it would be cool if we *could* do real regexes.  I belive 
        it's possible if we a sequence like  '+~'  for regex inclusion 
        and '-~'  regex exclusion.   Because '+' is optional, you should
        also be able to have a bare '~', as long as it's quoted;  otherwise 
        a shell might confuse this with a homedir.  
        
        Thus the following are all equivalent:

                % gradle  +~test       # regex
                % gradle  '~test'      # regex with bare '~' (suitably quoted)
                % gradle  '*test*'     # glob syntax for "match test anywhere"

        Here's a nice illustration of where a real regex could be handy:

                % gradle  '~(?i)test$'

        This would mean:  recursively run any task that ends with
        the string 'test', matched in a case-insensitive way.
        That's not too far-fetched of an example.   Who knows 
        what significance people might give to capitalization 
        in a set of naming conventions they use.

        Of course you could support stuff that really is far-fetched too.  :)

        The one trick to all of this is that the '~' should always come 
        immediately before the task pattern if you've got multiple task 
        decorators.  For example, to exclude a pattern like "contains
        the string 'bar'", you'd say:
        
                        % gradle foo -~bar
        not:    
                        % gradle foo ~-bar

        With that one rule, there's never any ambiguity as to whether
        the '-' is part of the pattern (bar) or a negation indicating
        a skipped task.


------------------------------------------------------------------

    [5]  Sometimes, you're building on a machine that simply can't 
         support a particular task.   For example, some tasks 
         might require other programs that aren't present
         (or resources like network access, licence keys, etc.)
         Maybe only a special "golden build box" has everything
         required to run every single task.

         Example:   
                Let's say we have two tasks 'foo', and 'bar', each 
                of which have a long chains of dependencies:

                        foo->a->b->c->nasty->d->e->f->g->h->i
                        bar->j->k->l->m->n->o->p

                Suppose that the build machine you're on can't do 
                task 'nasty' for some reason (e.g.:  it may require
                something platform specific, licence keys, etc.).
                
                You'd like to be able to say:

                      % gradle foo bar  ...something_magical....

                Where ...something_magical... allows gradle to
                be smart enough to *cleanly* skip task 'foo'
                without first doing all the work of building 
                i,h,g,f,e, & d, then failing on 'nasty'.

                The thing is, I want to be able to do this without digging
                through what 'foo' depends on by hand.  I want some way for
                gradle to do that "...something magical..." bit for me!

        Here's an idea:
            What if '^' denoted "is wedged in there somewhere".
            In the case given above, we'd be able to say:

                % gradle foo bar -^nasty

        This would mean:
                Recursively run 'foo' as long as it does not directly
                or indirectly depend on the 'nasty' task.

                Recursively run 'bar' as long as it does not directly
                or indirectly depend on the 'nasty' task.

        More generally:

                -^taskName      Excludes any task that ultimately depends
                                upon taskName
        
                +^taskName      Includes any task that ultimately depends
                                upon taskName
                        
                 ^taskName      Same as  +^taskName  -- the '+' is optional


        This works nicely with recursive/non-recursive namespaces, negation,
        glob, and regex syntax.    

        For example, consider:

                % gradle  foo  a/b:-^~test


        That would mean:
               Recursively run the 'foo' task, but skip any task that
               directly or indirectly depends upon any task that includes
               the string 'test' in the subproject a/b.

               NOTE:

                   o   If 'foo' in subproject x/y/z  depended on 'mytest' 
                       in subproject a/b/c, it WOULD run -- 'a/b:' is 
                       non-recursive. 

                   o   If foo in in subproject p/d/q were dependent upon 
                       a task named 'your_test' in subproject 'a/b'
                       then foo would NOT run, as it would match 
                       the rule  a/b:-^~test 

        A nice side-feature for skipping would be something like

                % gradle -n --show-skipped   foo -bar  ...

        Where '-n'  could be like the standard  "don't really do anything"
        flag (like Gnu Make), and the --show-skipped flag would emit 
        a list of whatever tasks were skipped.

        The algorithm to make all this would wouldn't even be that hard:
        just process the rules in-order.  Discovering whether a task 
        ultimately depends on something could be handled by doing a 
        no-op "visitor pattern" style dag traversal.  You could cache 
        the result and if necessary, perform a topological sort before 
        executing anything for real.


------------------------------------------------------------------

    [6]  Providing a way to tunnel a set of args to a task via the 
         command line would be nice, because Gradle tasks could be
         used as a sort of dependency-aware program launcher.   
         
         Thinking analogously:
                o  Many utilities use '-' to indicate stdin
                o  A common way of saying "no more switches" is  '--'

         What if '_' meant "command line argument for task"

         Consider the following:

                % gradle  mytask _  'foo bar'                  # case 1
                % gradle  mytask _  '[foo, bar]'               # case 2
                % gradle  mytask _  '[foo:bar, moo:cow]'       # case 3

         Suppose that when a task executes, it gets a set of parameters
         in a variable called  'arg'.   Further, let's say that gradle
         parsed that arg like a string, array, or map (cases 1,2, and 3 
         respectively) accordingly.   Thus in your build.gradle you 
         could have:

                createTask("mytask")
                {
                    if ( it.arg   ...) 
                    { 
                        ... 
                    }
                }

        This could then launch your program (or do whatever else you wanted)
        with all the power of  resolve() and friends to set classpaths,
        ensure preconditions, and so forth.   In effect, it would let
        gradle become a lightweight workflow engine (of sorts), with
        the ability to parameterize ad-hoc actions directly on the command 
        line, rather than force everything into resource files.  If gradle
        is to be a dsl for dependency management (and not just a build system),
        this seems like it could be a pretty big win for very little effort.

        Hans and I talked about this a bit yesterday, and he was wondering
        about also adding something like:

                % gradle  mytask  _  '{ ...code... }'

        It's a bit unclear to me what the best values of  'this'
        should be within '{...code...}', and whether  {...code...}
        should replace action, or just be a way to configure 'mytask'.

        After writing all this stuff down, I think it's time for
        me to step back for a while.  :)




      Sick with a cold in Boston,
      -Jon


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply via email to