NinjaGaiden
NinjaGaiden

Reputation: 3146

run before and after each target in a makefile

I would like to run a command before and after each target in a makefile.

so something like this

pre:
       @echo pre
       @echo running | mailx -s "Start {target}" [email protected]
post:
       @echo post
       @echo post | mailx -s "Finish {target}" [email protected]
j:
       long_running_command && echo $@ > $@
k: j
       long_running_command2 && echo $@ > $@

I would like to run pre and post for j and k. Ideally, I would like to get an email for each task that starts and stops.

Upvotes: 3

Views: 3456

Answers (2)

rampion
rampion

Reputation: 89093

If you only have one command per recipe, you can do this by changing the configuration for the shell the commands are run in.

Have the config file run the pre commands directly and trap EXIT to run the after commands in.

For example:

$ cat Makefile
SHELL := BASH_ENV=/dev/fd/3 3<<<'echo before; trap "echo after" EXIT' /bin/bash

default:
  echo default

other:
  echo first
  echo second
$ make default
echo default
before
default
after

However this may not be what you want if a recipe runs several commands, as the before and after code will run each time.

$ make other
echo first
before
first
after
echo second
before
second
after

And I don't know of a way (outside of recursive Makefiles) to have different recipes use different shells, so this won't work if you only want to set before/after for several recipes.

Upvotes: 1

MadScientist
MadScientist

Reputation: 100926

One way to do it is to modify all the recipes in your makefile to invoke some command. You can put it into a variable so it doesn't look too gross:

START = mailto --subject 'Started target $@' [email protected]
END = mailto --subject 'Finished target $@' [email protected]

j:
        $(START)
        long_running_command && echo $@ > $@
        $(END)
k: j
        $(START)
        long_running_command2 && echo $@ > $@
        $(END)

The nice thing about this is you can pick and choose which targets you want it for; maybe some of them don't need it. The disadvantage is if the command fails you won't get any "end" email at all.

If you really want to do it for every single target, then you can write a shell script that mimics the shell's behavior but also sends mail, while running the command.

$ cat mailshell
#!/bin/sh
# get rid of the -c flag
shift
mailto --subject "started $*" [email protected]
/bin/sh -c "$@"
r=$?
mailto --subject "ended $* with exit code $r" [email protected]
exit $r

(note this is totally untested but you get the idea I hope). Then in your makefile, set SHELL to that shell:

SHELL := mailshell

j:
        long_running_command && echo $@ > $@
k: j
        long_running_command2 && echo $@ > $@

I guess you could still pick and choose by setting SHELL as a target-specific variable only for those targets you wanted to use the shell.

One downside of this is that if you have recipes that have multiple lines you'll get an email for each line individually. You can work around this by enabling .ONESHELL: to force the entire recipe to be passed to a single shell. However, I believe that this may require your mailshell tool to be more sophisticated.

Upvotes: 3

Related Questions