32

Is there a way to exit with an error condition if a file does not exist? I am currently doing something like this:

all: foo

foo:
    test -s /opt/local/bin/gsort || echo "GNU sort does not exist! Exiting..." && exit

Running make runs the all target, which runs foo.

The expectation is that if the test -s conditional fails, then the echo/exit statements are executed.

However, even if /usr/bin/gsort exists, I get the result of the echo statement but the exit command does not run. This is the opposite of what I am hoping to accomplish.

What is the correct way to do something like the above?

user2226755
  • 12,494
  • 5
  • 50
  • 73
Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
  • 1
    If I try this on the command line, having the right hand side of the `||` in parenthesis, to get a sub shell, makes it work as expected. I have no idea if it's the same in a makefile. Without the parenthesis, the "exit" will be executed even if the test is positive. – HonkyTonk Jan 15 '13 at 23:29

5 Answers5

38

I realize this is a bit old at this point, but you don't need to even use a subshell to test if a file exists in Make.

It also depends on how you want/expect it to run.

Using the wildcard function, like so:

all: foo
foo:
ifeq (,$(wildcard /opt/local/bin/gsort))
    $(error GNU Sort does not exist!)
endif

is one good way to do it. Note here that the ifeq clause is not indented because it is evaluated before the target itself.

If you want this to happen unconditionally for every target, you can just move it outside of a target:

ifeq (,$(wildcard /opt/local/bin/gsort))
$(error GNU Sort does not exist!)
endif
user2226755
  • 12,494
  • 5
  • 50
  • 73
KingRadical
  • 1,282
  • 11
  • 8
23

exit alone returns the status of the last command executed. In this case, it returns zero, which means everything is ok.

This is, because || and && have equal precedence, and the shell interprets the command as if it were written

( test ... || echo ... ) && exit

If you want to signal failure you must exit with a non zero value, e.g. exit 1. And if you want to echo and exit, just put the commands in sequence separated by ;

all: foo

foo:
    test -s /opt/local/bin/gsort || { echo "GNU sort does not exist! Exiting..."; exit 1; }
user2226755
  • 12,494
  • 5
  • 50
  • 73
Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
9

Every command line in make runs in its own sub-shell. So running exit just exits that sub-shell--not the makefile as a whole. By default, make execution will stop if any sub-shell returns an unsuccessful exit status (by convention, 0 means success, so anything else will halt execution). The simplest method would be just to use the exit status of the test command:

all: foo

foo:
    test -s /opt/local/bin/gsort

Printing a diagnostic message complicates things slightly because commands like echo will return an exit status of 0, causing make to think everything is fine. To work around this, you need to run a command after it that will give the sub-shell a non-zero exit status:

all: foo

foo:
    test -s /opt/local/bin/gsort || { echo "GNU sort does not exist! Exiting..."; exit 1; }

or even just

all: foo

foo:
    test -s /opt/local/bin/gsort || { echo "GNU sort does not exist! Exiting..."; false; }
user2226755
  • 12,494
  • 5
  • 50
  • 73
laindir
  • 1,650
  • 1
  • 14
  • 19
3

Simply do:

all: /opt/local/bin/gsort

and if /opt/local/bin/gsort is missing, you will get a "no rule to make target `/opt/local/bin/gsort'" error message.

But if you also want some nice explanation with it do:

/opt/local/bin/gsort:
    echo "GNU sort does not exist! Exiting..."
    false

In GNU/Make if the target is not declared .PHONEY and doesn't have any dependencies, the rule will be invoked if a file matching that target does not exist.

The code above will trigger the false command only when /opt/local/bin/gsort does not exist, will return a non 0 value, and make will fail.

user2226755
  • 12,494
  • 5
  • 50
  • 73
Chen Levy
  • 15,438
  • 17
  • 74
  • 92
  • Is there a way to do this such that make continues to check for every missing file, and then return false? That way, if someone is missing a lot of files, all will show up, instead of one at a time. – thepiercingarrow Mar 10 '16 at 03:28
  • 1
    @thepiercingarrow Use `make -k`, see http://www.gnu.org/software/make/manual/html_node/Testing.html – Olaf Dietsche Aug 31 '19 at 20:03
3

Since you're checking whether gsort executable file exists, you can use which or type shell command for that, e.g.:

all: foo
  :

foo:
  which gsort || exit 1
  # or
  type gsort || exit 1

You don't need an error message, since it'll automatically print:

/bin/sh: line 0: type: gsort: not found

which is obvious.


Alternatively use test or [ (see: help test/help [ for syntax), e.g.

test -x /opt/local/bin/gsort || { echo Error msg; exit 1; }

which checks if given file exists and it's executable, otherwise show the message and exit. The parenthesis are important to override the normal precedence of operators (left to right) by grouping the commands (see Compound Commands section in man bash for further info).


Another way is to use the rule's targets to check if the file exists and add as dependency to all, e.g.

all: /opt/local/bin/gsort
  @echo Success.

/opt/local/bin/gsort:
  @echo "GNU sort does not exist! Exiting..."
  exit 1

Result:

$ make
GNU sort does not exist! Exiting...
exit 1
make: *** [/opt/local/bin/gsort] Error 1
user2226755
  • 12,494
  • 5
  • 50
  • 73
kenorb
  • 155,785
  • 88
  • 678
  • 743