40

In my Makefile, I need to test if the current directory is an SVN repo or not and if it is not I want to indicate an error using the $(error) directive in Makefile.

So I plan to use the return value of $(shell svn info .) but I'm not sure how to get this value from within the Makefile.

Note: I'm not trying to get the return value in a recipe, but rather in the middle of the Makefile.

Right now I'm doing something like this, which works just because stdout is blank when it is an error:

SVN_INFO := $(shell svn info . 2> /dev/null)
ifeq ($(SVN_INFO),)
    $(error "Not an SVN repo...")
endif

I'd still like to find out if it is possible to get the return value instead within the Makefile.

jww
  • 97,681
  • 90
  • 411
  • 885
Tuxdude
  • 47,485
  • 15
  • 109
  • 110
  • 6
    Oddly, I was never able to get GNU Make's [`.SHELLSTATUS` variable](https://www.gnu.org/software/make/manual/html_node/Shell-Function.html) to work as expected. It was always empty. I had to use the methods below. – jww Jul 29 '17 at 00:08
  • See also: https://stackoverflow.com/q/225542/94687 That question is specifically about forcing make to exit with an error, and your wording is more general, but nevertheless that seems to be what you actually want. – imz -- Ivan Zakharyaschev Oct 10 '18 at 17:06
  • Possible duplicate of [How to make a failing $(shell) command interrupt Make](https://stackoverflow.com/questions/225542/how-to-make-a-failing-shell-command-interrupt-make) – imz -- Ivan Zakharyaschev Oct 10 '18 at 17:06

5 Answers5

40

How about using $? to echo the exit status of the last command?

SVN_INFO := $(shell svn info . 2> /dev/null; echo $$?)
ifeq ($(SVN_INFO),1)
    $(error "Not an SVN repo...")
endif
eriktous
  • 6,569
  • 2
  • 25
  • 35
  • Thanks @eriktous - Cool trick - didn't think of that... I had to do some minor modifications for it to work - I've posted my answer based on your input :) – Tuxdude Sep 13 '11 at 03:41
  • 2
    Is there a way to do that, while keeping the output instead of directing it to '/dev/null'? What if I wanted to call $(error $(SVN_INFO)), but only when the return code was "1"? – jeremfg Jul 13 '16 at 21:13
  • @jeremfg You can skip the pipe to /dev/null and the code should still work as shown. – akhan May 27 '18 at 09:24
  • @jeremfg : The trick I use is to have a construct like `$(shell svn info . 2> /dev/null || echo fail` and then an `ifeq ($(SVN_INFO), fail)` for handling the error. This guarantees that you get the standard output if the command executed successfully, and "fail" if it failed. It's not pretty, and it obviously won't work for commands that can legitimately return the string "fail", but it works for all my usages. – Daniel Kamil Kozar Sep 04 '19 at 14:53
  • @DanielKamilKozar I don't follow how this help. Now you're not only hiding the return code, but the error message as well. – jeremfg Sep 05 '19 at 15:07
  • @jeremfg : Like I said, it's far away from the perfect solution, but if you're stuck on an older version of GNU Make and can't use `.SHELLSTATUS`, I don't think there are better solutions except perhaps writing more elaborate scripts in the `$(shell` invocation to handle the error (for example, by writing the message to a file in `/tmp`, which isn't a lot prettier either). My example works for calling `pkg-config` well enough, which is pretty much the only thing I call from my makefiles via `$(shell` anyway. – Daniel Kamil Kozar Sep 05 '19 at 15:10
  • @akhan Won't the variable SVN_INFO contain everything that was sent to the error stream if I remove the null redirection? How can I then do the following condition against "1" alone? – jeremfg Sep 05 '19 at 15:11
  • @DanielKamilKozar I agree. I was just trying to see how your proposal was better than this answer. I'd rather return the error code than a hard-coded string. It already provides more information, even if it's not everything. – jeremfg Sep 05 '19 at 15:15
  • @jeremfg The assignment will only capture stdout, which is the output of `echo $$?`. However, you should pipe stdout of `svn info` to /dev/null as done in the accepted answer. `SVN_INFO := $(shell svn info . >/dev/null; echo $$?)` – akhan Sep 05 '19 at 18:51
24

If you want to preserve the original output then you need to do some tricks. If you are lucky enough to have GNU Make 4.2 (released on 2016-05-22) or later at your disposal you can use the .SHELLSTATUS variable as follows.

var := $(shell echo "blabla" ; false)

ifneq ($(.SHELLSTATUS),0)
  $(error shell command failed! output was $(var))
endif

all:
    @echo Never reached but output would have been $(var)

Alternatively you could use a temporary file or play with Make's eval to store the string and/or the exit code into a Make variable. The example below gets this done but I would certainly like to see a better implementation than this embarrassingly complicated version.

ret := $(shell echo "blabla"; false; echo " $$?")
rc := $(lastword $(ret))
# Remove the last word by calculating <word count - 1> and
# using it as the second parameter of wordlist.
string:=$(wordlist 1,$(shell echo $$(($(words $(ret))-1))),$(ret))

ifneq ($(rc),0)
  $(error shell command failed with $(rc)! output was "$(string)")
endif

all:
    @echo Never reached but output would have been \"$(string)\"
stefanct
  • 2,503
  • 1
  • 28
  • 32
  • When I try to use `$(.SHELLSTATUS)` in GNUmake on Solaris it is empty. `$(info "status: $(.SHELLSTATUS)")` prints nothing. – jww Aug 24 '17 at 17:30
  • @jww: have you checked the version? 4.2 is relatively new (to stable distributions) and might not have propagated to wherever you got yours :) `make -v` I don't know other reasons than that for your problem. – stefanct Oct 30 '17 at 08:53
  • Can't wait for Make 4.2 to make it into production in popular distros! – obskyr Mar 15 '18 at 12:08
