EMake for Simple Emacs Lisp Projects
One of the issues in developing an Emacs Lisp package was configuring a test suite. You could set up a Travis CI for an Emacs Lisp project using Cask, but it was not very elegant. You had to specify dependencies both in the library header and the Cask file, which was redundant.
Recently, a utility named EMake has been released. I developed a solution for running package lint before, but EMake can run package-lint, byte-compile, and ERT/buttercup tests and does not depend on Cask. Still, configuring EMake is a manual process. I have created a trivial configuration generator for EMake, so I will introduce it to you. This configuration is based on an example by Sean Allred, who is the author of EMake, and supports a project with a single Emacs Lisp file. I have worked on a tool on project with multiple files, but it is outside of the scope of this post.
Usage
akirak/setup-simple-emake
function generates .gitignore
, .travis.yml
, and Makefile
for the current projectile project. For example, if you are working on a project named foobar.el
, a normal way to generate a test suite is to run the command in the buffer of foobar.el
.
This function basically just copies source files from the example repository. .gitignore
and .travis.yml
usually do not need a modification, but Makefile
may need human intervention. If you do not have any unit tests, then you have to comment out PACKAGE_TESTS
variable and make test
target empty. See an example in my test suite.
How it works
Writing common assets to the local repository
As source files are downloaded from the example on GitHub, their URLs can be built using the following function:
(defun akirak/emake-example-file-url (filename &optional branch)
"Return a source URL of FILENAME at BRANCH."
(format "https://raw.githubusercontent.com/vermiculus/emake.el-example/%s/%s"
(or branch "master") filename))
.gitignore
and .travis.yml
are downloaded using the following function:
(defun akirak/write-emake-assets (&optional branch)
"Download emake configuration source files except for Makefile.
BRANCH is a revision of the files to retrieve."
(let ((project-root (projectile-project-root))
(filenames '(".travis.yml" ".gitignore")))
(dolist (filename filenames)
(unless (or (not (file-exists-p (expand-file-name filename project-root)))
(yes-or-no-p (format "%s already exists. Overwrite it?" filename)))
(user-error "Aborted")))
(dolist (filename filenames)
(let ((fpath (expand-file-name filename project-root))
(url (akirak/emake-example-file-url filename (or branch "master"))))
(url-copy-file url fpath 'overwrite)))))
Generating a Makefile
The previous function is triggered from the following function, which generates a modified Makefile
. It just substitutes PACKAGE_BASENAME
based on the base name of the current buffer. It also replaces wget
with curl -O
, because wget
is not always available. It is indeed a trivial interactive function, but it does its job for a simple project.
(defun akirak/setup-simple-emake ()
"Set up emake for a single-file project."
(interactive)
(akirak/write-emake-assets)
(let* ((project-root (projectile-project-root))
(package-basename (read-from-minibuffer "Package basename: "
(when (eq major-mode 'emacs-lisp-mode)
(file-name-base (buffer-file-name)))))
(project-makefile (expand-file-name "Makefile" project-root))
(url (akirak/emake-example-file-url "Makefile")))
(unless (or (not (file-exists-p project-makefile))
(yes-or-no-p (format "%s already exists. Overwrite it?" project-makefile)))
(user-error "Aborted"))
(url-copy-file url project-makefile 'overwrite)
(find-file (expand-file-name "Makefile" project-root))
(goto-char (point-min))
(re-search-forward (rx bol "PACKAGE_BASENAME" (1+ space)
":=" (1+ space) (group (1+ wordchar)) eol))
(replace-match package-basename nil nil nil 1)
(re-search-forward (rx "wget"))
(replace-match "curl -O")))
Conclusion
Thanks to EMake, now I can set up a test suite that runs on Travis CI more easily than before. I usually don’t have unit tests, because most of my packages are small UI wrappers written in Emacs Lisp. This is far from best practices, but even having only package-lint and byte-compile helps me identify trivial mistakes with my packages, and EMake makes it easier.