Sunday, December 20, 2015

Dynamic PATH in GNU Make

Sometimes you may have several targets, where the 1st one creates a new directory, puts some files in it & the 2nd target expects newly created directory to be added to PATH. For example:

$ make -v | head -1
GNU Make 4.0

$ cat
PATH := toolchain:$(PATH)

src/.configure: | toolchain src
        cd src && ./
        touch $@

        mkdir $@
        printf "#!/bin/sh\necho foo" > $@/
        chmod +x $@/

        mkdir $@
        cp $@

toolchain target here creates the directory w/ new executables. src target emulates unpacking a tarball w/ script in it that runs, expecting it to be in PATH:

$ cat

echo PATH: $PATH

If we run this example, will unfortunately fail:

$ make -f 2>&1 | cut -c -72
mkdir toolchain
printf "#!/bin/sh\necho foo" > toolchain/
chmod +x toolchain/
mkdir src
cp src
cd src && ./
PATH: toolchain:/home/alex/.rvm/gems/ruby-2.1.3/bin:/home/alex/.rvm/gems

./ line 5: command not found recipe for target 'src/.configure' failed
make: *** [src/.configure] Error 127

The error is in the line where is invoked:

cd src && ./

As soon as we chdir to src, toolchain directory in the PATH becomes unreachable. If we try in use $(realpath) it won't help because when PATH variable is set there is no toolchain directory yet & $(realpath) will expand to an empty string.

What if PATH was an old school macro that was reevaluated every time it was accessed? If we change PATH := to:

path.orig := $(PATH)
PATH = $(warning $(shell echo PWD=`pwd`))$(realpath toolchain):$(path.orig)

Then PATH becomes a recursively expanded variable & a handy $(warning) function will print to the stderr the current working directory exactly in the moment PATH is being evaluated (it won't mangle the PATH value because $(warning) always expands to an empty string).

$ rm -rf toolchain src ; make -f 2>&1 | cut -c -100
mkdir toolchain PWD=/home/alex/lib/writing/
printf "#!/bin/sh\necho foo" > toolchain/
chmod +x toolchain/
mkdir src PWD=/home/alex/lib/writing/
cp src
cd src && ./ PWD=/home/alex/lib/writing/
PATH: /home/alex/lib/writing/

touch src/.configure

As we see, PATH was accessed 3 times: before printf/cp invocations & after ./ (because for ./ there is no need to consult PATH).

No comments:

Post a Comment