Let’s say you have a makefile, and you already have a shell script (probably called something like
env.sh) that you source before running your app to set environment variables in it. Now you’re writing tests (run by Make), and you need your environment variables there, too. “Aha”, you think, “I’ll source the env script in a makefile variable”:
DUMMY_ENV := $(shell source env.sh)
Well, that doesn’t work. Make runs its commands in a subshell, so the variables exported by
source aren’t available to other commands.
You notice that Make and Bash have awfully similar syntaces for setting variables. So you try just
includeing the script as if it were a makefile:
Nice! You’ve got the variables in your commands! Except, well, Make doesn’t parse quotes, so they’re part of the values. Not so nice. You need to munge the file into something closer to make: remove the quotes, maybe convert the
= to a
:=. You need to convert your
env.sh into an
env.mk. Sounds like a job for Make!
env.mk: env.sh cat $< | sed 's/"//g ; s/=/:=/' > $@
But when to make
env.mk? It needs to run before any target. You could just append it to all of your prerequisites, but that’s kind of horrible, and besides, it’ll mess up anything that’s depending on the order of the prerequisites. Well, there’s one target you could use. Make can make itself. If there’s a target for
makefile, and its prerequisites are new, the target will run before anything, because the makefile might change. So you add
env.mk as a prerequisite of
Now the file is created when you make any target! But it’s not being included. You need to do that after it’s been made, at runtime.
eval can run any Make code, even an
include, at runtime:
makefile: env.mk $(eval include env.mk)
And that works! The first time. Once you’ve made it,
env.mk is up to date, so the
eval never runs. You need to include
env.mk at the top, too, but not care if it doesn’t exist yet (because it’s about to be made and included anyway). That’s what
-include is for:
So, put it all together, and you end up with:
-include env.mk makefile: env.mk $(eval include env.mk) env.mk: env.sh cat $< | sed 's/"//g ; s/=/:=/' > $@
And that’s how you source a shell script in Make.
Maybe then you notice that the
makefile rule isn’t necessary. Anything that’s
included is listed as a makefile and remade before the makefile runs. And because Make restarts execution once it’s remade a makefile, you don’t need the
eval include. All you need is:
-include env.mk env.mk: env.sh cat $< | sed 's/"//g ; s/=/:=/' > $@
That’s a useless use of
cat, isn’t it.
-include env.mk env.mk: env.sh sed 's/"//g ; s/=/:=/' < $< > $@