alphapapa / makem.sh

Makefile-like script for linting and testing Emacs Lisp packages
GNU General Public License v3.0
163 stars 13 forks source link
emacs

+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.

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]]

Copy =makem.sh= into your package's root directory. Optionally, also copy =Makefile=, to make calling the script easier.

:CONTENTS:

** =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.

  1. Put =makem.sh= in your package's repo and make it executable.
  2. 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.

** 0.8-pre

Changes

Fixes

Compatibility

** 0.7.1

Fixes

** 0.7

Added

Fixed

Credits

** 0.6

Added

Fixed

Internal

** 0.5

Changed

** 0.4.2

Fixed

** 0.4.1

Fixed

** 0.4

Added

Fixed

** 0.3

Added

Changed

** 0.2.1

Fixed

** 0.2

Added

** 0.1.1

Updated

** 0.1

First tagged release.

There are several similar tools, each of which is slightly different.

Notes:

** [[https://github.com/cask/cask][Cask]]

Cask is a classic Emacs package project management tool. It's powerful and well-documented. It's much more sophisticated than =makem.sh=.

** [[https://github.com/doublep/eldev][Eldev]]

Eldev is a powerful, flexible tool. It has many features and can be extended and configured for each project. It's designed to be much more sophisticated than =makem.sh=.

** [[https://github.com/vermiculus/emake.el][emake]]

=emake= is intended for continuous integration testing. It is powerful and well-documented, and provides some more specific flexibility than =makem.sh=.

** [[https://gitlab.petton.fr/DamienCassou/makel/][makel]]

Of these alternatives, =makel= is most like =makem.sh=. It's simple and requires little configuration.

Inspired by Damien Cassou's excellent [[https://gitlab.petton.fr/DamienCassou/makel][makel]] project.

Bug reports, feature requests, suggestions — /oh my/!

GPLv3

Local Variables:

eval: (require 'org-make-toc)

before-save-hook: org-make-toc

org-export-with-properties: ()

org-export-with-title: t

End: