Makefile conditionals

Jaime Terreu
3 min readApr 5, 2022

My new gig consists of staunch Linux power users and understandably, they focused on tooling and dev environments that prioritized a Linux setup. As the first Mac user on the team I needed to walk through some fires to get a working environment but in the process learned a tonne, specifically around Makefile’s.

My first pull request was to set a variable that holds a compatible docker image tag for the operating system being used. This necessary because modern Mac arm64 architecture does not play nice with some containers and arm64 versions need to be used instead.

Setting the variable

The first thing I investigated was how to actually tell what architecture I was running on.

$ uname -s
Darwin

Perfect! “Darwin” tells me I’m using a Mac, and whilst it’s not telling me arm64 specifically, it’s good enough to move forwards.

I now needed to set a variable in a Makefile. There were plenty of examples already in the Makefile for that so this part was easy.

# Makefile
some_variable := "Something"
another_variable = "Something else"

A couple of options here due to the := = expressions. Very briefly, := will evaluate once and = will recursively evaluate. Because there will be nothing dynamic happening in this variable, := works just fine.

I now have to call the uname command to set the variable and it turns out Makefile’s have special syntax when you want to run commands in this way.

# Makefile
uname_S := $(shell sh -c 'uname -s 2> /dev/null || echo not')

This looks odd but lets break it down!

The shell function lets the Makefile communicate with the outside world, it takes a shell command as an argument and expands it.

The next bit sh -c invokes the default shell that exists in usr/bin/sh .

The interesting stuff happens here 'uname -s 2> /dev/null || echo not' .

uname -s as we know tells us what system we are running with on. whilst we could get away with just using this command the extra parts add some defensive niceties.

2> /dev/null is wacky to look at but essentially ensures that if uname -s errors, we swallow the error instead of setting the error value on the variable. You can read more about it here https://linuxhint.com/two-dev-null-command-purpose/

If the happy path fails then || echo not just sets a nonsense value of not to the variable.

Creating the conditional

So we have a the uname_S variable and now we have to find out how to use it in some sort of conditional logic that lets us set the tag of an arm64 compatible image.

There are a few ways to do this but I want it to be easy to read and very obvious, so ideally I would like to use a good old fashioned if statement like so.

# Makefileifeq ($(uname_S),Darwin)
CONTAINER_TAG=latest-arm64
else
CONTAINER_TAG=0.12.17
endif

The if statement is very readable and most folks will have a good idea of what’s going on here.

Using the container tag variable

The last part of the journey is actually using this variable we have conditionally set in the docker-compose command that spins up the container.

There are likely multiple ways to do this but I went with something simple and obvious.

# Makefile.PHONY run-container
run-container:
CONTAINER_TAG=${CONTAINER_TAG} docker-compose up -d some-service

There are a few Makefile specific things here and they are simply to allow me to run this docker-compose command from a command line like so:

$ make run-container

The main bit is CONTAINER_TAG=${CONTAINER_TAG} docker-compose up -d some-service . This allows CONTAINER_TAG to be available as an environment variable from withing the docker-compose.yml file.

# docker-compose.ymlversion: "3.7"
services:
some-service:
image: "some-service/some-service:${CONTAINER_TAG}
-- other stuff

Putting it all together

So now we have everything we need to do what we want. The final Makefile looks like this:

# Makefileuname_S := $(shell sh -c 'uname -s 2> /dev/null || echo not')ifeq ($(uname_S),Darwin)
CONTAINER_TAG=latest-arm64
else
CONTAINER_TAG=0.12.17
endif
.PHONY run-container
run-container:
CONTAINER_TAG=${CONTAINER_TAG} docker-compose up -d some-service

I was pretty chuffed to learn about Makefile’s and how powerful they can be. Working out how to do all this has certainly made me more curious about diving deeper and seeing what other things are possible.

I hope this has provided some usefulness to you and piqued your interest in Makefiles also.

--

--