| 
GNU Make path handling PDF Print E-mail
Monday, 03 December 2007
Makefile builders often have to manipulate file system paths, but GNU Make itself provides few functions for path manipulation.  And cross-platform Make is made difficult by differences in path syntax.   This month's article looks at ways to manipulate paths in GNU Make and navigate through the cross-platform mine field.

The Basics: Target name matching
Take a look at the following example Makefile and suppose that ../foo is missing.  Does the Makefile manage to create it?

.PHONY: all
all: ../foo

.././foo:
    touch $@

If you run that Makefile with GNU Make you might be surprised to see the error

make: *** No rule to make target `../foo', needed by `all'.  Stop.

Now if you change the Makefile to:

.PHONY: all
all: ../foo

./../foo:
    touch $@

you'll find that it works as expected and performs a touch ../foo.

The first Makefile fails because GNU Make doesn't do path manipulation in target names and so it sees two different targets called ../foo and .././foo and fails to make the connection between the two.   The second Makefile works because I lied in the last sentence. GNU Make does do a tiny big of path manipulation: it will strip leading ./ from target names.   So in the second Makefile both targets are ../foo and it works as expected.

In general the rule with GNU Make targets is that they are treated as literal strings without interpreting them in any way.   That means it's essential that when referring to a target in a Makefile you always make sure that the same string is used.

The Basics: Lists of paths
It bears repeating that GNU Make lists are just strings where any whitespace is considered to be the list seperator.  That means that paths with spaces in them are not recommended because it makes using many of GNU Make's built in functions impossible, and spaces in paths cause problems with targets. 

For example, suppose a target is /tmp/sub directory/target and we write a rule for it like this:

/tmp/sub directory/target:
    @do stuff

GNU Make will actually interpret that as two rules, one for /tmp/sub and one for directory/target, just as if you'd written

/tmp/sub:
    @do stuff
directory/target:
    @do stuff

You can work around that by escaping that space with \ but that escape is poorly respected by GNU Make (it only works in target names and the $(wildcard) function).   For more on spaces in GNU Make read my previous article GNU Make meets file names with spaces in them.

Unless you have to, avoid spaces in target names.

The Basics: Lists of paths in VPATH and vpath
Another place that lists of paths appear in GNU Make is when specifying the VPATH or in a vpath directive.   For example, it's possible to set the VPATH to search for source files to a list of : or whitespace separated paths:

VPATH = ../src:../thirdparty/src /src

vpath %c ../src:../thirdparty/src /src

GNU Make will split that path correctly at either colons or whitespaces.  On Windows system that native builds of GNU Make use ; as the path separator for VPATH (and vpath) because : is needed for drive letters.   In these paths GNU Make on Windows actually tries to be smart and will split on colons unless it looks like a drive letter (one letter followed by a colon).   This drive letter intelligence actually creates a problem if you have a directory in the path whose name is a single letter: in that case you must use ; as the path separator otherwise GNU Make will think it's a drive.

VPATH = ../src;../thirdparty/src /src

vpath %c ../src;../thirdparty/src /src

On both POSIX and Windows systems a space in a path is a separator in a VPATH and vpath.   So the space is the best bet for cross-platform GNU Makeing.

/ or \

On POSIX systems / is the path separator and on Windows systems it's \.   It's common to see paths being built up in Makefiles like this:

SRCDIR := src
MODULE_DIR := module_1

MODULE_SRCS := $(SRCDIR)/$(MODULE_DIR)

It would be nice to remove the POSIX-only / there are replace it with something that would work with a different separator.   One way to do that is to define a variable called / (GNU Make lets you get away using almost anything as a variable name) and use it in place of /.

/ := /

SRCDIR := src
MODULE_DIR := module_1

MODULE_SRCS := $(SRCDIR)$/$(MODULE_DIR)

Of course, if that makes you uncomfortable just call is SEP:

SEP := /

SRCDIR := src
MODULE_DIR := module_1

MODULE_SRCS := $(SRCDIR)$(SEP)$(MODULE_DIR)

Now when switching to Windows you can just redefine / or SEP to \.  I use $(strip) to define / because it's hard to get a literal \ on its own (since GNU Make interprets it as a line continuation and it can't be escaped).

/ := $(strip \)

SRCDIR := src
MODULE_DIR := module_1

MODULE_SRCS := $(SRCDIR)$/$(MODULE_DIR)

Note, however, that the Windows builds of GNU Make will also accept / as a path separator, so paths like c:/src are legal.  Using those paths will simplify the Makefile but be careful when passing them to a native Windows tool that expects \ separaed paths.   If that's necessary then the follwing simple function will convert a forward slash path to a back slash path:

forward-to-backward = $(subst /,\,$1)

