25

I'm wondering if there's a way to implement trap in GNU make, similar to that built into BASH?

If the user presses CTRL-C, or if make itself fails (non-zero exit), I'd like to call a particular target or macro.

Jolta
  • 2,620
  • 1
  • 29
  • 42
khosrow
  • 8,799
  • 4
  • 21
  • 24
  • +1 for the interesting question, even though what you are doing sounds like a bad idea. – finnw Jun 10 '09 at 07:32

6 Answers6

27

At this point in time, GNU make doesn't have native support.

There is a reliable workaround however:

.PHONY: internal-target external-target

external-target:
  bash -c "trap 'trap - SIGINT SIGTERM ERR; <DO CLEANUP HERE>; exit 1' SIGINT SIGTERM ERR; $(MAKE) internal-target"

internal-target:
  echo "doing stuff here"

This catches interruptions, terminations AND any non-zero exit codes.

Note the $(MAKE) so cmdline overrides and make options get passed to submake.

On trap:

  • clear trap handler (with -)
  • do the cleanup
  • exit with non-zero exit status, so build automation tools report the failed build.

DELETE_ON_ERROR does NOT work for directories, so this is key for cleaning up after mktemp -d, for example

Replace <DO CLEANUP HERE> with valid CMD.

Kevin
  • 2,761
  • 1
  • 27
  • 31
  • Besides the nested quote in your echo, which of course are illegal, this does not seem to work, at least for what I want to accomplish. How is one is supposed to use it? Suppose my makefile had only one target with a sleep and a touch, like @andrewdotn example (ignore .DELETE_ON_ERROR), how would you modify it to use this workaround? – Davide Oct 06 '17 at 18:44
  • Please see edits. echo "doing stuff here" would be the sleep/touch. DO CLEANUP HERE would be something like, echo you pressed ctrl-c – Kevin Oct 06 '17 at 23:52
20

A simplified version of @kevinf’s answer which seems good enough for basic cases:

run:
    bash -c "trap 'docker-compose down' EXIT; docker-compose up --build"

(This example is for a reason: docker-compose up does say

When the command exits, all containers are stopped.

but it does not rm the stopped containers like docker run --rm would, so you can still see them with docker ps -a.)

Jesse Glick
  • 24,539
  • 10
  • 90
  • 112
  • I still see `recipe for target 'run' failed ... make: *** [run] Interrupt` when I try this, and the make target does not run the `docker-compose down` command. (I have the exact use case described in this answer.) – ely Nov 14 '17 at 16:09
  • This did work for me for this exact use case of making sure down runs after up. – Austin Mackillop Oct 30 '19 at 00:47
4

No. GNU make’s signal handling already leaves a lot to be desired. From within its signal handler, it calls functions like printf that are not safe to be called from within a signal handler. I have seen this cause problems, for example .DELETE_ON_ERROR rules don’t always run if stderr is redirected to stdout.

For example, on a CentOS 7.4 box:

  1. Create the following Makefile:

    .DELETE_ON_ERROR:
    
    foo:
            touch $@
            sleep 10
    
  2. Open it in vim and run :make,

  3. While it is sleeping, hit Ctrl-C

Vim/make prints

Press ENTER or type command to continue
touch foo
sleep 10
^C
shell returned 130

Interrupt: Press ENTER or type command to continue

Make was sent an interrupt signal, but foo still exists.

andrewdotn
  • 32,721
  • 10
  • 101
  • 130
2

Make does not support it, but using BASH tricks you can accomplish something similar.

default: complete

complete: do_mount
        echo "Do something here..."

do_mount:
        mkdir -p "$(MOUNTPOINT)"
        ( while ps -p $$PPID >/dev/null ; do \
                sleep 1 ; \
        done ; \
        unmount "$(MOUNTPOINT)" \
        ) &
        mount "$(MOUNTSOURCE)" "$(MOUNTPOINT)" -o bind

The "unmount" will run after the "make" completes. This is usually a satisfactory solution if you are attempting to cleanup operations that may occur during the build but are not cleaned up normally on "make" exit.

PSpiller
  • 21
  • 2
1

No. As far as I know there is no such functionality.

JesperE
  • 63,317
  • 21
  • 138
  • 197
1

make produces return codes. As far as I can remember right now, it returns 0 for success, 2 for failure (please check the documentation). Therefore, would it be enough for you to wrap make inside a shell script for example?

jbatista
  • 2,747
  • 8
  • 30
  • 48
  • 1
    I can do that, but trying to avoid it if possible, it's self-contained and I'm *trying* to keep it that way. – khosrow Jun 10 '09 at 11:07