# Cask, a build and test tool for Emacs Lisp packages

> Cask installs a package's dependencies from MELPA, byte-compiles it, and runs its tests against a chosen Emacs version, driven by a small file and a Makefile. Its author is blunt about the documentation at cask.readthedocs.io, about Windows, and about the cost of the discipline Cask imposes on you.

**cask/cask** — Project management tool for Emacs

- Repository: https://github.com/cask/cask
- Website: http://cask.readthedocs.io
- Stars: 1,326 · Forks: 149
- Language: Emacs Lisp
- License: GPL-3.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/cask-cask

## An npm for Emacs Lisp, with the registry hardcoded to MELPA

Cask is a project management tool for Emacs Lisp, written in Emacs Lisp itself. The README frames it historically: it was an early npm, uv or cargo for Emacs Lisp, with hardcoded ties to MELPA, which it calls an early npm registry, PyPi or crates.io.

The framing continues with an assessment of the current state. As of September 2026, the README says, Emacs still lacks an npm-like build tool. That is the gap Cask occupies, and the hardcoded tie to MELPA is both its main convenience and its main constraint, since a package hosted somewhere other than MELPA is not what this tool was built to fetch.

The audience is the author of an Emacs package who wants three things done consistently: install the dependencies, compile the code, and run the tests. The tool's own philosophy is visible in the questions it answers later on, including the blunt observation that most Emacs packages do no testing at all. Cask exists to make the disciplined path the cheap one, and the price of that is a per-project configuration file and a Makefile, which the README cheerfully calls egregious boilerplate.

The project is GPL-3.0 licensed, with the licence text in the COPYING file, and the last push to the repository was on 2026-09-24. It publishes no tagged releases, so the version of Cask you have is whatever you cloned.

## Three lines in a Cask file, and a source list

The configuration surface is a single file called Cask, and the baseline version of it is short enough to read in one breath:

```elisp
(source gnu)
(source melpa)

(package-file "your-main-file.el")
```

The two source lines declare where packages come from, which is where the hardcoded MELPA tie shows up. The package-file line names your main file, and it is the anchor for everything else: Cask needs to know which file is the entry point of the package in order to install its dependencies and compile it.

With nothing more than that baseline, the README says you can run most of the commands described in the documentation. The file is meant to live at the root of the project, alongside the Makefile, and the repository's own layout follows that pattern: Cask, Makefile, README.makefile, README.org, cask.el, cask-cli.el, cask-bootstrap.el, a bin/ directory, package-build/, features/, fixtures/ and a test/ directory.

The split into cask.el, cask-cli.el and cask-bootstrap.el is worth noticing, because it matches the guidance given later in the README about how Cask is meant to be used. The tool has moved towards being a command line program that happens to be written in Emacs Lisp, with the elisp library role reduced, and the file layout reflects that direction rather than fighting it.

## The Makefile contract: package-directory, install, compile, clean

The typical Makefile is quoted in the README as boilerplate, and it is worth reading line by line because it names the operations you will use most.

```makefile
export EMACS ?= $(shell command -v emacs 2>/dev/null)
CASK_DIR := $(shell cask package-directory)

$(CASK_DIR): Cask
	cask install
	@touch $(CASK_DIR)

.PHONY: compile
compile: cask
	cask emacs -batch -L . -L test \
	  -f batch-byte-compile $$(cask files); \
	  (ret=$$? ; cask clean-elc && exit $$ret)
```

The first line decides which Emacs the build uses, defaulting to whatever `emacs` is on the path. The second asks Cask where the package directory lives, and the rule that depends on it runs `cask install` when the Cask file is newer than that directory, which is the classic Make dependency trick expressed with a tool-specific query.

The compile target is the interesting one. It asks Cask for the list of files with `cask files`, byte-compiles them in batch mode, and then always runs `cask clean-elc` before exiting, passing the original return code through. That ordering matters: a compile that leaves .elc files lying around will break a later byte-compile, so the cleanup runs on failure as well as success.

The project's own Makefile shows the same ideas in a fuller form, with a default CASK of bin/cask, an EMACS default of emacs, targets named readlink, spaces, unit and ecukes, a servant directory for package serving, and Sphinx for building documentation into build/doc.

## The EMACS variable, and a CI matrix across four Emacs versions

One environment variable does the work of testing against different Emacs builds. The README says the EMACS environment variable governs which Emacs binary to test, and gives the example of running ert tests under an older Emacs:

```bash
EMACS=emacs-25.3 cask emacs --batch -l mytest.el -f ert-run-tests-batch
```

The same variable appears in the Makefile as an overridable export, so a project can pin it in one place and override it per invocation.

The GitHub Actions example shows how this turns into a matrix. The workflow checks out the repository, uses purcell/setup-emacs to install a specific Emacs version, and runs a matrix over versions 26.3, 27.2, 28.2 and 29.2. Two caches are configured, one for the .cask directory in the workspace and one for the ~/.cask directory that holds the Cask executable, and the Cask executable is only installed when that second cache misses, using cask/setup-cask with the version pinned to snapshot. The final step puts $HOME/.cask/bin on the path.

That is a fair amount of machinery, and the author is upfront that this is the price of the tool. Asked whether Cask makes things more complicated, the answer is yes, because it forces you to test your package under multiple versions of Emacs, with the observation that most packages do no testing at all.

## Bitrot, the .cask directory, and the honest answer about Windows

The FAQ in the README is the most useful part of the documentation, and it is written in a voice you will not find in most project readmes.

