96

In his answer @Grundlefleck explains how to check whether a directory exists or not. I tried some to use this inside a makefile as follow:

foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then
        echo "Dir exists"
    fi

Running make foo.bak (given that foo.bar exists) yields the following error:

echo "foo"
foo
if [ -d "~/Dropbox" ]; then
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [foo.bak] Error 2

The workaround I made was to have a standalone bash script where the test is implemented and I called the script from the makefile. This, however, sounds very cumbersome. Is there a nicer way to check whether a directory exists from within a makefile?

Community
  • 1
  • 1
Dror
  • 12,174
  • 21
  • 90
  • 160

7 Answers7

111

Make commands, if a shell command, must be in one line, or be on multiple lines using a backslash for line extension. So, this approach will work:

foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then echo "Dir exists"; fi

Or

foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then \
        echo "Dir exists"; \
    fi
lurker
  • 56,987
  • 9
  • 69
  • 103
  • 1
    It looks like the issue is that everything needs to be on one line. Is that true? – Patrick Collins Dec 15 '15 at 23:40
  • @PatrickCollins individual commands can be on separate lines. For example `echo "foo" followed by `mv bar bah` can be commands on separate lines. The shell, though, sees the `if` expression through to the `fi` as a single command, so it must be on one line, or have "escaped" (`\`) line breaks. As you can see in the answer, `echo "foo"` can be on a separate line before the `if` expression as it's a separate command. – lurker Dec 16 '15 at 00:13
  • 1
    @PatrickCollins To make the long answer short, yes. Each line is ran it it's own shell, so you can't put the test on one line and the block below it, as the block will be in a different shell. This can formatted across multiple lines with the line-continuation character '\' which again, effectively puts it all on one line. – Edwin Buck Jun 06 '16 at 16:48
  • ; is must, this is okay: if [ -d ${BOOTSTRAPDIR} ]; then \ echo "Found ${BOOTSTRAPDIR}"; \ else \ echo "Do not find ${BOOTSTRAPDIR}"; \ fi – tom Oct 29 '21 at 13:57
66

This approach functions with minimal echos:

.PHONY: all
all:
ifneq ($(wildcard ~/Dropbox/.*),)
        @echo "Found ~/Dropbox."
else
        @echo "Did not find ~/Dropbox."
endif
cforbish
  • 8,567
  • 3
  • 28
  • 32
  • 11
    `$(wildcard ~/Dropbox/.)` is enough. The wildcard character is not necessary in this use case – Daniel Alder Dec 22 '14 at 09:00
  • For me it actually didn't work until I removed the * wildcard character, for some reason. – Kzqai Jan 10 '22 at 23:10
  • in my case: ```Makefile ifneq "$(wildcard path/$(var)/*)" "" @echo "dir already exists" else @echo "do something" endif ``` – K8sCat Jan 04 '23 at 09:42
51

Act upon the absence of a directory

If you only need to know if a directory does not exist and want to act upon that by for example creating it, you can use ordinary Makefile targets:

directory = ~/Dropbox

all: | $(directory)
    @echo "Continuation regardless of existence of ~/Dropbox"

$(directory):
    @echo "Folder $(directory) does not exist"
    mkdir -p $@

.PHONY: all

Remarks:

  • The | indicates that make shouldn't care about the timestamp (it's an order-only-prerequisite).
  • Rather than write mkdir -p $@, you can write false to exit, or solve your case differently.

If you also need to run a particular series of instructions upon the existence of a directory, you cannot use the above. In other words, it is equivalent to:

if [ ! -d "~/Dropbox" ]; then
    echo "The ~/Dropbox folder does not exist"
fi

There is no else statement.

Act upon the presence of a directory

If you want the opposite if-statement this is also possible:

directory = $(wildcard ~/Dropbox)

all: | $(directory)
    @echo "Continuation regardless of existence of ~/Dropbox"

$(directory):
    @echo "Folder $(directory) exists"

.PHONY: all $(directory)

This is equivalent to:

if [ -d "~/Dropbox" ]; then
    echo "The ~/Dropbox folder does exist"
fi

Again, there is no else statement.

Act upon both the presence and the absence of a directory

This becomes a bit more cumbersome, but in the end gives you nice targets for both cases:

directory = ~/Dropbox
dir_target = $(directory)-$(wildcard $(directory))
dir_present = $(directory)-$(directory)
dir_absent = $(directory)-

all: | $(dir_target)
    @echo "Continuation regardless of existence of ~/Dropbox"

$(dir_present):
    @echo "Folder $(directory) exists"

$(dir_absent):
    @echo "Folder $(directory) does not exist"

.PHONY: all

This is equivalent to:

if [ -d "~/Dropbox" ]; then
    echo "The ~/Dropbox folder does exist"
else
    echo "The ~/Dropbox folder does not exist"
fi

Naturally, the wildcard expansion might be slower than an if-else-statement. However, the third case is probably quite rare and is just added for completeness.

Anne van Rossum
  • 3,091
  • 1
  • 35
  • 39
18

Try this:

.PHONY: all
something:
    echo "hi"
all:
    test -d "Documents" && something

This will execute the commands under something only if Documents exists.

In order to address the problem noted in the comments, you can make a variable like this:

PATH_TEST = ~/SomeDirectory

test -d $(PATH_TEST) && something
Community
  • 1
  • 1
16

I had a case where I wanted to define a variable based on the test whether a directory exists or not at the top-most level of the Makefile where the approaches described above don't work. I found here a nice solution which can be used like this:

MY_DIRNAME=../External
ifneq "$(wildcard $(MY_DIRNAME) )" ""
  # if directory MY_DIRNAME exists:
  INCLUDES += -I../External
else
  # if it doesn't:
  INCLUDES += -I$(HOME)/Code/External
endif

This will modify the variable INCLUDES based on whether the directory stored in MY_DIRNAME exists or not.

(Motivation: In my case this variable would be used in another Makefile included later by the first:

include $(SFRAME_DIR)/Makefile.common

I wanted to have the same Makefile work in two different environments in a simple way.)

fuenfundachtzig
  • 7,952
  • 13
  • 62
  • 87
5

There is a very different answer that allows you to use your if statements as you envisioned them in one shell:

.ONESHELL:
foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then
        echo "Dir exists"
    fi

Note that the only difference is the ONESHELL special target.

Anne van Rossum
  • 3,091
  • 1
  • 35
  • 39
1

I use the following to detect if a file or a directory exists and act upon it :

$(if $(filter expected,$(wildcard *)), the expected file exists)

With your request :

.PHONY: ~/Dropbox

~/Dropbox:
    echo "Dir exists"

foo.bak: foo.bar | $(if $(filter ~/Dropbox,$(wildcard ~/*)), the expected file exists)

Which can further be simplify :

foo.bak: foo.bar | $(wildcard ~/Dropbox)