Working on Gradle-based enterprise builds requires familiarity with the usually quite large number of different project-specific tasks. Some sub-projects provide custom tasks to run maintenance scripts, others tasks to check additional properties of the build or to start and stop different components needed for the development life-cycle.
Bash auto-completion helps with the burden of remembering all the different tasks for the various sub-projects in a build. It also provides new ways to discover the features of a build in an exploratory way. In the following, I give a description on how to implement bash auto-completion, based on the following requirements:
- Fast auto-completion in the sub-second range. To compute the auto-completable suggestions, the Gradle project needs to be fully configured, which takes at least a few seconds for medium-sized builds. This means that the list of auto-completable suggestions needs to be precomputed.
- The auto-completable suggestions to show should be customizable. Some tasks, even if available on the project, should perhaps not be shown. This might be tasks that are not relevant for the developer but used in later integration stages. Other parameters to the build might be interesting to show in addition to the restricted set of tasks. This might include other parameters that can be passed to the Gradle build, such as task parameters or options to set the Gradle output logging level.
- No change to the Gradle wrapper scripts are needed.
The solution presented is two-fold. First, we introduce the shorthand gr
to run the Gradle wrapper from any directory of the multi-project build. Auto-completion is then implemented in the second step. Note that, although Bash is a Unix shell, the following also works for Bash ports on Windows such as MSYS (part for example of msysGit).
Gradle shorthand gr
There are different design choices to implement the shorthand gr
, influenced by the features it should have. The implementation given here works as follows. It searches from the current directory upward until it finds a Gradle wrapper script which it then invokes.
We first implement the function upsearch
that can search upward in a directory tree. All of the following goes into the Bash startup file ~/.bashrc
:
upsearch () { # adapted from https://gist.github.com/lsiden/1577473 local the_test=$1 local curdir=`pwd` while [[ "`pwd`" != '/' ]]; do if eval "[[ $the_test ]]"; then pwd cd "$curdir" return 0 fi cd .. done cd "$curdir" return 1 }
We then implement a function _findgradledir
that searches for the Gradle wrapper file named gradlew
:
_findgradledir () { upsearch '-f gradlew' }
The function _gradlew
, implemented in the following, uses _findgradledir
to find the wrapper file, which it then invokes.
_gradlew () { local gradle_project_dir=`_findgradledir` if [ -z "$gradle_project_dir" ]; then echo "Gradle project directory not found" 1>&2 return 1 fi "$gradle_project_dir/gradlew" -p "$gradle_project_dir" "$@" }
Last, we define the command gr
. Its implementation is directly given by the function _gradlew
. We can also pass default parameters to the wrapper invocation. For example, to improve startup and execution time of the Gradle invocation, we add the --daemon
flag that activates the Gradle daemon:
alias gr="_gradlew --daemon"
Auto-completion
Implementing auto-completion requires not only some bash scripting but also some implementation on the Gradle side that gives us the list of auto-completable suggestions. We introduce a task named bashCompletion
that generates the list of tasks in a multi-project build and writes them line-by-line into a text file. The bashCompletion
task is defined in the root Gradle project (but could be put for example in an init script if it were to be used for many builds):
task bashCompletion << { def allTaskNames = [] getAllTasks(true).each { proj, taskSet -> taskSet.each { if (proj.parent) { allTaskNames += proj.name + ':' + it.name } else { allTaskNames += it.name } } } def commands = (defaultGradleCommands + allTaskNames).sort().unique().join('\n') file('gradle-autocomplete').text = commands }
The presented implementation is a very basic one and many customizations are possible here. For example, task exclusion lists or other build parameters could be used. The auto-completable suggestions are stored sorted in a line-based text file so that it has a nice format for diffs when we put it into version control. More information on why this might be useful is provided in another blog article here.
On the Bash side (~/.bashrc
), basic auto-completion is fairly trivial to implement. We use the previously defined function _findgradledir
to find the text file of auto-completable suggestions created by our bashCompletion
task:
_gradle_complete() { local cur _get_comp_words_by_ref -n : cur local gradle_project_dir=`_findgradledir` if [ -z "$gradle_project_dir" ]; then return 1 fi if [ ! -f "$gradle_project_dir/gradle-autocomplete" ]; then return 1 fi local commands=$(cat "$gradle_project_dir/gradle-autocomplete" | tr '\n' ' ') COMPREPLY=( $(compgen -W "$commands" -- $cur) ) __ltrim_colon_completions "$cur" }
The line _get_comp_words_by_ref -n : cur
is needed for bash completion to work with colons in task calls of sub-projects.
Finally we use the bash complete
command to attach the implemented bash completion function to our Gradle shorthand gr
:
complete -F _gradle_complete gr