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.
akirak/setup-simple-emake function generates
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
This function basically just copies source files from the example repository.
.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))
.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
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")))
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.