09/09/2022

How to create Makefile targets with dynamic parameters and autocompletion - Make

Make is one of the oldest build automation tools ever (the original Make was created in 1976!). And since then, it got many implementations as BSD make, GNU make, and Microsoft nmake. It uses a declarative syntax, and sometimes that's the best and worst thing about it!

You probably used Make at least once. It's widely adopted in the tech industry, or as someone said, "Make is the second best tool to automate anything!". 😄️

After this quick intro, in this post I show a trick that I used for a long time to create Makefile targets with dynamic parameters and autocompletion.

But what does that actually mean? Let's have a look!

Makefile target with a simple parameter

The standard way in Make is to use variables as parameters. So you need to pass the variable externally in the command line:

hello:
    @echo "Hello $(NAME)!"

Then run it, and it will print the hello with the variable.

$ make hello NAME=Ahmed
Hello Ahmed!

It's working as expected, and it could print any name, but what if you have a limit list of variables like this:

$ make build TARGET=prod/backend

In this case, it will be a bit annoying because this doesn't support autocompletion which means:

  1. You need to know and write the whole parameter and the argument every time.
  2. You don't know what values are available to that parameter and could easily misspell the target.

Is there a better way to do it? Yes, dynamic parameters!

Makefile target with a dynamic parameter

Let's have this example, I have a small Kubernetes project with multi-components/environments and I don't really want to define the exact list of envs or components every time I add one. So I need a way to create targets with dynamic parameters that should support autocompletion.

Here how I want it:

# Build all prod components.
$ make build prod

# Or just building a single component from each env.
$ make build prod/backend

# It also works with autocompletion and it will list the available options.
$ make build prod/[tab tab]

backend database frontend

Here is my way to do it! This working example is mainly for Kubernetes, but the same idea could be applied for anything else.

# Makefile

# Silence any make output to avoid interfere with manifests apply.
MAKEFLAGS += -s

SHELL := bash

#
# Kustomize - Build.

KUSTOMIZE_PLUGIN_HOME := $$HOME/.config/kustomize/plugin

# Find directories with Kustomization file.
define kustomize_get_dirs
    $(shell dirname $(shell grep -rl '^kind: Kustomization' $(1)))
endef

# Run "kustomize build" with certain options.
define kustomize_build
  export KUSTOMIZE_PLUGIN_HOME=${KUSTOMIZE_PLUGIN_HOME}; \
  kustomize build $(1) \
    --enable-alpha-plugins \
    --load-restrictor LoadRestrictionsNone
endef

# Find Kustomization files for each env.
COMMON := $(call kustomize_get_dirs, common)
PROD := $(call kustomize_get_dirs, prod)
STAGE := $(call kustomize_get_dirs, stage)

ALL_ENVS := \
  $(COMMON) \
  $(PROD) \
  $(STAGE)

# This is just to load all build folders as dynamic make targets.
$(ALL_ENVS):

# Use Make targets as input to "build" target, but exclude "build" itself.
.PHONY build
build:
    $(call kustomize_build, $(filter-out $@, $(MAKECMDGOALS)))

So what is the trick here?

The trick to creating dynamic parameters with autocompletion for Makefile targets is to load the parameters as targets! Let's take a look at the example mentioned above:

     | This is a predefined Make target.
     |
     +---+
make build prod/backend
           +----------+
           |
           | This is a dynamic (auto-generated) Make target
           | and it's used as an input for the previous target "build".
           | This target is defined/generated by "$(ALL_ENVS):" line.

The steps to apply the same notion to anything:

  1. Create a Make function (A) that creates any patterns you want to apply.
  2. Load the patterns from the first function dynamically using $(): as a target.
  3. Create a Make function (B) that will use the input to do a specific task.
  4. Create a Make target that will use function (B) to execute any other targets as input.

---

That was one of many Make capabilities that make your life easier, and that's why I love Make, where it always fills the gap between automation tools!

That's it! Happy automation :-)

Powered by Blogger.

Hello, my name is Ahmed AbouZaid, I'm a passionate Tech Lead DevOps Engineer. 👋

With 16+ years of open-source contributions, 12+ years of professional hands-on experience in DevOps, and an M.Sc. in Data Engineering from Edinburgh Napier University (UK), I enjoy facilitating the growth of both businesses and individuals.

I specialize in Cloud-Native and Kubernetes. I'm also a Free/Open source geek and book author. My favorite topics are DevOps transformation, automation, data, and metrics.

Contact Me

Name

Email *

Message *

Start Your DevOps Engineer Journey!

Start Your DevOps Engineer Journey!
Start your DevOps career for free the Agile way in 2024 with the Dynamic DevOps Roadmap ⭐

Latest Post

2023 Highlights

Image generated with Craiyon . Finally, 2023 is over! What a year! One more crazy year, but it was the culmination ...

Popular Posts

Blog Archive