Makem.sh
Makefile-like script for linting and testing Emacs Lisp packages
Install / Use
/learn @alphapapa/Makem.shREADME
#+TITLE: makem.sh
#+PROPERTY: LOGGING nil
Note: This readme works with the org-make-toc https://github.com/alphapapa/org-make-toc package, which automatically updates the table of contents.
=makem=.sh is a script that helps to build, lint, and test Emacs Lisp packages. It aims to make linting and testing as simple as possible without requiring per-package configuration.
It works similarly to a Makefile in that "rules" are called to perform actions such as byte-compiling, linting, testing, etc.
Source and test files are discovered automatically from the project's Git repo, and package dependencies within them are parsed automatically.
Output is simple: by default, there is no output unless errors occur. With increasing verbosity levels, more detail gives positive feedback. Output is colored by default to make reading easy.
The script can run Emacs with the developer's local Emacs configuration, or with a clean, "sandbox" configuration that can be optionally removed afterward. This is especially helpful when upstream dependencies may have released new versions that differ from those installed in the developer's personal configuration.
- Contents :noexport: :PROPERTIES: :TOC: :include siblings :depth 0 :END: :CONTENTS:
- [[#installation][Installation]]
- [[#usage][Usage]]
- [[#changelog][Changelog]]
- [[#comparisons][Comparisons]] :END:
- Screenshots :PROPERTIES: :TOC: :ignore (this) :END:
Some example output. The first shows running the =test= rule with verbosity level 1, which shows which tests are run but omits each test's output unless it fails:
[[images/make-test-v.png]]
Increasing the verbosity shows green output for passing tests:
[[images/make-test-vv.png]]
The =lint-compile= rule treats byte-compiler warnings as errors:
[[images/make-lint-compile.png]]
The =all= rule runs all rules and treats warnings as errors:
[[images/make-all.png]]
Of course, with increased verbosity, it also shows which rules did not signal errors:
[[images/make-all-v.png]]
The included =test.yml= GitHub Actions file can be used to easily set up CI, giving output like:
[[images/github-action.png]]
- Installation :PROPERTIES: :TOC: 0 :END:
Copy =makem.sh= into your package's root directory. Optionally, also copy =Makefile=, to make calling the script easier.
- Usage :PROPERTIES: :TOC: :include descendants :END: The =makem.sh= script can be called directly or through a =Makefile=. Use =makem.sh --help= to list available rules. The Emacs library =makem.el= provides a Transient dispatcher (like Magit) to easily run the script from within Emacs with selected options.
:CONTENTS:
- [[#makemsh-script][makem.sh script]]
- [[#transient-menu-makemel][Transient menu (makem.el)]]
- [[#makefile][Makefile]]
- [[#github-action][GitHub Action]]
- [[#github-linguist-statistics][GitHub Linguist statistics]]
- [[#git-pre-push-hook][git pre-push hook]]
- [[#spell-checking][Spell checking]] :END:
** =makem.sh= script
The script may be called directly to specify additional options.
#+BEGIN_EXAMPLE makem.sh [OPTIONS] RULES...
Linter- and test-specific rules will error when their linters or tests are not found. With -vv, rules that run multiple rules will show a message for unavailable linters or tests.
Rules: all Run all lints and tests. compile Byte-compile source files.
lint Run all linters, ignoring unavailable ones.
lint-checkdoc Run checkdoc.
lint-compile Byte-compile source files with warnings as errors.
lint-declare Run check-declare.
lint-elsa Run Elsa (not included in "lint" rule).
lint-indent Lint indentation.
lint-package Run package-lint.
lint-regexps Run relint.
test, tests Run all tests, ignoring missing test types.
test-buttercup Run Buttercup tests.
test-ert Run ERT tests.
test-ert-interactive Run ERT tests interactively.
batch Run Emacs in batch mode, loading project source and test files
automatically, with remaining args (after "--") passed to Emacs.
interactive Run Emacs interactively, loading project source and test files
automatically, with remaining args (after "--") passed to Emacs.
Options: -d, --debug Print debug info. -h, --help I need somebody! -v, --verbose Increase verbosity, up to -vvv. --no-color Disable color output.
--debug-load-path Print load-path from inside Emacs.
-E, --emacs PATH Run Emacs at PATH.
-e, --exclude FILE Exclude FILE from linting and testing.
-f, --file FILE Check FILE in addition to discovered files.
-c, --compile-batch Batch-compile files (instead of separately; quicker, but
may hide problems).
-C, --no-compile Don't compile files automatically.
Sandbox options: -s[DIR], --sandbox[=DIR] Run Emacs with an empty config in a sandbox DIR. If DIR does not exist, make it. If DIR is not specified, use a temporary sandbox directory and delete it afterward, implying --install-deps and --install-linters. --install-deps Automatically install package dependencies. --install-linters Automatically install linters. -i, --install PACKAGE Install PACKAGE before running rules.
An Emacs version-specific subdirectory is automatically made inside
the sandbox, allowing testing with multiple Emacs versions. When
specifying a sandbox directory, use options --install-deps and
--install-linters on first-run and omit them afterward to save time.
Source files are automatically discovered from git, or may be specified with options. Package dependencies are discovered from "Package-Requires" headers in source files, from -pkg.el files, and from a Cask file.
Checkdoc's spell checker may not recognize some words, causing the
lint-checkdoc' rule to fail. Custom words can be added in file-local or directory-local variables using the variable ispell-buffer-session-localwords', which should be set to a list of
strings.
#+END_EXAMPLE
** Transient menu (=makem.el=)
The Elisp file =makem.el= provides a Transient dispatcher (this file should be installed into your Emacs configuration rather than into a project's directory). Use =M-x makem RET= to show it.
[[images/transient.png]]
** Makefile
A default =Makefile= is provided which calls the =makem.sh= script. Call it with the name of a rule and an optional verbosity level, like:
#+BEGIN_SRC sh
Run all rules.
$ make all
Run all lints.
$ make lint
Run all tests.
$ make test
Run ERT tests with verbosity level 1.
$ make v=v test-ert
Run Buttercup tests with verbosity level 2.
$ make v=vv test-buttercup
Run tests with emacs-sandbox.sh in a temporary sandbox.
Implies install-deps=t.
$ make sandbox=t test
Initialize a permanent sandbox directory, DIR (the developer might
choose to recreate it manually when necessary, leaving it in place
to save time otherwise). Then run all linters and tests.
$ make sandbox=DIR install-deps=t install-linters=t $ make sandbox=DIR all #+END_SRC
** GitHub Action
Using Steve Purcell's [[https://github.com/purcell/setup-emacs][setup-emacs]] Action, it's easy to set up CI on GitHub for an Emacs package.
- Put =makem.sh= in your package's repo and make it executable.
- Add [[file:test.yml][test.yml]] (from the =makem.sh= repo) to your package's repo at =.github/workflows/test.yml=. It should work without modification for most Emacs packages.
** GitHub Linguist statistics
Having =makem.sh= in your repository will affect GitHub's language stats provided by [[Https://github.com/github/linguist][Linguist]], which might cause it to be classified as a Shell project rather than an Emacs Lisp one. The [[https://github.com/github/linguist#my-repository-is-detected-as-the-wrong-language][Linguist documentation]] explains how to avoid this. Probably the most appropriate way is to use a =.gitattributes= file to classify =makem.sh= as vendored, like:
#+BEGIN_EXAMPLE sh makem.sh linguist-vendored #+END_EXAMPLE
** git pre-push hook
It's often helpful to run tests automatically before pushing with git. Here's an example of using =makem.sh= in a =pre-push= hook:
#+BEGIN_SRC sh #!/bin/sh
* Commit parameters
Unused now, but good for future reference. See man 5 githooks.
remote="$1" url="$2"
read local_ref local_sha remote_ref remote_sha
* Run tests
Not using sandbox and auto-install, because "git push" shouldn't
cause remote code to be downloaded and executed (i.e. what would
happen by installing packages). It can be done manually when
needed. However, in a CI system running in a container, where
testing in a clean config against the latest available dependency
versions is desired, one could use:
make sandbox=t install-deps=t test
make test #+END_SRC
** Spell checking
Checkdoc's spell checker may not recognize some words, causing the ~lint-checkdoc~ rule to fail. Custom words can be added in file-local or directory-local variables using the variable ~ispell-buffer-session-localwords~, which should be set to a list of strings.
- Changelog :PROPERTIES: :TOC: :ignore children :END:
** 0.8-pre
Changes
- Don't initialize package system when not necessary (as initializing the package system causes some other libraries, like ~url~, to be loaded, which can obscure problems which might occur otherwise). ([[https://github.com/alphapapa/makem.sh/issues/47][#47]]. Thanks to [[https://github.com/josephmturner][Joseph Turner]] for reporting.)
Fixes
- Escape backticks in ~makem.sh~'s ~usage~ function's Bash here-document. (Th
