Reputation: 3251
I am having the darndest time trying to figure out how to run a shell command from Gradle, since it seems like Gradle makes it very difficult to do this.
Here is the command:
git branch --merged | grep -v \* | grep -v master | grep -v develop | grep -v dmz | xargs git branch -D
It's just a convenience command to clean up local branches that have been merged.
Here is the task I created:
task gitCleanLocalBranches {
doLast {
exec {
workingDir '.'
commandLine 'git branch --merged | grep -v \\* | grep -v master | grep -v develop | grep -v dmz | xargs git branch -D'
}
}
}
The task fails with:
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':gitCleanLocalBranches'.
> A problem occurred starting process 'command 'git branch -a''
* Try:
Run with --info or --debug option to get more log output.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':gitCleanLocalBranches'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:100)
...
Caused by: org.gradle.process.internal.ExecException: A problem occurred starting process 'command 'git branch --merged | grep -v \* | grep -v master | grep -v develop | grep -v dmz | xargs git branch -D''
at org.gradle.process.internal.DefaultExecHandle.execExceptionFor(DefaultExecHandle.java:222)
... 3 more
Caused by: net.rubygrapefruit.platform.NativeException: Could not start 'git branch --merged | grep -v \* | grep -v master | grep -v develop | grep -v dmz | xargs git branch -D'
at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:27)
... 4 more
Caused by: java.io.IOException: Cannot run program "git branch --merged | grep -v \* | grep -v master | grep -v develop | grep -v dmz | xargs git branch -D" (in directory "/home/wlaw/sterlib"): error=2, No such file or directory
at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
... 6 more
Caused by: java.io.IOException: error=2, No such file or directory
... 7 more
So I figured that the command is too complicated so I tried something simpler, and changed commandLine
to:
commandLine 'git branch -a'
But I got the exact same error. Why is Gradle not able to find anything in the PATH
environment variable?
Upvotes: 32
Views: 54234
Reputation: 301
I’m not a gradle user, but the example in OP is using the Exec task type. To me it looks like it's based on the low-level Java Runtime.exec()
which ought to end up in the classical exec
family of functions that you find in C.
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Runtime.html https://www.geeksforgeeks.org/exec-family-of-functions-in-c/
By low level I mean that you are in full control, but you also have to do all the shell's work yourself. Identifying what the command is and its arguments. Start the pipes as child processes, monitoring processes for exit, connecting the inputs and outputs of pipes, handling redirects of inputs, expanding symbols like wildcard globes and $VARIABLES.
git branch --merged | grep -v -e \* -e master -e develop -e dmz | xargs git branch -D
So when running the above in my terminal using using zsh
and some extra fluff (oh-my-zsh), zsh will create three child processes using exec
, one for each pipe. Note how the *
needs to be escaped by a backslash to prevent it from being treated as a glob pattern by the shell. If left unescaped the shell would replace it by the by all files in the current working directory matching the pattern, in this case all files (except the ones starting with .
).
In my shell grep
is overloaded by an alias
:
# alias grep
grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn,.idea,.tox}'
Therefore what finally gets executed is this:
# pstree $$
-+= 01103 alice -zsh
|--= 83249 alice git branch --merged
|--- 83250 alice grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox -v -e * -e master -e develop -e dmz
|--- 83251 alice xargs git branch -D
As you understand this gets very difficult and error prone to do yourself. Therefore, we piggyback on the shell by having it execute our script.
This leads us to the other way, calling myScript.execute()
on a string. I assume it's a method unique to Gradle or Groovy. It likely translates into something like (or possibly the shell is hard coded to /bin/bash
):
commandLine = System.getenv('SHELL'), '-c', myScript
Upvotes: 0
Reputation: 884
If you need to save the command output you could do this:
def gitBranchA = "git branch -a".execute().text.trim()
Upvotes: 11
Reputation: 124804
The command to execute and its arguments must be separate parameters to pass to commandLine
, like this:
commandLine 'git', 'branch', '-a'
If you want to execute a complicated pipeline as in your first example, you can wrap it in a shell script.
I cannot test this, but I think this should work as well:
commandLine 'sh', '-c', 'git branch --merged | grep -v -e \* -e master -e develop -e dmz | xargs git branch -D'
Note: I took the liberty and simplified the grep
a bit.
Lastly, you could also create a Git alias in your .gitconfig
to wrap the complex pipeline.
Upvotes: 45