Very nice, and I like it a lot,
2009/3/14 <[email protected]>
>
>
> 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'.
>
For this to be handy I think we should use the same that javadoc does for
long commandlines, use @ for commandline expansion by a file, so:
% gradle mytask _ @mytask.custom.code
with @mytask.custom.code contain the ...code... part.
I think it would make sense to allow commandline expansion with @ in
general. When large projects are using all the features of the command line
mentioned here they probably would find this usefull and create a couple of
commandline files that they can easily execute with @... .
> 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
>
>
>