On why a working setup stops working, the answer is bitrot and weak hacks, primarily, with a concrete remedy: always try removing ~/.emacs.d/.cask, since the author is liable to make backwards-incompatible changes. That is a real warning about the tool's versioning, not a rhetorical one, and it is worth taking literally before debugging anything else.

On Windows, the answer is that if you use Emacs on Windows you are not doing it right, with the concession that a sufficiently motivated PowerShell user could finesse the Makefile for a typical Windows environment. So: unsupported in practice, not impossible in principle.

On the install directory, the answer explains that after surveying cross-platform schemes such as systemd-path and XDG_DATA_HOME, Cask falls back to the old, unspoken standbys of ~/.local/bin and ~/bin. If neither exists, make install gives up with an error about not knowing where to install. The workaround is to specify the directory yourself:

```bash
DESTDIR="$HOME/path/to/install/dir" make install
```

The Makefile in the repository uses bash explicitly, and it resolves the shell and the readlink utility at make time, which is a hint that portability is handled by asking the environment rather than by assuming.

## The documentation site and the tool have drifted apart

The most striking passage in the README is a warning about the project's own hosted documentation. Asked why everything said at cask.readthedocs.io is inconsistent with the tool, the answer is that the reader should disregard nearly everything there, and specifically should not trust the Quickstart page.

The reason given is a change in what Cask is. It is now largely a command line tool, independent of whatever you do inside Emacs, and the author advises against requiring cask in your dot.emacs. The recommendation to do that, the author says, belongs to the bygone era of pallet; the README says you are on your own.

The same drift explains another deprecation. The cask exec invocation is deprecated, and the README no longer recommends cask exec ert-runner or cask exec ecukes. The.ert-runner file at the repository root is a remnant of the older arrangement, and the Makefile still carries an ecukes target among its test prerequisites, which is what a project in the middle of that transition looks like in practice.

For a reader deciding whether to adopt Cask, this is the section to slow down on. A tool whose author says the published quickstart is wrong is a tool with real implementation knowledge behind it and a documentation debt in front of it. You will be learning it from the source, the Makefile and the FAQ rather than from the website.

## What Cask is not, and what it costs

Cask is not a package registry, and it is not a package manager in the sense people usually mean. It is a build and test harness that fetches dependencies from an archive you name, compiles your package, and runs your test suite against the Emacs versions you choose. The registry problem is still unsolved, which the README says plainly, and MELPA is a dependency of the design rather than a choice you can route around without abandoning the tool.

The cost of using it has three parts. There is the configuration, which the README calls egregious boilerplate and which in practice means a Cask file, a Makefile and a CI workflow. There is the ongoing maintenance, since the author warns that backwards-incompatible changes happen and the recommended fix is to delete the .cask directory and start over. And there is the compatibility work, because supporting several Emacs versions means your own package has to work on all of them, which is a discipline most Emacs packages do not currently have.

What you get in return is the last of those, made cheap. A package that builds and tests cleanly on 26.3 through 29.2 under a single command is a package users can install with confidence, and the machinery around it, the servant package server, the package-build directory, the fixtures and features used by the test suite, and the Sphinx documentation build, all exist to support that goal rather than to add features of their own. The README's own framing is the fairest summary available: Emacs still lacks the tool, and Cask is an early attempt at it that has drifted away from its own documentation.

## Conclusion

Cask fits an author of an Emacs Lisp package who wants dependency installation, byte-compilation and ert runs to be reproducible across the Emacs versions their users actually run, and who is prepared to own that CI configuration. It does not fit anyone who wants a one-command package manager, and the author himself says Emacs still lacks one as of September 2026. Before adopting it, read the Cask file in your own project and remember that the documentation site is not authoritative, because the author asks you to disregard nearly everything there especially the Quickstart page,, and that a broken environment is usually fixed by removing ~/.emacs.d/.cask. The licence is GPL-3.0, the last push was on 2026-09-24, and the repository publishes no tagged releases.

## FAQ

### How do I install Cask for an Emacs Lisp package?

Clone the repository and run make -C cask install. The install directory defaults to ~/.local/bin or ~/bin, and if neither exists the install fails, in which case you can point it somewhere with DESTDIR="$HOME/path/to/install/dir" make install.

### How do I run ert tests against a specific Emacs version with Cask?

Set the EMACS environment variable, which governs which Emacs binary is used, for example EMACS=emacs-25.3 cask emacs --batch -l mytest.el -f ert-run-tests-batch. The Makefile exports the same variable with a default of emacs on the path.

### What should I put in the Cask file of my package?

The baseline is a source line for gnu, a source line for melpa, and a package-file line naming your main file, for example (package-file "your-main-file.el"). With that baseline the README says most of the documented commands work.

### Why does Cask break after it was working, and what is the fix?

The README attributes it to bitrot and weak hacks, and recommends removing ~/.emacs.d/.cask, noting that backwards-incompatible changes are liable to happen. It also states that the documentation at cask.readthedocs.io, especially the Quickstart page, should be disregarded.

### Is the cask exec invocation still supported?

No. The README marks the cask exec invocation as deprecated and no longer recommends cask exec ert-runner or cask exec ecukes. Cask is now largely a command line tool, and the author advises against requiring cask in your dot.emacs.

## Sources

- [cask/cask on GitHub](https://github.com/cask/cask)
- [Issues](https://github.com/cask/cask/issues)
- [License: GPL-3.0](https://github.com/cask/cask/blob/master/LICENSE)
- [Project website](http://cask.readthedocs.io)
- [README](https://github.com/cask/cask/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cask-cask
