Sharing Variables between GNU Make and the Bash

Both GNU Make and Bourne Again SHell allow for using variables. This post explains how to share them between these two tools.

Table of Contents

Motivation

If you don’t want to read all this and just see how it’s done, for your convenience, all files mentioned in this article can be downloaded as a GitHub Gist: Link.

Let’s first look at a typical application scenario. I am often working with several different hosts which are running a container runtime. Typical parameters involved in these cases are, e.g.

These parameters can be supplied to docker compose by setting environment variables before invoking it. Of course, these parameters shall be versioned in version control system (VCS) as any other project data.

In addition to that, suppose some tasks of your projects tasks have already been automated using GNU Make. The following question arises:

How can environment variables not only be versioned in git, but also be shared between Makefiles and shell sessions, so that they are used by direct invocations of docker compose?

Step 1: Extract Variables from Your Makefile

One of the key elements to separate variable definitions from Makefiles is the include directive. This directive suspends reading the current makefile and reads one or more other makefiles before continuing, e.g.:

include env
export

In this case, the given Makefile includes the contents of a file called env from the same path as the Makefile. The export keyword makes sure that all variables known to GNU Make are also exposed as environment variables to tools invoked by it.

In our situation, the env file only contains variable definitions:

COMPOSE_FILE=../ts-client/docker-compose.yaml:docker-compose.myproject.yaml
DOCKER_CONTEXT=mycontext
COMPOSE_PROJECT_NAME=myproject

After including the aforementioned file, these variables can be referenced in the Makefile:

all:
	@echo $(COMPOSE_FILE)

Now, these variables should also be made available to the current Bash session.

Step 2: Set EnvVars depending on your CWD

The next key element we need to look at is some kind of hook, the shell executes when changing directories. direnv is such an extension to shells which allows to load/unload environment variables depending on the current directory.

After installing direnv in the system, the hook is enabled by adding the following to the ~/.bashrc file:

eval "$(direnv hook bash)"

With this setup, direnv searches for a .envrc file in every directory when entering. In case it finds such file, it executes it in a subshell. Only the environment variables of this subshell are then passed to the current shell session. The containing directory must be “enabled” by issuing direnv allow .

The next step is to make sure that the .envrc file exports the files from the env file which is also included by our Makefile:

file=$(cat env)

for line in $file; do
	export "${line}"
done

Conclusion

With this approach, we can comply with the DRY principle. Variables are defined only once in the env file and are available to both GNU Make and the current shell session.