12

This worked fine for me - based on @eriktous' answer with a minor modification of redirecting stdout as well to skip the output from svn info on a valid svn repo.

SVN_INFO := $(shell svn info . 1>&2 2> /dev/null; echo $$?)
ifneq ($(SVN_INFO),0)
    $(error "Not an SVN repo...")
endif
Tuxdude
  • 47,485
  • 15
  • 109
  • 110
  • 11
    I think @eriktous probably should have received the credit for the answer. – jww Jul 29 '17 at 00:11
  • 1
    shouldn't it be 2>/dev/null 1>&2 – listerreg May 05 '19 at 12:02
  • @listerreg depends on what the user wants. `2>/dev/null 1>&2` is going to discard all output, while `1>&2 2>/dev/null` is going to send the standard output to the original error output (usually the terminal) while discarding the standard error output. – ash Jul 23 '20 at 17:55
8

Maybe something like this?

IS_SVN_CHECKED_OUT := $(shell svn info . 1>/dev/null 2>&1 && echo "yes" || echo "no")
ifne ($(IS_SVN_CHECKED_OUT),yes)
    $(error "The current directory must be checked out from SVN.")
endif
Roland Illig
  • 40,703
  • 10
  • 88
  • 121
6

I use .NOTPARALLEL and a make function:

.NOTPARALLEL:   

# This function works almost exactly like the builtin shell command, except it
# stops everything with an error if the shell command given as its argument
# returns non-zero when executed.  The other difference is that the output
# is passed through the strip make function (the shell function strips only
# the last trailing newline).  In practice this doesn't matter much since
# the output is usually collapsed by the surroundeing make context to the
# same result produced by strip.
SHELL_CHECKED =                                                      \
  $(strip                                                            \
    $(if $(shell (($1) 1>/tmp/SC_so) || echo nonempty),              \
      $(error shell command '$1' failed.  Its stderr should be above \
              somewhere.  Its stdout is in '/tmp/SC_so'),            \
      $(shell cat /tmp/SC_so)))
Britton Kerin
  • 427
  • 4
  • 9
  • This approach also works without using `.NOTPARALLEL`. You can also combine it with `mktemp` to store the output into a new tempfile each time, and clean that up as part of the build recipe that consumes whatever you will use this for. Great answer – Aaron D Aug 10 '22 at 15:50
  • I have turned this answer into a gist: https://gist.github.com/lulingar/f84ab70b86b37104dbbd7be0581c69ed. – Luis E. Jan 10 '23 at 13:30