I recently migrated my dotfiles from rcm to GNU Stow. I didn’t really have a reason, I wasn’t unhappy with rcm at all, but I saw Stow mentioned somewhere and felt like trying it out. After migrating, I still don’t have strong feelings – both tools are appropriately good and boring – but I can at least now speak to the differences and maybe that would help someone out there decide for their own dotfiles.
Philosophy ๐
I worked with the rcm author, Mike Burns, at thoughtbot when he was first
actively developing it back in 2014. This is how I came to know of, adopt, and
contribute to it. He’s an excellent developer with strong opinions about
simplicity that I very much respect. He preferred a more BSD-like approach to
things vs the “bells and whistles” of GNU equivalents. For example, he liked to
point out that while GNU’s implementation of /bin/true needed 100 or so lines
to do nothing but exit successfully, BSD’s was just an empty file, which just so
happens to have the same behavior when executed. I don’t think that’s the case
any longer, but it speaks to the sort of philosophy he brings to tooling like
rcm.
In contrast, stow feels very GNU. For example, it has that classic home page
with links to documentation as ASCII text, PDF, Texinfo, or HTML “entirely on
one web page”, carefully noting its size in bytes. It also ships a man page
missing just enough details before instructing you to info stow for more.
Usage & Ergonomics ๐
In keeping with the author’s preferences, rcm is built as a suite of
singly-purposed POSIX sh scripts to capture (mkrc), list (lsrc), install
(rcup), and remove (rcdn) dotfiles symlinks. Stow, on the other hand, is
just one Perl library-as-executable (stow) with “action options” (-S, -D,
and -R) to perform similar tasks. I like the rcm approach better; I also
dislike that stow has no lsrc equivalent. I end up using --verbose
together with --simulate to approximate the list operation, poorly.
Directory Layout ๐
The tools each expect a different organization of files. And I find rcm’s
directory layout more intuitive. It assumes that any top-level file x should
be symlinked from $HOME/.x. There are ways to ignore files, or change the
dot-prefixing behavior, but that’s the out-of-the-box default, which is
well-suited to rcm’s duties.
Perhaps an example of its GNU-ness, Stow seems to be a big hammer (effectively,
a package manager) used to drive a very small nail (managing dotfiles). As such,
each top-level directory is considered a “package” that can be stowed. Stowing a
package means symlinking the files within that package at equivalent paths in
the stow directory’s direct parent. This means you need to think about that
extra layer of organization, which felt unnecessary to me at first. You also
have to use a --dotfiles flag, which makes any files named dot-x get linked
as .x.
This all means that using stow for dotfiles requires quite a bit more care
than with rcm. You have to:
- Place the stow directory as a direct child of
$HOME(or set explicit--dirand--target) - Place the actual dotfiles within some parent directory (the “package”)
- Name files
dot-and pass--dotfiles
Here’s an example rcm layout and invocation:
~/.dotfiles/
config/nvim/init.vim
zshrc
% rcup
LINK ~/.config/nvim/init.vim -> ~/.dotfiles/config/nvim/init.vim
LINK ~/.zshrc -> ~/.dotfiles/zshrc
And here is the equivalent for Stow:
# Stow layout
~/.dotfiles/
nvim/
dot-config/nvim/init.vim
zsh/
dot-zshrc
% stow --dotfiles
LINK: ~/.config/nvim/init.vim -> ~/.dotfiles/nvim/dot-config/nvim/init.vim
LINK: ~/.zshrc -> ~/.dotfiles/zsh/dot-zshrc
You don’t have to break out the named packages like I do here. You could use a
single “package” named dotfiles or misc, but I will admit using proper
package names has grown on me. While overly verbose for the case of a single
dotfile that is the same name as the package, it is nice when you can group
things. I like that someone can clone my dotfiles and cohesively and easily
adopt just my nvim setup, or just my Zsh and Haskell setups, etc.
Another interesting use-case is having different packages manage paths within
the same location. With rcm, I had a single local/bin/ directory of scripts
that was installed to ~/.local/bin. With Stow, I can organize scripts that are
related to other configuration alongside it. For example, I now use
git/dot-local/bin for any custom git subcommmands, and separately I have
haskell/dot-local/bin for scripts used to automate tasks within Haskell
projects.
rcm supports host- and tag- prefixed directories. The former are installed
if $(hostname) matches and the latter are installed if the given tag is
requested (by config or CLI arguments). This provides some of the same
functionality as packages but it’s not as elegant or flexible.
Functionality Gaps ๐
There are only two features in rcm that Stow lacks, at least that I’ve found so
far: hooks/, and -c.
With rcm, you can add a conventionally-named executable to hooks/, such as
pre-up, which rcm will run at the appropriate time (in this case, at the start
of rcup). I used this to do things like clone down my vim plugin manager
plugin before linking, or to fix some permissions after. With Stow, I believe I
would just need my own script(s) that run code before or after a stow
invocation. That said, I found hooks to be a double-edged sword that I actively
disabled (e.g. mkrc -k) just as often as I’d expect them to run. In my
experience, I only really benefited from them on the “first run” use-case, which
I could solve by having my system provisioning script, which was
invoking rcup and will now invoke stow, run things itself at the appropriate
time.
Lastly, rcm has a -c flag, which instructs it to copy the dotfile instead of
linking it. Generally speaking, this is because there may be tools that don’t
like their configuration files being symlinks. I ran into this only once, when
some AWS-related tool didn’t like ~/.aws/config being a symlink. I’ve since
stopped using that tool, so I no longer need this behavior.
I might argue, if one were to have strong opinions about simplicity, that a tool that can’t follow a symlink to its configuration is in fact broken and we shouldn’t be adding “bells and whistles” to support them.