Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terminate makefile command when subcommand throws?

Tags:

shell

makefile

I've got the following Makefile:

#runs the working directory unit tests
test:
    @NODE_ENV=test; \
        mocha --ignore-leaks $(shell find ./test -name \*test.js);

#deploys working directory
deploy:
    @make test; \
    make deploy-git; \
    make deploy-servers;

#deploys working to git deployment branch
deploy-git:
    @status=$$(git status --porcelain); \
    if test "x$${status}" = x; then \
        git branch -f deployment; \
        git push origin deployment; \
        echo "Done deploying to git deployment branch."; \
    else \
        git status; \
        echo "Error: cannot deploy. Working directory is dirty."; \
    fi

deploy-servers:
#   for each server
#       @DEPLOY_SERVER_IP = "127.0.0.1"; \
#       make deploy-server

#deploy-server:
#   connect to this server with ssh
#   check if app is already running
#   stop the app on the server if already running
#   set working directory to app folder
#   update deployment git branch
#   use git to move head to deployment branch
#   start app again

Note that deploy-servers and deploy-server are just dummies for now. This is what the deploy command should do:

  1. run the tests (make test), exit on failure
  2. push current head to deployment branch (make deploy-git), exit on failure
  3. pull deployment branch on servers (make deploy-servers)

You can see this in the Makefile as:

deploy:
    @make test; \
    make deploy-git; \
    make deploy-servers;

The issue is that I am not sure how to prevent make deploy-git from executing when make test fails, and how to prevent make deploy-servers from executing when the tests fail or when make deploy-git fails.

Is there a clear way to do this, or should I resort to using shell files or write these tools in a normal programming language?

like image 382
Tom Avatar asked May 11 '12 21:05

Tom


3 Answers

The exit status of a shell command list is the exit status of the last command in the list. Simply turn your command list into separate single simple commands. By default, make stops when a command returns nonzero. So you get what you want with

deploy:
    @make test
    make deploy-git
    make deploy-servers

Should you ever want to ignore the exit status of a simple command, you can prefix it with a dash:

 target:
     cmd1
     -cmd2 # It is okay if this fails
     cmd3

Your make manual has all the details.

like image 154
Jens Avatar answered Dec 08 '22 00:12

Jens


Others have given answers which are based on splitting the "recipe" into individual commands.

In situations where that is not viable, what you can do is set -e in the shell script to make it terminate if a command fails:

target:
        set -e ; \
          command1 ; \
          command2 ; command3 ; \
          ... commandN

This is the same set -e that you would put near the top of a shell script to get it to bail when some command terminates unsuccessfully.

Suppose that we are not interested in the termination statuses of command2 and command3. Suppose it is okay if these indicate failure, or do not reliably use termination status. Then, instead of set -e we can code an explicit exit test:

target:
        command1 ; \
        command2 || exit 1 ; \
        command3 ; \
        true  # exit 0 will do here also.

Since command3 can indicate failure, and we don't want it to fail our build, we add a successful dummy command.

like image 25
Kaz Avatar answered Dec 08 '22 01:12

Kaz


make should already do this; it executes complex commands with sh -e, which (as long as it's not in a loop in a POSIX compatible shell) will abort execution if a command exits nonzero, and aborts the entire Makefile on failure of a command unless you specifically tell it not to. If you're feeling paranoid, you can use && in place of ; in your commands.

like image 24
geekosaur Avatar answered Dec 08 '22 00:12

geekosaur