3

Here's a simple Makefile with 4 targets (a, b, c and all). Target b can fail (represented here with exit 1).

a:
    echo "a"

b:
    exit 1

c:
    echo "c"

all: a b c

When running make all, c is never printed as b fails and target c is consequently not run. But in my particular case, I want c to be run, even if b fails.

I'm wondering if there is a way to define the "continue if error" policy directly inside the dependencies of target all.

I know that the desired behaviour can be reached by :

  • running make -i all (--ignore-errors) or make -k all (--keep-going)
  • using a "recursive" make
  • prefixing failing command in b with - (like -exit 1)
  • running tasks separately with make a; make b || make c

but all of these options implies to modify targets a, b or c, or modify the way make all is called.

Is there a way to have the expected behaviour by just modifying the all target dependencies (something like all: a -b c, but that definition does not work, obviously)?

Additional requirement : make all should return with exit code 1 if b fails, even if c target succeeds.

norbjd
  • 10,166
  • 4
  • 45
  • 80

2 Answers2

2

If you want to run all recipes of a, -b, c even if the -<something> ones fail you can use a pattern rule for -<something> targets:

a c:
    @echo "$@"

b:
    @echo "$@"; exit 1

all: a -b c

-%:
    -@$(MAKE) $*

Demo (with --no-print-directory for simpler output):

$ make --no-print-directory all
a
b
Makefile:5: recipe for target 'b' failed
make[1]: *** [b] Error 1
Makefile:10: recipe for target '-b' failed
make: [-b] Error 2 (ignored)
c

But if you also want to "remember" their exit status, things are a bit more difficult. We need to store the exit status somewhere, for instance in a file and reuse it for the all recipe:

a c:
    @echo "$@"

b:
    @echo "$@"; exit 1

all: a -b c
    @exit_status=`cat b_exit_status`; exit $$exit_status

-%:
    -@$(MAKE) $*; echo "$$?" > $*_exit_status

Demo:

$ make --no-print-directory all
a
b
Makefile:5: recipe for target 'b' failed
make[1]: *** [b] Error 1
c
Makefile:8: recipe for target 'all' failed
make: *** [all] Error 2

Hard-wiring the name of the potentially failing target in the recipe of all is not very elegant. But it should be quite easy to solve:

a b c:
    @echo "$@"

d:
    @echo "$@"; exit 1

all: a -b c -d
    @for f in $(patsubst -%,%_exit_status,$(filter -%,$^)); do \
        tmp=`cat $$f`; \
        printf '%s: %s\n' "$$f" "$$tmp"; \
        if [ "$$tmp" -ne 0 ]; then exit $$tmp; fi; \
    done

-%:
    -@$(MAKE) $*; echo "$$?" > $*_exit_status

.PHONY: clean
clean:
    rm -f *_exit_status

Demo:

$ make --no-print-directory all
a
b
c
d
Makefile:5: recipe for target 'd' failed
make[1]: *** [d] Error 1
b_exit_status: 0
d_exit_status: 2
Makefile:8: recipe for target 'all' failed
make: *** [all] Error 2
Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • Thanks for the well-explained answer. Another question (though not related) : if I have multiple `Makefile` in my current folder, or if I have to pass parameters to the `Makefile`, how could I specify these parameters in `$(MAKE)` call? – norbjd Dec 13 '18 at 14:42
  • `make -f VAR1=VALUE1 VAR2=VALUE2...`: `-f ` tells make that the Makefile to use is `` and `VAR1=VALUE1` defines make variable `VAR1` with value `VALUE1`. – Renaud Pacalet Dec 13 '18 at 15:45
  • My question was unclear, sorry, in fact I'm already running `make -f VAR1=VALUE1 VAR2=VALUE2`. But when running `$(MAKE)` inside the `Makefile`, the command run is `make` without the parameters I used to run the initial `make`. I was wondering if there is a simple way to propagate those parameters to `$(MAKE)`. – norbjd Dec 13 '18 at 16:12
  • 1
    This is a very different question and it is difficult to answer in a comment. You should probably ask a new question. – Renaud Pacalet Dec 13 '18 at 16:49
  • The original parameters won't appear on the command-line where the sub-make is run. The sub-make will know of them however. They are passed by other means. (Shared memory? Environment variables? Who cares... if you do, have a look at [the secution about recursion](https://www.gnu.org/software/make/manual/make.html#Recursion) in the manual.) – bobbogo Dec 14 '18 at 16:21
0

While you can't transmit parameters through prerequisite names (or at least only if you change the prerequisites completely), you could employ target-specific variables. But the solution isn't exactly pretty, with an additional variable leading every recipe line:

CIE_DASH = $(if $(filter $@,$(CONTINUE_SET)),-)

all: a b c
all: CONTINUE_SET += b

a: 
    $(CIE_DASH)echo "a"

b: CONTINUE_SET += e
b: e 
    $(CIE_DASH)exit 1

e:
    $(CIE_DASH)exit 1
c:
    $(CIE_DASH)echo "c"
Vroomfondel
  • 2,704
  • 1
  • 15
  • 29
  • Thanks for the answer. In my case, why `exit 1` is not the only line that should be prefixed by `$(CIE_DASH)`? Because only `b` target can fail. Also, why `e` is needed? Finally, with this solution, how could I capture exit code of `b` and propagate it to `c` (see the additional requirement in my question)? – norbjd Dec 13 '18 at 12:04
  • I just put `$(CIE_DASH)` in front of everything because you don't know in advance if some day you're going to add `a` or `c` to the `CONTINUE_SET`. `e` isn't needed, I just wanted to demonstrate that this is a hierarchical method to suppress exit on error - the programmer writing the `all` target may be unaware of the things happening down below `b`. – Vroomfondel Dec 13 '18 at 12:12
  • Propagation to `c` is impossible/unreliable because make is free to decide on the order of evaluation of independent targets. Propagating to the top level rule... honestly I think this going to get even uglier and you can't use my solution then because the dash forces make to ignore the return code - no way to access it. – Vroomfondel Dec 13 '18 at 12:21
  • Thanks for the explanation. Got the idea, it's clever and it may be useful in some cases. But in the specific case I explained, this solution is similar to my 3rd point : prefixing failing command in `b` with `-` (like `-exit 1`), because I know that only `b` can fail in my example. So there is no need here to use `CONTINUE_SET`, nor prefixing all lines by `CIE_DASH`. – norbjd Dec 13 '18 at 12:37