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 example-01.mk
PATH := toolchain:$(PATH)

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

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

src:
        mkdir $@
        cp configure.sh $@

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

$ cat configure.sh
#!/bin/sh

echo PATH: $PATH
echo
foo.sh

If we run this example, configure.sh will unfortunately fail:

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

./configure.sh: line 5: foo.sh: command not found
example-01.mk:4: recipe for target 'src/.configure' failed
make: *** [src/.configure] Error 127

The error is in the line where configure.sh is invoked:

cd src && ./configure.sh

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 example-02.mk 2>&1 | cut -c -100
mkdir toolchain
example-02.mk:2: PWD=/home/alex/lib/writing/gromnitsky.blogspot.com/posts/2015-12-20.1450641571
printf "#!/bin/sh\necho foo" > toolchain/foo.sh
chmod +x toolchain/foo.sh
mkdir src
example-02.mk:2: PWD=/home/alex/lib/writing/gromnitsky.blogspot.com/posts/2015-12-20.1450641571
cp configure.sh src
cd src && ./configure.sh
example-02.mk:2: PWD=/home/alex/lib/writing/gromnitsky.blogspot.com/posts/2015-12-20.1450641571
PATH: /home/alex/lib/writing/gromnitsky.blogspot.com/posts/2015-12-20.1450641571/toolchain:/home/ale

foo
touch src/.configure

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

No comments:

Post a Comment