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:
- It creates all the required directories if they don't already exist.
- 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.
$<refers to the first item in the list of dependencies for the current target: lets say a file calledsome-elisp.elthat matches the input pattern we defined$@refers to the expanded target. In our case here withsome-elisp.el, it will expand to~/.config/emacs/some-elisp.el.
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!
- Previous: Spring Cleaning