26

I have a Makefile that starts by running a tool before applying the build rules (which this tool writes for me). If this tool, which is a python script, exits with a non-null status code, I want GNU Make to stop right there and not go on with building the program.

Currently, I do something like this (top level, i.e. column 1):

$(info Generating build rules...)
$(shell python collect_sources.py)
include BuildRules.mk

But this does not stop make if collect_sources.py exits with a status code of 1. This also captures the standard output of collect_sources.py but does not print it out, so I have the feeling I'm looking in the wrong direction.

If at all possible, the solution should even work when a simple MS-DOS shell is the standard system shell.

Any suggestion?

Carl Seleborg
  • 13,125
  • 11
  • 58
  • 70
  • 4
    If you have GNU make 4.2 or above, you can check the `$(.SHELL_STATUS)` variable which will contain the exit status of the last `$(shell ...)` function evaluated. – MadScientist Aug 29 '17 at 12:48
  • 3
    The variable name is `$(.SHELLSTATUS)`, without `_`. https://www.gnu.org/software/make/manual/html_node/Shell-Function.html#Shell-Function – user3159253 Mar 06 '18 at 13:24
  • See also https://stackoverflow.com/a/59392005/491884, you can use a hack (kill parent make process with `|| kill $$PPID`). – jmuc Dec 18 '19 at 12:27

5 Answers5

7

There might be a better way, but I tried the following and it works:

$(if $(shell if your_command; then echo ok; fi), , $(error your_command failed))

Here I did assume that your_command does not give any output, but it shouldn't be hard to work around such a situation.

Edit: To make it work with the default Windows shell (and probably any decent shell) you could write your_command && echo ok instead of the if within the shell function. I do not think this is possible for (older) DOS shells. For these you probably want to adapt your_command or write a wrapper script to print something on error (or success).

mweerden
  • 13,619
  • 5
  • 32
  • 32
  • Thank you, looks like a step in the right direction; wish it could be macroized like sherr(), haven't managed that so far. – Michael Shigorin Jan 20 '15 at 15:13
  • Just putting it out there, if you're willing to assume Unix (I totally am), this was the only variant I found that worked. I was doing some total nonsense in a `define template` which later was `$(eval $(call template,arg1,arg2)`...which was aweful. But it totally works with the above, being careful to doubl `$$` *everything*. Hope someboedy benefits from this someday (the `@` at the beginning is because I'm in a target definition): `@$$(if $$(shell if ./generate.sh $(2) $(1); then echo "score"; fi), ,$$(error Unable to generate [$(2)] from [$(1)]))` – svenevs Mar 12 '17 at 14:34
4

You should use a regular target to create BuildRules.mk:

BuildRules.mk: collect_sources.py
        python $< >$@

include BuildRules.mk

This is the standard trick to use when automatically generating dependencies.

JesperE
  • 63,317
  • 21
  • 138
  • 197
  • Jesper, thanks for your input, but I don't see how your rule can fix my problem. I need collect_sources.py to run everytime, so that I can update the list of source files and rules, etc. Or am I missing something? – Carl Seleborg Oct 22 '08 at 18:46
  • Add a ".PHONY: BuildRules.mk" at the top, then. – JesperE Oct 23 '08 at 17:01
  • Perhaps I'm missing something obvious here, but I can't seem to get this to work correctly. BuildRules.mk is remade every time (using .PHONY), but the include happens before this is done. This effectively means you are always using the previous BuildRules.mk. – mweerden Oct 23 '08 at 21:45
  • Hm. The .PHONY is preventing Make from reloading the generated file, I missed that. BuildRules.mk needs a proper dependency on something. – JesperE Oct 25 '08 at 09:35
  • How about using $(wildcard ...) to let BuildRules.mk depend on all files which might affect the build rules? – JesperE Oct 25 '08 at 09:44
4

Ok, here's my own solution, which is unfortunately not based on the status code of the collect_sources.py script, but which Works For Me (TM) and lets me see any output that the script produces:

SHELL_OUTPUT := $(shell python collect_sources.py 2>&1)
ifeq ($(filter error: [Errno %],$(SHELL_OUTPUT)),)
  $(info $(SHELL_OUTPUT))
else
  $(error $(SHELL_OUTPUT))
endif

The script is written so that any error produces an output beginning with "collect_sources: error:". Additionally, if python cannot find or execute the given script, it outputs an error message containing the message "[Errno 2]" or similar. So this little piece of code just captures the output (redirecting stderr to stdout) and searches for error messages. If none is found, it simply uses $(info) to print the output, otherwise it uses $(error), which effectively makes Make stop.

Note that the indentation in the ifeq ... endif is done with spaces. If tabs are used, Make thinks you're trying to invoke a command and complains about it.

Carl Seleborg
  • 13,125
  • 11
  • 58
  • 70
0

Fixing https://stackoverflow.com/a/226974/192373

.PHONY: BuildRules.mk

BuildRules.mk: collect_sources.py
        echo Generating build rules...)
        python $< >$@
        $(MAKE) -f BuildRules.mk
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
-1

Make sure you're not invoking make/gmake with the -k option.

JohnMcG
  • 8,709
  • 6
  • 42
  • 49
  • 1
    Your suggestion seems logical, but the truth is that the `-k` option has no effect on the behavior of `make` upon failing commands in the `shell` function. – Kristian Spangsege Nov 06 '13 at 01:40