Makefile Application Presets
Tags: make, programming, cli
Published: 2020-05-27 04:08:23

---
title: Makefile Application Presets
published: true
date: 2020-05-26 13:30:21 UTC
tags: make, programming, cli
canonical_url: https://www.robg3d.com/2020/05/makefile-application-presets/
---

We saw in the [last post](https://lithic.tech/blog/2020-05/makefile-wildcards) how to use Makefile wildcards to write targets like this:

```
migrate-to-%:
    @bundle exec rake migrate[$(*)] 
guard-%:
    @if [-z '${${*}}']; then echo 'ERROR: variable $* not set' && exit 1; fi
logs: guard-STACK
    @awslogs get -w /ecs/$(STACK)_MyService

```

So that we can build CLIs like this:

```
$ make migrate-to-50
Migrating to version 50...

$ make logs
ERROR: variable STACK not set
$ STACK=qa make logs
...

```

(Note the `@` prefix on commands in the Makefile, it avoids the line being echoed to stdout)

This is neat but it only works well for user-supplied values, like `"50"`.There are cases where we want the user to supply an **argument** , but not the **value**.Say, for example, users want to specify ‘production’ to ‘staging’but we don’t want them to remember the URL to the server.

We can use wildcards to dynamically select a Make variable:

```
staging_url:=https://staging-api.lithic.tech
production_url:=https://api.lithic.tech

ping-%:
    curl "$($(*)_url)"

```

And we can use it as so:

```
$ make ping-staging
curl "https://staging-api.lithic.tech"

```

Okay, this example isn’t incredibly useful.But for some clients, we have multiple deployed versions of the same application,and we can use these variables to avoid having to remember where applications are deployed.

For example, let’s say we have 3 versions of a codebase deployed in Heroku: one staging and two production apps.In the Make snippet below, each `_app` variable refers to the name of a Heroku app.We can use that app name to get the database string using the Heroku CLI,and pass that to `psql` (Postgres CLI).

```
staging_app:=lithic-api-staging
production-pdx_app:=lithic-api-production
production-nyc_app:=lithic-api-production-nyc

psql-%:
    psql `heroku config:get DATABASE_URL --app=$($(*)_app)`

```

Now to connect to staging, it’s as simple as:

```
$ make psql-staging

```

If we use Heroku’s Review Apps, we should also support an environment-variable version of these sorts of commands,since the app names are ephemeral. Instead of a wildcard, we’ll require the `APP` environment variable is set:

```
psql-app: guard-APP
    psql `heroku config:get DATABASE_URL --app=${APP}`

```

## Putting It All Together

The example above has a couple small error cases that may confuse users:if `psql` or `heroku` are not on the `PATH`,the command will error with a sort of cryptic error:

```
$ make psql-staging
psql `heroku config:get DATABASE_URL --app=lithic-api-staging`
/bin/sh: heroku: command not found
could not connect to server: No such file or directory

```

Yuck! Can we use something like our lovely `guard-%` target to declare our executable dependencies?

**_You bet we can!_**

```
cmd-exists-%:
    @hash $(*) > /dev/null 2>&1 || \
        (echo "ERROR: '$(*)' must be installed and available on your PATH."; exit 1)

guard-%:
    @if [-z '${${*}}']; then echo 'ERROR: environment variable $* not set' && exit 1; fi

psql-%: cmd-exists-heroku cmd-exists-psql
    psql `heroku config:get DATABASE_URL --app=$($(*)_app)`

psql-app: guard-APP cmd-exists-heroku cmd-exists-psql
    psql `heroku config:get DATABASE_URL --app=${APP}`
```

Now if you’re missing `heroku` or `psql`, you get a nice message:

```
$ make psql-staging
ERROR: 'heroku' must be installed and available on your PATH.
```

Who knew Make could be so _fun_ to use?

## Not done with Make yet

We’ll have one more blog post involving Make next week, along with a dump of a bunch of useful Makefile helpers.Stay tuned!

_This was originally posted on my consultancy’s blog, at [https://lithic.tech/blog/2020-05/makefile-apps](https://lithic.tech/blog/2020-05/makefile-apps). If you have any questions, please leave a comment here!_