Skip to main content
~mattyblog

Using Makefiles for Stuff aroung the Home (dir)

I use a Makefile to move my emacs configuration from its git repo in my home directory to the emacs config directory. There are a variety of better ways a person could move files from one place to another and a Makefile probably wouldn't be the wisest first choice. Writing a script to execute a copy command would be very straightforward and absolutely be a more sane choice.

I wanted to learn a little more about the tool and this seemed like a fun way to waste some time doing it. I did waste a lot of time doing it, but I learned a lot about the tool. I learned enough to make use of it in more useful circumstances. So it wasn't a total wash.

I will say this for using a Makefile instead of a script: it is weirdly satisfying only seeing the files you've modified get deployed to where they need to go. I thought it might be fun to go through how that works in the context of my emacs Makefile.

After this experiment I ended up adopted Makefiles for several things including ersatz.website. It is surprisingly useful and fairly quick to write once you know what you're doing.

Emacs Makefile: how it works

The Makefile I use for my emacs stuff isn't perfect by any means but it works pretty good. You can view my Makefile in its entirity on sourcehut here.

The most important part is the phony all target:

.PHONY: all
all: $(ELISP_OUT_DIR) \
	$(INIT_ELISP_OUT_DIR) \
	$(SNIPPETS_OUT_DIR) \
	$(ELISP:./%.el=$(ELISP_OUT_DIR)/%.el) \
	$(INIT_ELISP:./%.el=$(INIT_ELISP_OUT_DIR)/%.el) \
	$(SNIPPETS:./snippets/%=$(SNIPPETS_OUT_DIR)/%)

The all target does a couple important things:

  1. It creates all the required directories if they don't already exist.
  2. It sets up the pattern rules that Make will use to translate input files to output files.

These pattern rules are defined where they are needed: as dependencies to the all rule. When running the all target - the default target because it is the first target defined in the file - Make will also evaluate the target's dependencies. It will effectively do a topological sort of your targets and their dependencies to figure out what the required order of operations is.

The pattern rules are the real core of Make. They are what really drive the automated incremental builds based on file modified timestamps. We can look at my elisp pattern rule to see how this works. In the above snippet, $(ELISP:./%.el=$(ELISP_OUT_DIR)/%.el) tells Make that files in the pattern ./%.el should be translated to $(ELISP_OUT_DIR)/%.el.

You will notice that the input and output patterns share what is called a "stem" represented by the %. It represents a non-empty string. The stem in the input and output rules need to match.

You can use these rules when defining your Make targets. In the below snippet I am using the output pattern for my elisp files as the target, and the input pattern as the dependency for that target.

$(ELISP_OUT_DIR)/%.el: ./%.el
	install $< $@

We told Make that we need to execute this target in the definition of our pattern in the all target. The body if the target simply runs the install command using two automatic variables.

With all that set up in the Makefile, I can just type make in the terminal, and make will figure out what %.el files changed by comparing the timestamps of the input and output files and then execute the rule for any inputs that are newer.

Whew. Was it worth doing?

I learned how to use some basic Makefile techniques, and it was kind of fun to figure it all out. This is an extremely straightforward Makefile, and I am guessing it could be made even simpler with some smarter pattern or rule definitions. I am not a Make expert, and there is a lot about it I am sure I don't know. It really feels like I was learning some kind of long lost sorcery trying to implement my file.

I was able to leverage a lot of this in the Makefile I use for this website. I build the index.html page separately from the 11ty blog. The index.html page also has its own dependencies! There is a clojurescript compilation step for the generative art on the index.html page that will only be recompiled if the relevant clojurescript stuff changes. It is really nice to not have to recompile that stuff or regenerate the whole blog if I only want to update some styles in my index.html. That's why learning Make is really useful.

I started learning Go recently and was really surprised to discover that a lot of folks writing Go code actually use Makefiles to build their binaries as well!

If you know how I can improve on the Makefile I use for my emacs configuration, you should get in touch with me and tell me how to make it better!

∗ ∗ ∗