emacs package config repo thing
Nix 61%
Just 27%
Emacs Lisp 12%
<1%

README.org
* Nix Meta-Flake for Developing Emacs Lisp Projects
This is a Nix flake that lets you configure checking of Emacs Lisp packages (both locally and on CI).

There are [[#alternatives][several alternatives]] for Emacs Lisp CI, but this project is unique in that it is powered by Nix and provides the following most notable features:

- In continuous integration, this project makes it easy to run byte-compile (and testing in the future) on all Emacs versions supported by your package(s). It is aimed at authors who maintain a number of Emacs Lisp packages.
- In local usage, it can integrate with [[https://github.com/cachix/git-hooks.nix][Nix Git hooks]], so it even nicely supports projects that contain multiple programming languages.

This is a meta-framework which allows you to reuse a single flake across multiple projects.
You can fork this repository to personalize your workflows and then use it in all of your Emacs Lisp projects.
If your project is complex enough to require a specific flake configuration, you can copy the ~flake.nix~ from this repository and use it in the single repository.
The common code is maintained in [[https://github.com/emacs-twist/elisp-rice][a separate repository]] as a [[https://flake.parts/][flake-parts]] module, so you don't have to merge upstream bugfixes by hand.

To use this project, you will need some experience with Nix, but the resulting configuration of each project will be extremely concise.
** Installation
To use this flake on your machine, you need the following requirements:

- Git (required)
- Nix with flakes enabled (required)
- [[https://just.systems/man/en/][just]] (optional but strongly recommended for usability)
- [[https://cli.github.com/][GitHub CLI]], a.k.a. ~gh~ (optional but useful in some situations)
*** (Optional) Fork this repository
Because of security concerns, it is discouraged to use this ~rice-config~ repository directly. Instead, I would recommend that you fork the repository and add ~flake.lock~ to it.

This repository depends on third-party repositories via its flake inputs. The inputs are not locked, which is open to supply-chain attacks.
The upstream projects of this flake are well trusted in the Nix community, and they should be safe as long as you run pure evaluation of Nix.
However, I am irresponsible for any security problem caused by any of the projects maintained outside of this organization.
Also note that this repository may add new dependencies in the future.
In such a situation, you won't be notified of the change beforehand, if you use the flake without locking the input.
For the reasons described above, I would recommend tracking the inputs for yourself, rather than to be at stake of unknown third-party developers.
The maintenance effort would be near constant no matter how many projects you maintain, given that you will have only one flake for all of them.

First fork this repository on GitHub and clone it to your computer:

#+begin_src sh
  git clone github:YOUR-USER/rice-config
#+end_src

Then generate ~flake.lock~:

#+begin_src sh
  cd rice-config
  nix flake lock
#+end_src

Commit the lock file and push it to your forked repository.
*** Create a MELPA recipe for your package
Twist, the Nix library of Rice, primarily supports MELPA, and Rice currently targets projects that are submitted to MELPA.

If your package is not available on MELPA yet, you have to create a MELPA recipe as if you are planning on submission in the future.

1. First fork [[https://github.com/melpa/melpa][MELPA]] on your GitHub account.
2. Create a recipe for your package and save it to ~recipes/PACKAGE-NAME~ in the repository. [[https://github.com/melpa/melpa?tab=readme-ov-file#recipe-format][The recipe format]] is S-exp-based, and you can edit it using ~lisp-data-mode~ on Emacs.
3. Commit the recipe and push it to a branch on your remote repository.

An example of the most basic recipe is as follows:

#+begin_src elisp
  (hello :fetcher github :repo "your-user/hello.el")
#+end_src
*** Add flake.nix and (optional) justfile
You have to add ~flake.nix~ to your individual projects.
While it is possible to use its flake API only with ~nix~ command, the commands are quite verbose and hence your developer experience would be painful.
On your machine, it is recommended to use [[https://just.systems/man/en/][just]] (which is like a modern Make for running commands) for the development workflow.

I have created [[file:template][a flake template]] that provides a boilerplate with ~flake.nix~, ~justfile~, and some other typical configuration files.
With this template, you can get started with Rice just by editing parameters.

Using the template, you can add the configuration files to your existing project by running the following command:

#+begin_src sh
  nix flake init -t "github:emacs-twist/rice-config?dir=templates"
#+end_src

Because of the ~?~ character, you probably have to quote the URL, though it depends on your shell.

~flake.nix~ in an individual project is just an input to this flake (i.e. ~rice-config~), and it is designed to be minimal.
You can initialize the file from the flake template, you only have to set ~elisp-rice.packages~ in the outputs.
A minimal example is as follows:

#+begin_src nix
  {
    outputs = {...}: {
      elisp-rice = {
        packages = ["hello"];
      };
    };
  }
#+end_src

In this example, ~hello~ is the name of your package.
If your package contains multiple source files, you only specify the main library.
If your repository contains multiple packages, you have to specify all of them in ~packages~.

You can also set up tests by setting ~tests~:

#+begin_src nix
  {
    outputs = {...}: {
      elisp-rice = {
        packages = ["hello"];
        tests = {
          buttercup.enable = true;
        };
      };
    };
  }
#+end_src

Open ~justfile~ to adapt it to your project.
You only need to edit variables.
You must set ~package~, and you may also have to set ~rice-config~, ~melpa~, and ~emacs-version~ as well.

The ~justfile~ only supports a single package and a single Emacs version, but you can override it at runtime using ~--set~ option of ~just~ or even edit ~justfile~ without committing it to the repository.
*** (Optional) Set up a CI on GitHub Actions
We provide reusable GitHub workflows for running common checks on CI.
Check out [[https://github.com/emacs-twist/elisp-workflows][elisp-workflows]] repository.
You can call the workflows directly, or fork the repository and maintain it for yourself, if you worry about security.
Note that the workflows don't require the ~justfile~.
** Usage
Once your project is set up, this ~rice-config~ repository (or its fork) will become a Nix flake that provides the interface.
You will use the flake with the following inputs overridden for your project:

- ~rice-src~ :: The source repository of your project.
- ~rice-lock~ :: The lock directory tracking the Emacs Lisp dependencies of your project. This is optional if your project only depends on built-in libraries.
- ~melpa~ :: A repository containing the recipe for your project.

You *could*​ browse the flake interface using ~nix flake show~ as follows:

#+begin_src sh
  nix flake show github:your-user/rice-config \
      --override-input rice-src "path:$PWD" \
      --override-input rice-lock "path:$PWD/.rice-lock/default" \
      --override-input melpa github:your-user/melpa/your-package
#+end_src

Note the above command (or any evaluation on this flake) *doesn't work*​ if the lock directory has a missing dependency.
See the next subsection on how to initialize the lock directory.

However, it is tedious to repeat the ~--override-input~ options for all of the commands, so the ~justfile~ provides the following shorthand:

#+begin_src sh
  just show
#+end_src

~just show~ also accepts Nix options:

#+begin_src sh
  just show --json
#+end_src

Another way to inspect the flake is ~nix eval~, and the ~justfile~ provides a shorthand for the command:

#+begin_src sh
  just eval melpaRecipes --apply builtins.attrNames --json
#+end_src

You can browse the recipes in ~justfile~ from the command line:

#+begin_src sh
  just -l
#+end_src
*** Initializing a lock directory
The ~justfile~ contains a recipe for initializing a lock directory.

#+begin_src sh
  just lock
#+end_src

The lock directory will be created in ~lock-dir~ defined in the ~justfile~.
By default, the location is ~.rice-lock/default~, but you can create multiple lock directories by overriding the variable:

#+begin_src sh
  just --set lock-dir .rice-lock/secondary lock
#+end_src

However, it is recommended to use the minimum version of Emacs supported by your package to generate a lock directory that contains all dependencies:

#+begin_src sh
  just --set emacs emacs-lowest lock
#+end_src

The above command generates a lock directory that contains all dependencies to make your package run on the minimum supported version of Emacs.
Generally speaking, a newer version of Emacs has more built-in packages, so it's safe to use the oldest version of Emacs.
You can still create multiple versions of lock directories.

Note: You can set ~emacs~ to any package provided by [[https://github.com/purcell/nix-emacs-ci][nix-emacs-ci]].
This includes stable release versions (e.g. ~emacs-28-1~) and snapshot versions (i.e. ~emacs-snapshot~ and ~emacs-release-snapshot~).
Furthermore, rice adds an aliased version named ~emacs-lowest~ which points to the minimum Emacs version supported by your package(s).

To override an existing directory, you need to set ~--force~ option:

#+begin_src sh
  just --set emacs emacs-lowest lock --force
#+end_src

The lock command supports some other options.
You can view the help:

#+begin_src sh
  just lock --help
#+end_src
*** Byte-compile
In recent years, the byte-compiler of Emacs has made a significant advancement, and it is one of the most useful tools for linting Emacs Lisp code.
Rice primarily aims at integrating the byte-compiler into your development workflow.
**** Running byte-compile on a specific Emacs version
#+begin_src sh
  just check-compile
#+end_src

This compiles all source files in the package in a sandboxed environment of Nix.
It is generally suitable for CI, but you can run it locally for faster feedback, without waiting for CI.

It runs ~nix build~ on one of the outputs under ~checks~ (e.g. ~#checks.x86_64-linux.hello-compile-emacs-29-4~) with the inputs overridden.
**** Shell for byte-compiling
#+begin_src sh
  just shell-compile
#+end_src

Once you enter the shell, you can byte-compile individual source files by running a wrapper script:

#+begin_src sh
  elisp-byte-compile SOURCE..
#+end_src

This is more suitable during development, because you can re-run the command after you edit a source file without reloading the Nix environment.
However, I would recommend use of watch mode, which I will describe later.

Technically, this ~justfile~ recipe is a wrapper for one of the outputs under ~devShells~ (e.g. ~devShells.x86_64-linux.emacs-29-4-for-hello~).
The shell environment provides Emacs with the package dependencies (but not the package itself).
**** Watch mode
You can re-run byte-compile whenever a file is changed:

#+begin_src sh
  just watch-compile
#+end_src

It enters the same shell as ~just shell-compile~ but runs a command in the shell.
Internally, it uses ~entr~ to watch file changes.
~entr~ is bundled in the shell but light on size.
*** Testing (manual/automated)
Testing should be done on all supported Emacs versions, which can be tedious for package authors and maintainers.
This is another area where Rice is trying to improve.

If you have set up a test suite via ~tests~ output of the flake, you can run tests using ~test-*~ recipe:

#+begin_src sh
  just test-buttercup
#+end_src

It runs ~test-buttercup-with-emacs-snapshot~ package of the flake with the inputs.
**** Shell with a specific Emacs version
Instead of running the pre-configured test suite for your package, it is also possible to run a given command in a package-enabled Emacs environment.

You can enter a shell with the package(s) available:

#+begin_src sh
  just shell-emacs
#+end_src

You can run ~emacs -q~ to start Emacs without your init file loaded.
That environment will be suitable for manual testing your package in a reproducible manner.
You can also run Emacs with ~-batch~ flag to dispatch automated testing.

This enters a shell environment with one of the packages under ~packages~ (e.g. ~packages.x86_64-linux.emacs-29-4-with-packages~).
The recipe accepts options, which are passed to ~nix shell~, so you can run a test suite directly:

#+begin_src sh
  just shell-emacs --command emacs -batch -L tests -l hello-test
#+end_src
** Plans
This project does not support all common types of checking in Emacs Lisp yet.
The following should be covered in the future:

- [[https://github.com/purcell/package-lint][package-lint]]: This requires ~package.el~ to download dependencies, so it requires internet connection, which cannot be run in a pure Nix environment. It should be an application provided under ~packages~ output of the flake.
- ~checkdoc~ and other minor checks that can be run statically: This should be an optional addition to ~checks~. Only one Emacs version (either the latest release or a snapshot version) would be enough.
- Add support for more test packages, e.g. ERT.

The above tasks are likely to require enhancement of the rice module.
** Alternatives
:PROPERTIES:
:CUSTOM_ID: alternatives
:END:
As mentioned earlier, several alternatives do exist for checking Emacs Lisp packages.
For information on eariler projects, see [[https://github.com/alphapapa/makem.sh/?tab=readme-ov-file#comparisons][comparisons]] by the author of [[https://github.com/alphapapa/makem.sh/][makem.sh]].
Below is a list of some (but not comprehensive) recent projects:

- [[https://emacs-eask.github.io/][eask]], which seems actively developed and reliable
- [[https://github.com/riscy/melpazoid/][melpazoid]] by one of the maintainers of MELPA
- [[https://github.com/leotaku/elisp-check][elisp-check]], which works great on GitHub Actions

Rice doesn't aim to be a superset/competitor of these projects.
It is:

- Built with Nix (and [[https://github.com/emacs-twist/twist.nix][twist.nix]]) to support reproducibility, integration with native (non Emacs Lisp) dependencies, avoiding S-exp (or any other custom) DSLs. The nature of defining everything in Nix would allow end-to-end/UI testing involving the Emacs Lisp package(s) under test.
- Integrating [[https://github.com/purcell/nix-emacs-ci][nix-emacs-ci]] to support generating an up-to-date CI matrix, without needing manual configuration.
- Designed to help the user maintain a number of Emacs Lisp projects, by centralizing the configuration in a single place.

Rice is an immature project and does not support as many checks as earlier projects.
Because of this, I won't provide a list of feature comparisons yet.
** Contributing
An improvement or enhancement to this flake is welcome.
Please feel free to open a ticket to request a feature or send a PR to implement a planned feature.