Unifying Projects with Makefiles

At work, I end up spending time in many different projects. Some of these are similar, some are quite different. The tasks I want to do in them are usually the same: set up the project, initialize data, run tests, run the service.

Most companies don’t have a single stack anymore. You might have a few projects that use Elixir, and a few that use Rails (even those might have various versions of Rails). Sometimes my changes in a Rails project require me to run an Elixir service on my machine, but I have no clue how to set that up.

Take migrations as an example. Rails has you run ./bin/rails db:migrate and in Elixir I would do mix ecto.migrate. Both commands do the same thing, but have different syntaxes.

What I like is commonalities. My process for getting any project up and running should be the same, no matter which stack it’s on. The steps for setting up a repo should also be easy to understand and easy to learn from if I care to peek under the hood.

I ended up going with setting up a Makefile in each project at Chime. In the above example, I should just be able to make migrate in either project, and presto! My project does the right thing.

But… Why make?

I know, I know! Make is that thing you used forever ago to coordinate how your C++ files compiled to avoid linking errors. Or at least, I think that’s what I used to use them for. We’re going to use them as a glorified command line execution system — something far more benign.

Make is available in pretty much every linux environment. It’s straight forward, and it’s easy to debug. It doesn’t have a ton of bells and whistles, but that means it will always just work.

When a developer clones a new repo at Chime, they can take a quick glance at the Makefile and know what has to happen (also, keeping README’s up to date is key). I can run make build, then make setup_db followed by a make test and it won’t matter if it’s Ruby, Elixir, or some other crazy stack.

The best part? You can actually group make commands into similar files and then include them in your main Makefile. We use this to DRY up common make commands that relate to our mysql instance, as well as other things like bringing up and down docker-compose environments. You can do that by adding this to your Makefile:

include Makefile.common

You could have things like Makefile.rails and Makefile.phoenix to dry things up even more.

Tips & Tricks

Make it self-documenting! One of my favorite parts about this, is adding the following to my top level common Makefile:

This will pull any comments that start with #: out and display them nicely for anyone who’s looking at what’s available.

You can also use environment variables to pass parameters. To see a full example, here’s one that I have set up for a personal project of mine:

The end result of running make help looks like this:

Documentation is good.

Do you have some tips or tricks you can share? I’d love to hear them!