A Windows oddity: case insensitive but case preserving
On POSIX systems file names as case sensitive, on Windows they are not.  On Windows the files File, file and FILE are all the same physical file.  But Windows also has the oddity that the first time a file is accessed the specific case used is recorded and preserved.  Thus if we touch File it will appear as File in the file system (but can be accessed as FILE, file or any other combination of case).

By default GNU Make does case sensitive target comparisons and thus the following Makefile does not do what you might expect:

.PHONY: all
all: File

file:
    @touch $@

It is possible to compile GNU Make on Windows to do case insensitive comparisons (with the HAVE_CASE_INSENSITIVE_FS option).

This oddity is more likely to arise when a target is specified in a Makefile and also found using a wildcard search.  The target names may differ in case and that may cause an unexpected 'No rule to make' error.

GNU Make built in path functions and macros
It's possible to find out the current working directory in GNU Make using the built in CURDIR.  Note that CURDIR will follow symlinks so suppose you are in /foo but /foo is actually a symlink to /somewhwere/foo.   CURDIR will report the directory as /somewhere/foo.  If you need the non-symlink-followed directory name then call pwd via $(shell):

CURRENT_DIRECTORY := $(shell pwd)

The other directory that it's often interesting to find is the directory in which the current Makefile is stored.  This can be done using the MAKEFILE_LIST macro that was introduced in GNU Make 3.80.   At the start of a Makefile it's possible to extract its directory as follows:

CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
MAKEFILE_DIRECTORY := $(dir $(CURRENT_MAKEFILE))

GNU Make has functions for splitting paths into components: dir, notdir, basename and suffix.

Consider a file name /foo/bar/source.c stored in the macro FILE.   The four functions above can be used to extract the directory, file name and suffix:

Function Result
dir /foo/bar/
notdir source.c
basename source
suffix .c

You can see that the directory, the non-directory part, the suffix (or extension) and the non-directory part without the suffix have been extracted.   These four functions make file name manipulation easy.   If there was no directory specified then GNU Make assumes ./.  For example, suppose that FILE was just source.c:

Function Result
dir ./
notdir source.c
basename source
suffix .c

Since these functions are commonly used in conjunction with GNU Make's automatic macros (like $@) GNU Make provides a modifier syntax.  Appending D or F to any automatic macro is equivalent to calling $(dir) or $(notdir) on it.  For example, $(@D) is equivalent to $(dir $@) and $(@F) is the same as $(notdir $@).

GNU Make 3.81 added two very useful functions: abspath and realpath.

realpath is a GNU Make wrapper for the C library realpath function which removes ./, resolves ../, removes duplicated / and follows symlinks.  The argument to realpath must exist in the file system.   The path returned by realpath is absolute.  If the path does not exist then the function returns an empty string.

Note that you cannot use realpath as a file exists function because the final part of the path does not have to exist; to do file exists used $(wildcard FILE).

For example, the full path of the current directory could be found like this:

current := $(realpath ./)

abspath is similar but it does not follow symlinks and does not have to refer to an existing file or directory.  


John Graham-Cumming is Mr Make and Chief Scientist at Electric Cloud, Inc., a Silicon Valley start-up that speeds up builds by 10-20x using cluster technology.

Trackback(0)
Comments (3)add comment
0
jgc: ...
The GNU Make documentation is clear about realpath and abspath:

$(realpath names...)
For each file name in names return the canonical absolute name. A canonical name does not contain any . or .. components, nor any repeated path separators (/) or symlinks. In case of a failure the empty string is returned. Consult the realpath(3) documentation for a list of possible failure causes.

$(abspath names...)
For each file name in names return an absolute name that does not contain any . or .. components, nor any repeated path separators (/). Note that, in contrast to realpath function, abspath does not resolve symlinks and does not require the file names to refer to an existing file or directory. Use the wildcard function to test for existence.

John.
1

February 18, 2008
Votes: +0
0
Corey Brenner: ...
So, which is it? Does realpath have to refer to an already-existing entity in the file system, or can it refer to something which does not exist?

In my use of $(abspath), it certainly tramples symlinks (i.e., if I have sources in /tmp/fffooo and I want to refer to that path as /tmp/foo, and symlink /tmp/fffooo /tmp/foo, then cd /tmp/foo, $(abspath .) will tell me /tmp/fffooo.)

Or is $(realpath) what I want? If so, couldn't they have called it something like $(whereyouthinkyouare)?

2

February 15, 2008
Votes: +0
0
Jose Vasconcellos: ...
One caveat for windows users: the native version of gnu make 3.81 fails to process this correctly: $(abspath $(CURDIR))
3

January 02, 2008
Votes: +0

Write comment
smaller | bigger

security image
Write the displayed characters


busy
 
< Prev   Next >
If you already have an account on CM Crossroads, Login Now. If you do not, register using the link below...

NOTE: Once you register you will need to activate your account by clicking the link sent to you by email.

Video News

Agile in turbulent times Webinar