GitHub - doublep/eldev: Elisp Development Tool
source link: https://github.com/doublep/eldev
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
README.adoc
Eldev
Eldev (Elisp Development Tool) is an Emacs-based build tool, targeted solely at Elisp projects. It is an alternative to Cask. Unlike Cask, Eldev itself is fully written in Elisp and its configuration files are also Elisp programs. If you are familiar with Java world, Cask can be seen as a parallel to Maven — it uses project description, while Eldev is sort of a parallel to Gradle — its configuration is a program on its own.
- Brief overview
- Requirements
- Installation
- Getting started
- Initializing a project
- Project dependencies
- Loading modes
- Build system
- Testing
- Quickly evaluating expressions
- Running Emacs
- Executing on different Emacs versions
- Continuous integration
- Filesets
- Extending Eldev
- Influential environment variables
- See also
Brief overview
Eldev features:
-
Eldev configuration is Elisp. It can change many defaults or even define additional commands and options.
-
Built-in support for regression/unit testing.
-
There are four levels of configuration — you can customize most aspects of Eldev for your project needs and personal preferences.
-
You can use local dependencies, even those that don’t use Eldev (though some restrictions do apply, of course). This is similar to Cask linking, but with more flexibility.
-
Eldev is fast.
Drawbacks:
-
Eldev doesn’t run the project being tested/built in a separate process, so it is not as pure as Cask. However, Emacs packages won’t live in a sterile world anyway: typical user setup will include dozens of other packages.
-
Eldev depends much more on Emacs internals. It is more likely to break with future Emacs versions than Cask.
-
Eldev is a recent development and is not widely used, so there can be bugs. However, Eldev contains a reasonably large regression test collection, so it is not completely untested.
Requirements
Eldev runs on Emacs 24.4 and up. On earlier Emacs versions it will be overly verbose, but this is rather an Emacs problem.
Linux or other POSIX-like system is currently required. However, since there is only a small shell script that is really OS-dependent, porting to other systems should not be difficult (volunteers welcome).
Eldev intentionally has no dependencies, at least currently: otherwise your project would also see them, which could in theory lead to some problems.
Eldev could reasonably be backported to work on Emacs 24.1 and up if anyone interested has access to such old versions.
Installation
There are several ways to install Eldev.
Bootstrapping from Melpa: if you have a catch-all directory for executables
-
From this directory (e.g.
~/bin
) execute:$ curl -fsSL https://raw.github.com/doublep/eldev/master/bin/eldev > eldev && chmod a+x eldev
You can even do this from
/usr/local/bin
provided you have the necessary permissions.
No further steps necessary — Eldev will bootstrap itself as needed on first invocation.
Bootstrapping from Melpa: general case
-
Execute:
$ curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/eldev | sh
This will install
eldev
script to~/.eldev/bin
. -
Add the directory to your
$PATH
; e.g. in~/.profile
add this:export PATH="$HOME/.eldev/bin:$PATH"
Afterwards Eldev will bootstrap itself as needed on first invocation.
eldev
doesn’t really need to be findable through $PATH
— it
will work regardless. This is rather for your convenience, so that
you don’t need to type the full path again and again.
Installing from sources
-
Clone the source tree from GitHub.
-
In the cloned working directory execute:
$ ./install.sh DIRECTORY
Here
DIRECTORY
is the location ofeldev
executable should be put. It should be in$PATH
environment variable, or else you will need to specify full path each time you invoke Eldev. You probably have sth. like~/bin
in your$PATH
already, which would be a good value forDIRECTORY
. You could even install in e.g./usr/local/bin
— but make sure you have permissions first.
Mostly for developing Eldev itself
-
Clone the source tree from GitHub.
-
Set environment variable
$ELDEV_LOCAL
to the full path of the working directory. -
Make sure executable
eldev
is available. Either follow any of the first way to install Eldev, or symlink/copy filebin/eldev
from the cloned directory to somewhere on your$PATH
.
Now each time Eldev is executed, it will use the sources at
$ELDEV_LOCAL
. You can even modify it and see how that affects Eldev
immediately.
Getting started
Eldev comes with built-in help. Just run:
$ eldev help
This will list all the commands Eldev supports. To see detailed description of any of those, type:
$ eldev help COMMAND
In the help you can also see lots of options — both global and specific to certain commands. Many common things are possible just out of the box, but later we will discuss how to define additional commands and options or change defaults for the existing.
Two most important global options to remember are --trace
(-t
) and
--debug
(-d
). With the first one, Eldev prints lots of additional
information about what it is doing to stdout. With the second, Eldev
prints stacktraces for most errors. These options will often help you
figure out what’s going wrong without requesting any external
assistance.
Eldev mostly follows GNU conventions in its command line. Perhaps the only exception is that global options must be specified before command name and command-specific options — after it.
Initializing a project
When Eldev starts up, it configures itself for the project in the
directory where it is run from. This is done by loading Elisp file
called Eldev
(without extension!) in the current directory. This
file is similar to Make’s Makefile
or Cask’s Cask
. But even more
so to Gradle’s build.gradle
: because it is a program. File Eldev
is not strictly required, but nearly all projects will have one.
You can create the file in your project manually, but it is easier to just let Eldev itself do it for you, especially the first time:
$ eldev init
If you let the initializer do its work, it will create file Eldev
already prepared to download project dependencies. If you answer “no”
to its question (or execute as eldev init --non-interactive
), just
edit the created file and uncomment some of the calls to
eldev-use-package-archive
there as appropriate. These forms
instruct Eldev to use specific package archives to download project
dependencies.
After this step, Eldev is ready to work with your project.
Setup procedure in details
Now that we have created file Eldev
, it makes sense to go over the
full startup process:
-
Load file
~/.eldev/config
-
Load file
Eldev
in the current directory -
Load file
Eldev-local
in the current directory -
Execute setup forms specified on the command line
None of these Elisp files and forms are required. They are also not restricted in what they do. However, their intended usage is different.
File ~/.eldev/config
is user-specific. It is meant mostly for
customizing Eldev to your personal preferences. For example, if you
hate coloring of Eldev output, add form (setf eldev-coloring-mode
nil)
to it. Then every Eldev process started for any project will
default to using uncolored output.
File Eldev
is project-specific. It is the only configuration file
that should be added to project’s VCS (Git, Mercurial, etc.). Typical
usage of this file is to define in which package archives to look up
dependencies. It is also the place to define project-specific
builders and commands, for example to build project documentation from
source.
File Eldev-local
is working directory or user/project-specific.
Unlike Eldev
, it should not be added to VCS: it is meant to be
created by each developer (should he want to do so) to customize how
Eldev behaves in this specific directory. The most common use is to
define local dependencies. A good practice is to instruct your VSC to
ignore this file, e.g. list it in .gitignore
for Git.
Finally, it is possible to specify some (short) setup forms on the
command line using --setup
(-S
) option. This is not supposed to
be used often, mostly in cases where you run Eldev on a use-once
project checkout, e.g. on a continuous
integration server.
Project dependencies
Eldev picks up project dependencies from package declaration,
i.e. usually from Package-Requires
header in the project’s main
.el
file. You don’t need to declare these dependencies second time
in Eldev
and keep track that they remain in sync.
However, you do need to tell Eldev how to find these dependencies.
Like Cask, by default it doesn’t use any package archives. To tell it
to use an archive, call function eldev-use-package-archive
in
Eldev
(you have such forms already in place if you have used eldev
init
). For example:
(eldev-use-package-archive 'melpa-stable)
Eldev knows about three “standard” archives, which should cover most
of your needs: gnu
, melpa-stable
and melpa-unstable
. Note that
https://melpa.org
is called melpa-unstable
. This is to emphasize
that you shouldn’t use it if melpa-stable
is enough, because you
wouldn’t want your tests fail only because a dependency in an unstable
version has a bug.
Emacs 25 and up supports package archive priorities. Eldev utilizes this to assign the standard archives it knows about priorities 300, 200 and 100 in the order they are listed above.
If dependencies for your project are only available from some other archive, you can still use the same function. Just substite the symbolic archive name with a cons cell of name and URL as strings:
(eldev-use-package-archive '("myarchive" . "http://my.archive.com/packages/"))
You don’t need to perform any additional steps to have Eldev actually
install the dependencies: any command that needs them will make sure
they are installed first. However, if you want to check if package
archives have been specified correctly and all dependencies can be
looked up without problems, you can explicitly use command prepare
.
Local dependencies
Imagine you are developing more than one project at once and they depend on each other. You’d typically want to test the changes you make in one of them from another right away. If you are familiar with Cask, this is solved by linking projects in it.
Eldev provides a more flexible approach to this problem called local
dependencies. Let’s assume you develop project foo
in directory
~/foo
and also a library called barlib
in ~/barlib
. And foo
uses the library. To have Eldev use your local copy of barlib
instead of downloading it e.g. from Melpa, add the following form in
file ~/foo/Eldev-local
:
(eldev-use-local-dependency "~/barlib")
Note that the form must not be added to Eldev
: other developers
who check out your project probably don‘t even have a local copy of
barlib
or maybe have it in some other place. In other words, this
should really remain your own private setting and go to Eldev-local
.
Local dependencies have loading modes, just as the project’s package itself. Those will be discussed later.
Eldev correctly handles situations with changing definitions of local
dependencies. I.e. by simply commenting out or uncommenting
eldev-use-local-dependency
call, you can quickly test your project
both with a Melpa-provided package and with a local dependency — Eldev
will adapt without any additional work from you.
Additional dependencies
It is possible to register additional dependencies for use only by
certain Eldev commands. Perhaps the most useful is to make certain
packages available for testing purposes. For example, if your project
doesn’t depend on package foo
on its own, but your test files do,
add the following form to Eldev
file:
(eldev-add-extra-dependencies 'test 'foo)
Additional dependencies are looked up in the same way as normal ones. So, you need to make sure that all of them are available from the package archives you instructed Eldev to use.
The following commands make use of additional dependencies: build
,
emacs
, eval
, exec
and test
. Commands you define yourself can
also take advantage of this mechanism, see function
eldev-load-project-dependencies
.
Examining dependencies
Sometimes it is useful to check what a project depends on, especially if it is not your project, just something you have checked out. There are two commands for this in Eldev.
First is dependencies
(can be shortened to deps
). It lists
direct dependencies of the project being built. By default, it
omits any built-in packages, most importantly emacs
. If you want to
check those too, add option -b
(--list-built-ins
).
Second is dependecy-tree
(short alias: dtree
). It prints a tree
of project direct dependencies, direct dependencies of those, and so
on — recursively. Like with the first command, use option -b
if you
want to see built-ins in the tree.
Both commands can also list additional dependencies if instructed: just specify set name(s) on the command line, e.g.:
$ eldev dependencies test
You can also check which archives Eldev uses to look up dependencies for this particular project with the following command:
$ eldev archives
Upgrading dependencies
Eldev will install project dependencies automatically, but it will never upgrade them, at least if you don’t change your project to require a newer version. However, you can always explicitly ask Eldev to upgrade the installed dependencies:
$ eldev upgrade
First, package archive contents will be refetched, so that Eldev knows about newly available versions. Next, this command upgrades (or installs, if necessary) all project dependencies and all additional dependencies you might have registered (see above). If you don’t want to upgrade everything, you can explicitly list names of the packages that should be upgraded:
$ eldev upgrade dash ht
You can also check what Eldev would upgrade without actually upgrading anything:
$ eldev upgrade --dry-run
Loading modes
In Eldev the project’s package and its local dependencies have loading modes. This affects exactly how the package (that of the project or of its local dependency) becomes loadable by Emacs.
Default loading mode is called as-is
. It means the directory where
project (or local dependency) is located is simply added to Emacs
varible load-path
and normal Emacs loading should be able to find
required features from there on. This is the fastest mode, since it
requires no preparation and in most cases is basically what you want
during development.
However, users won’t have your project loaded like that. To emulate
the way that most of the people will use it, you can use loading mode
packaged
. In this mode, Eldev will first build a package out of
your project (or local dependency), then install and activate it using
Emacs’ packaging system. This is quite a bit slower than as-is
,
because it involves several preparation steps. However, this is
almost exactly the way normal users will use your project after
e.g. installing it from Melpa. For this reason, this mode is
recommended for continuous integration and
other forms of automated testing.
Other modes include byte-compiled
and source
. In these modes
loading is performed just as in as-is
mode, but before that Eldev
either byte-compiles everything or, vice-versa, removes .elc
files.
So, after discussing the loading modes, let’s have a look at how exactly you tell Eldev which one to use.
For the project itself, this is done from the command line using
global option --loading
(or -m
) with its argument being the name
of the mode. Since this is supposed to be used quite frequently,
there are also shortcut options to select specific modes: --as-is
(or -a
), --packaged
(-p
), --source
(-s
) or --byte-compiled
(-b
). For example, the following command will run unit-tests in the
project, having it loaded as an Emacs package:
$ eldev -p test
Remember, that as everything in Eldev, this can be customized.
E.g. if you want to run your project byte-compiled by default, add
this to your Eldev-local
:
(setf eldev-project-loading-mode 'byte-compiled)
For local dependencies the mode can be chosen when calling
eldev-use-local-dependency
. For example:
(eldev-use-local-dependency "~/barlib" 'packaged)
As mentioned above, loading mode defaults to as-is
.
There are a few other loading modes useful only for certain projects. You can always ask Eldev for a full list:
$ eldev --list-modes
Build system
Eldev comes with quite a sofisticated build system. While by default
it only knows how to build packages, byte-compile .el
files and make
.info
from .texi
, you can extend it with custom builders that
can do anything you want. For example, generate resource files that
should be included in the final package.
The main command is predictably called build
. There are also
several related commands which will be discussed in the next sections.
Targets
Build system is based on targets. Targets come in two kinds: real
and virtual. First type of targets corresponds to files — not
necessarily already existing. When needed, such targets get rebuilt
and the files are (re)generated in process. Targets of the second
type always have names that begin with “:” (like keywords in Elisp).
Most import virtual target is called :default
— this is what Eldev
will build if you don’t request anything explicitly.
To find all targets in a project (more precisely, its main
target set):
$ eldev targets
Project’s targets form a tree. Before a higher-level target can be built, all its children must be up-to-date, i.e. built first if necessary. In the tree you can also see sources for some targets. Those can be distinguished by lack of builder name in brackets. Additionally, if output is colored, targets have special color, while sources use default text color.
Here is how target tree looks for Eldev project itself (version may be different and more targets may be added in future):
:default bin/eldev [SUBST] bin/eldev.in :package dist/eldev-0.1.tar [PACK] bin/eldev [repeated, see above] eldev-ert.el eldev-util.el eldev.el :compile eldev-ert.elc [ELC] eldev-ert.el eldev-util.elc [ELC] eldev-util.el eldev.elc [ELC] eldev.el :package-archive-entry dist/eldev-0.1.entry [repeated, see ‘dist/eldev-0.1.tar’ above]
And a short explanation of various elements:
:default
, :package
, :compile
etc.
Virtual targets. The ones you see above are typical, but there could be more.
bin/eldev
, dist/eldev-0.1.tar
, eldev-ert.elc
etc.
Real targets.
SUBST
, PACK
, ELC
Builders used to generate target. Note that virtual targets never
have builders. SUBST
is not a standard builder, it is defined
in file Eldev
of the project.
bin/eldev.in
, eldev-ert.el
etc.
Sources for generating targets. Certain targets have more than
one source file. Also note how targets can have other targets as
their sources (bin/eldev
is both a target on its own and a
source for dist/eldev-0.1.tar
).
[repeated ...]
To avoid exponential increase in tree size, Eldev doesn’t repeat target subtrees. Instead, only root target of a subtree is printed.
Target cross-dependencies
FIXME
Target sets
Eldev groups all targets into sets. Normally, there are only two
sets called main
and test
, but you can define more if you need
(see variable eldev-filesets
). For example, if your project
includes a development tool that certainly shouldn’t be included in
project’s package, it makes sense to break it out into a separate
target set.
Target sets should be seen only as ways of grouping targets together
for the purpose of quickly enumerating them. Two targets in the same
set can be completely independent from each other. Similarly, targets
from different sets can depend on each other (provided this doesn’t
create a circular dependency, of course). For example, targets in set
test
will often depend on those in set main
, because test .el
files usually require
some features from main
.
By default, command build
operates only on main
target set. You
can use option --set
(-s
) to process a different target set. If
you want to build several sets at once, repeat the option as many
times as needed. Finally, you can use special name all
to order
Eldev to operate on all defined sets at once.
Command targets
instead of the option expects set names as its
arguments. For example:
$ eldev targets test
Building packages
To build an Elisp package out of your project, use command package
:
$ eldev package
This command is basically a wrapper over the build system, it tells
the system to generate virtual target :package
. However, there are
a few options that can only be passed to this special command, not to
underlying build
.
Normally, packages are generated in subdirectory dist
(more
precisely, in directory specified by eldev-dist-dir
variable). If
needed, you can override this using --output-dir
option.
By default, Eldev will use package’s self-reported version, i.e. value
of “Version” header in its main .el
file. If you need to give the
package a different version, use option --force-version
. E.g. Melpa
would do this if it used Eldev.
Finally, if you are invoking Eldev from a different tool, you might be
interested in option --print-filename
. When it is specified, Eldev
will print absolute filename of the generated package and word
“generated” or “up-to-date” as the two last lines of its (stdout)
output. Otherwise it is a bit tricky to find the package, especially
if you don’t use --force-version
option. As an optimisation, you
can also reuse previous package file if Eldev says “up-to-date”.
Byte-compiling
You can use Eldev to byte-compile your project. Indirectly, this can
be done by selecting appropriate loading mode for
the project or its local dependencies. However, sometimes you might
want to do this explicitly. For this, use command compile
:
$ eldev compile
You can also byte-compile specific files:
$ eldev compile foo-util.el foo-misc.el
Eldev will not recompile .el
that have up-to-date .elc
versions.
So, if you issue command compile
twice in a row, it will say:
“Nothing to do” the second time.
However, simple comparison of modification time of .el
and its
.elc
file is not always enough. Suppose file foo-misc.el
has form
(require 'foo-util)
. If you edit foo-util.el
, byte-compiled file
foo-misc.elc
might no longer be correct, because it has been
compiled against old definitions from foo-util.el
. Luckily, Eldev
knows how to detect when a file require
s another. You can see
this in the target tree:
$ eldev targets --dependencies [...] :compile foo-misc.elc [ELC] foo-misc.el [inh] foo-util.elc [...]
As a result, if you now edit foo-util.el
and issue compile
again,
both foo-util.elc
and foo-misc.elc
will be rebuilt.
Eldev treats warnings from Emacs’ byte-compiler just as that —
warnings, i.e. they will be shown, but will not prevent compilation
from generally succeeding. However, during
automated testing you might want to check
that there are no warnings. The easiest way to do it is to use
--warnings-as-errors
option (-W
):
$ eldev compile --warnings-as-errors
Command compile
is actually only a wrapper over the generic building
system. You can rewrite all the examples above using command build
.
If you don’t specify files to compile, virtual target :compile
is
built. This target depends on all .elc
files in the project.
However, there is a subtle difference: for compile
you specify
source files, while build
expects targets. Therefore, example
$ eldev compile foo-util.el foo-misc.el
above is equivalent to this command:
$ eldev build foo-util.elc foo-misc.elc
with .el
in filenames substituted with .elc
.
Byte-compiling complicated macros
Certain files with macros in Elisp cannot be byte-compiled without
evaluating them first or carefully applying eval-and-compile
to
functions used in macroexpansions. Because Emacs packaging system
always loads (evaluates) package files before byte-compiling them
during installation, this is often overlooked.
Unlike the packaging system, Eldev by default expects that .el
files
can be compiled without loading them first, i.e. it expects that
eval-and-compile
is applied where needed. This is the default
because it is much faster on certain files.
However, if your project cannot be byte-compiled without loading first
and you don’t want to “fix” this, you can ask Eldev to behave like the
packaging system using --load-before-compiling
(-l
) option:
$ eldev compile -l
Projects that can only be compiled with this setting should specify it
as the default in their file Eldev
:
(setf eldev-build-load-before-byte-compiling t)
You can find more information in section “Evaluation During Compilation” of Elisp manual.
Speed of byte-compilation
While not particularly important in most cases, speed of byte-compilation can become an issue in large projects, especially if they use lots of macros. Eldev tries to speed up byte-compilation by compiling the files in “correct” order.
This means that if, as above, foo-misc.el
require
s feature
foo-util
, then foo-util.el
will always be byte-compiled first, so
that compilation of foo-misc.el
can use faster, byte-compiled
versions of definitions from that file. This works even if Eldev
doesn’t yet know which files require
which.
When Eldev has to change the planned order of byte-compilation because
of a require
form, it writes an appropriate message (you need to run
with option -v
or -t
to see it):
$ eldev -v compile [...] ELC foo-misc.el Byte-compiling file ‘foo-misc.el’... ELC foo-util.el Byte-compiling file ‘foo-util.el’ early as ‘require’d from another file... Done building “sources” for virtual target ‘:compile’
Cleaning
FIXME
Testing
Eldev has built-in support for running regression/unit tests of your project. Currently, Eldev supports only ERT. Other frameworks will also be supported in the future; leave a feature request in the issue tracker if you are interested.
Simply executing
$ eldev test
will run all your tests. By default, all tests are expected to be in
files named test.el
, tests.el
, *-test.el
, *-tests.el
or in
test
or tests
subdirectories of the project root. But you can
always change the value of eldev-test-fileset
variable in the
project’s Eldev
as appropriate.
By default, the command runs all available tests. However, during development you often need to run one or a few tests only — when you hunt a specific bug, for example. Eldev provides two ways to select which tests to run.
First is by using a selector:
$ eldev test foo-test-15
will run only the test with that specific name. It is of course possible to select more than one test by specifying multiple selectors: they are combined with ‘or’ operation. You can use any selector supported by the testing framework here, see its (i.e. read: “ERT’s”) documentation.
The second way is to avoid loading (and executing) certain test files
altogether. This can be achieved with --file
(-f
) option:
$ eldev test -f foo.el
will execute tests only in file foo.el
and not in e.g. bar.el
.
You don’t need to specify directory (e.g. test/foo.el
); for reasons
why, see explanation of Eldev filesets below.
Both ways of selecting tests can be used together. In this case they are combined with ‘and’ operation: only tests that match selector and which are defined in a loaded file are run.
How exactly tests are executed depends on test runner. If you
dislike the default behavior of Eldev, you can choose a different test
runner using --runner
(-r
) option of test
command; see the list
of available test runners with their descriptions using
--list-runners
option. If you always use a different test runner,
it is a good idea to set it as the default in file ~/.eldev/config
.
Finally, you can even write your own runner.
Reusing previous test results
ERT provides a few selectors that operate on tests’ last results. Even though different Eldev executions will run in different Emacs processes, you can still use these selectors: Eldev stores and then loads last results of test execution as needed.
For example, execute all tests until some fails (-s
is a shortcut
for --stop-on-unexpected
):
$ eldev test -s
If any fails, you might want to fix it and rerun again, to see if the fix helped. The easiest way is:
$ eldev test :failed
For more information, see documentation on ERT selectors — other
“special” selectors (e.g. :new
or :unexpected
) also work.
Testing command line simplifications
When variable eldev-test-dwim
(“do what I mean”) is non-nil (as by
default), Eldev supports a few simplifications of the command line to
make testing even more streamlined.
-
Any selector that ends in
.el
is instead treated as a file pattern. For example:$ eldev test foo.el
will work as if you specified
-f
beforefoo.el
. -
For ERT: any symbol selector that doesn’t match a test name is instead treated as regular expression (i.e. as a string). For example:
$ eldev test foo
will run all tests with names that contain
foo
. You can achieve the same result with ‘strict’ command line (see also ERT selector documentation) like this:$ eldev test \"foo\"
If you dislike these simplifications, set eldev-test-dwim
to nil in
~/.eldev/config
.
Quickly evaluating expressions
It is often useful to evaluate Elisp expressions in context of the
project you develop — and probably using functions from the project.
There are two commands for this in Eldev: eval
and exec
. The only
difference between them is that exec
doesn’t print results to
stdout, i.e. it assumes that the forms you evaluate produce some
detectable side-effects. Because of this similarity, we’ll consider
only eval
here.
The basic usage should be obvious:
$ eldev eval "(+ 1 2)"
Of course, evaluating (+ 1 2)
form is not terribly useful. Usually
you’ll want to use at least one function or variable from the project.
However, for that you need your project not only to be in load-path
(which Eldev guarantees), but also require
d. Luckily, you don’t
have to repeat (require 'my-package)
all the time on the command
line, as Eldev does this too, so normally you can just run it like
this:
$ eldev eval "(my-package-function)"
What Eldev actually does is requiring all features listed in variable
eldev-eval-required-features
. If value of that variable is symbol
:default
, value of eldev-default-required-features
is taken
instead. And finally, when value of the latter is symbol
:project-name
, only one feature with the same name as that of the
project is required. In 95% of the cases this is exactly what you
need. However, if the main feature of the project has a different
name, you can always change the value of one of the mentioned
variables in file Eldev
.
It can also make sense to change the variable’s value in Eldev-local
if you want certain features to always be available for quick testing.
Running Emacs
Sometimes you want to run Emacs with just your project installed and see how it works without any customization. You can achieve this in Eldev easily:
$ eldev emacs
This will spawn a separate Emacs that doesn’t read any initialization
scripts and doesn’t have access to your usual set of installed
packages, but instead has access to the project being built with Eldev
— and its dependencies, of course. Similar as with eval
and exec
commands, features listed in variable eldev-emacs-required-features
are required automatically.
You can also pass any Emacs options through the command line. For
example, this will visit file foo.bar
, which is useful if your
project is a mode for .bar
files:
$ eldev emacs foo.bar
See emacs --help
for what you can specify on the command line.
When issued as shown above, command emacs
will pass the rest of the
command line to Emacs, but also add a few things on its own. First,
it adds everything from the list eldev-emacs-default-command-line
,
which disables ~/.emacs
loading and similar things. Second, it adds
--eval
arguments to require the features as described above. And
only after that comes the actual command line you specified.
Occasionally you might not want this behavior. In this case, prepend
--
to the command line — then Eldev will pass everything after it to
the spawned Emacs as-is. Remember that you will likely need to pass
at least -q
(--no-init-file
) option to Emacs, otherwise it will
probably fail on your ~/.emacs
since it will not see your usual
packages. To illustrate:
$ eldev emacs -- -q foo.bar
Executing on different Emacs versions
Since Eldev itself is an Elisp program, version of Emacs you use can
affect any aspect of execution — even before it gets to running
something out of your project. Therefore, inside its “cache”
directory called .eldev
, the utility creates a subdirectory named
after Emacs version it is executed on. If it is run with a different
Emacs, it will not use dependencies or previous test results, but
rather install or recompute them from scratch.
Normally, Eldev uses command emacs
that is supposed to be resolvable
through $PATH
environment variable. However, you can always tell it
to use a different Emacs version by setting either ELDEV_EMACS
or
just EMACS
in the environment, e.g.:
$ EMACS=emacs25 eldev eval emacs-version
This is especially useful for testing your project with different Emacs versions.
Remember, however, that Eldev cannot separate byte-compiled files
(.elc
) from sources. From documentation of
byte-compile-dest-file-function
:
Note that the assumption that the source and compiled files are found in the same directory is hard-coded in various places in Emacs.
Therefore, if you use byte-compilation and switch Emacs versions, don’t forget to clean the directory.
Continuous integration
Because of Eldev’s trivial installation and built-in support for testing, it is a suitable tool for use on continuous integration servers. But of course this only applies if the test framework your project uses is already supported (currently only ERT).
Travis CI
The largest problem on Travis CI is to install Emacs binary of the desired version. Luckily, there is a tool that can be used for this called EVM. For convenience, Eldev provides a simple script specifically for use on Travis CI that installs Eldev and EVM in one go.
Here is a simple project-agnostic .travis.yml
file that you can use
as a basis:
language: emacs-lisp dist: trusty install: - curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/travis-eldev-and-evm > x.sh && source ./x.sh - evm install $EVM_EMACS --use env: # Add more lines like these if you want to test on different Emacs versions. - EVM_EMACS=emacs-26.3-travis script: - eldev -p -dtT test
If you want to additionally test that your project byte-compiles
cleanly, add another line to script
section:
- eldev -dtT compile --warnings-as-errors
Or maybe even this, if you want to make sure that test .el
files
also can be byte-compiled without warnings (this can sometimes catch
more problems):
- eldev -dtT compile --set all --warnings-as-errors
Filesets
Filesets are lists of rules that determine a collection of files inside given root directory, usually the project directory. Similar concepts are present in most build tools, version control systems and some other programs. Filesets in Eldev are inspired by Git.
Important examples of filesets are variables eldev-main-fileset
,
eldev-test-fileset
and eldev-standard-excludes
. Default values of
all three are simple filesets, but are not actually restricted to
those: when customizing for your project you can use any valid fileset
as a value for any of these variables. However, for most cases simple
filesets are all that you really need.
Simple filesets
From Lisp point of view, a simple fileset is a list of strings. A
single-string list can also be replaced with that string. The most
important filesets are eldev-main-fileset
and eldev-test-fileset
.
Using them you can define which .el
files are to be packaged and
which contain tests. Default values should be good enough for most
projects, but you can always change them in file Eldev
if needed.
Each rule is a string that matches file path — or its part — relative
to the root directory. Path elements must be separated with a slash
(/
) regardless of your OS, to be machine-independent. A rule may
contain glob wildcards (*
and ?
) with the usual meaning and also
double-star wildcard (**
) that must be its own path element. It
stands for any number (including zero) of nested subdirectories.
Example:
foo/**/bar-*.el
matches foo/bar-1.el
and foo/x/y/bar-baz.el
.
If a rule starts with an exclamation mark (!
), it is an exclusion
rule. Files that match it (after the mark is stripped) are excluded
from the result. Other (“normal”) rules are called inclusion rules.
Typically, a rule must match any part of a file path (below the root,
of course). However, if a rule starts with /
or ./
it is called
anchored and must match beginning of a file path. For example, rule
./README
matches file README
in the root directory, but not in any
of its subdirectories.
If a rule matches a directory, it also matches all of the files the
directory contains (with arbitrary nesting level). For example, rule
test
also matches file test/foo/bar.el
.
A rule that ends in a slash directly matches only directories. But,
in accordance to the previous paragraph, also all files within such
directories. So, there is a subtle difference: a rule test/
won’t
match a file named test
, but will match any file within a directory
named test
.
Finally, note a difference with Git concerning inclusions/exclusions and subdirectories. Git manual says: “It is not possible to re-include a file if a parent directory of that file is excluded.” Eldev filesets have no such exceptions.
Composite filesets
Eldev also supports composite filesets. They are built using common set/logic operations and can be nested, i.e. one composite fileset can include another. There are currently three types:
(:and ELEMENT...)
A file matches an :and
fileset if and only if it matches every
of its ELEMENT
filesets.
(:or ELEMENT...)
A file matches an :or
fileset if and only if it matches at least
one of its ELEMENT
filesets.
(:not NEGATED)
A file matches a :not
fileset when it doesn’t match its
NEGATED
fileset and vice versa.
Evaluated filesets
Finally, some parts of filesets — but not elements of simple filesets!
— can be evaluated. An evaluated element can be a variable name (a
symbol) or a form. When matching, such element will be evaluated
once, before eldev-find-files
or eldev-filter-files
start actual
work.
Result of evaluating such an expression can be an evaluated fileset in
turn — Eldev will keep evaluating elements until results finally
consist of only simple and composite filesets. To prevent accidental
infinite loops, there is a limit of eldev-fileset-max-iterations
on
how many times sequential evaluations can yield symbols or forms.
Example of an evaluated fileset can be seen from return value of
eldev-standard-fileset
function. E.g.:
(eldev-standard-fileset 'main) => (:and eldev-main-fileset (:not eldev-standard-excludes))
As the result contains references to two variables, they will be evaluated in turn — and so on, until everything is resolved.
Extending Eldev
Eldev is written to be not just configurable, but also extensible. It
makes perfect sense to have additional code in file Eldev
— if your
project has uncommon building steps. And also in ~/.eldev/config
—
if you want a special command for your own needs, for example. Or
maybe in Eldev-local
— if you need something extra only for one
specific project that you maintain.
Hooks
Eldev defines a few hooks (more might be added later).
eldev-executing-command-hook
Run before executing any command. Command name (as a symbol) is passed to the hook’s functions as the only argument. This is always the “canonical” command name, even if it is run using an alias.
eldev-COMMAND-hook
Run before executing specific command, functions have no
arguments. Eldev itself uses it (i.e. in its file Eldev
) to
print a disclaimer about its fairly slow tests.
Hook executed whenever build system is used. This is useful since at least commands `build`, `compile` and `package` invoke the build system: it would be impractical to add the same function to three hooks.
Writing builders
Eldev build system provides standard builders that cover all basic needs of Elisp packages. However, some projects have uncommon build steps. Instead of writing custom shell scripts, you can integrate them into the overall build process — which also simplifies further development.
An example of a project with additional build steps is Eldev itself.
Its executable(s) are combined from executable template that is
OS-specific and a common Elisp bootstrapping script. For example,
bin/eldev
is generated from files bin/eldev.in
and
bin/bootstrap.el.part
. However, only the first file counts as the
source; see how function eldev-substitute
works.
There is a simple builder for this in file Eldev
of the project:
(eldev-defbuilder eldev-builder-preprocess-.in (source target) :short-name "SUBST" :message source-and-target :source-files "*.in" :targets (".in" -> "") :collect ":default" :define-cleaner (eldev-cleaner-preprocessed "Delete results of preprocessing `.in' files. This is specific to Eldev itself." :aliases prep) (let ((modes (file-modes target))) (eldev-substitute source target) (when (or modes (string-prefix-p "bin/" target)) (set-file-modes target (or modes #o755)))))
Here eldev-defbuilder
is a macro much like defun
. It defines an
Elisp function named eldev-builder-preprocess-.in
and registers it
with parameters (the keyword lines before the body) as an Eldev
builder. Predictably, list (source target)
specifies function
arguments.
Let’s skip the keywords for a bit and have a look at the body. It
works exactly like in a normal Elisp function. Its job is to generate
target
from source
using builder-specific means. This particular
builder calls function eldev-substite
that does the actual work
(this function is available also to your project, should you need it).
But your builders could do whatever you want, including launching
external processes (C/C++ compiler, a Python script, etc.) and using
anything from Elisp repertoire. Note that return value of the body is
ignored. If building the target fails, builder should signal an
error.
Now back to the keyword parameters. As you can see, they all have a
name and exactly one value after it. First comes parameter
:short-name
. It specifies what you see in the target tree of the
project, i.e. builder’s name for the user. It is not required;
without it Eldev would have used preprocess-.in
as user-visible
name.
Next parameter is :message
. It determines what Eldev prints when
the builder is actually invoked. For example, when byte-compiling,
you’d see messages like this:
ELC some-file.el
That’s because byte-compiling builder has its :message
set to
source
(the default). Other valid values are target
and
source-and-target
(as in the example). Both source
and target
can be pluralized (i.e. sources-and-target
is also a valid value),
but singular/plural is not important in this case as both work
identically. Finally, value of :message
can be a function, in which
case it is called with the same arguments as the builder itself and
should return a string.
Value of :source-files
parameter must be a fileset. In
the above example, fileset consists of only one simple rule — which is
actually enough in most cases, — but it could also be much more
complicated. All files that match the fileset and do not match
eldev-standard-excludes
will be processed using this builder.
Parameter :targets
defines the rule used to construct target names
out of sources matched by :source-files
. There are several ways to
define this rule, we’ll consider them in their own
subsection.
Keyword :collect
determines how targets generated by this builder
are “collected” into virtual targets. In the example all such targets
are simply added to the virtual target :default
. However, here too
we have several other possibilities, which will be described
later.
Finally, keyword :define-cleaner
provides a simple way of linking
builders with the cleaning system.
Another important keyword is :type
. It is not used here only
because the example builder is of the default and most common type
that generates one target for each source file. All possible types
are: one-to-one
(the default), one-to-many
(several targets from
one source file), many-to-one
and many-to-many
. If you write a
builder of a non-default type, be aware that it will be called with a
list of strings instead of a single string as one or both of its
arguments, as appropriate. You should probably also name them in
plural in the definition in this case, to avoid confusion.
Target rules
Target rules define which target(s) will be built from given source(s). There are several ways to define a target rule. Yet more can be added in the future as real-world needs accumulate.
TARGET
All the sources will be passed together as a list to the builder
to generate one TARGET
. This is suitable for many-to-one
builders.
(TARGET-1 [TARGET-2 [...]])
Build several TARGETS
out of all the sources. This is for
many-to-many
and one-to-many
builders.
(SOURCE-SUFFIX -> TARGET-SUFFIX)
Build target name from source name by replacing filename suffixes.
SOURCE-SUFFIX
can also be a list of strings, in which case any
suffix from the list will be replaced. This is the type of target
rule you can see in the example and is suitable for one-to-one
builders. Another use of this rule type could be seen in
byte-compiling builder:
:targets (".el" -> ".elc")
And the most powerful of all target rules: a function (can be a lambda
form or a function name). It is called with a list of sources (even
if the builder is of one-to-one
or one-to-many
type) and must
return one of the types enumerated above.
Collecting into virtual targets
Real targets generated by the builders can optionally be combined into
virtual targets. The latter are used to easily build all real targets
of the same type; some (:default
, :compile
etc.) also have
special meaning to certain commands.
Like with the target rules, there are several ways to collect the targets.
VIRTUAL-TARGET
All real targets generated by the builder are combined into given
VIRTUAL-TARGET
. This is what you can see in the example.
(VIRTUAL-TARGET-1 [VIRTUAL-TARGET-2 [... VIRTUAL-TARGET-N]])
Combine the real targets into VIRTUAL-TARGET-N
, then put it to
the preceding virtual target and so on. This format is currently
unused in standard Eldev builders. It can generate target trees
of this form:
:gen-files :gen-sources :gen-el foo.el.in bar.el.in
It is expected (even if not required) that a different builder adds another branch to the tree, actually making it useful.
(ENTRY…)
, each ENTRY
being (REAL-TARGETS VIRTUAL-TARGETS)
Both of REAL-TARGETS
and VIRTUAL-TARGETS
must be either a list
or a single target string. For each ENTRY
this repeats the
logic of one of the two formats above, but instead of all targets
for the builder uses only those listed in REAL-TARGETS
for the
ENTRY
. This is not often needed, but can be useful if builder’s
targets come in two or more substantially different kinds.
Like with target rules, you can specify a function here. Such a function gets called with a list of real targets and must return a collection rule in one of the formats listed above.
Summary
To define a builder you need to write an Elisp function that generates
target(s) from source(s). If it processes multiple sources at once or
generates multiple targets, give it the appropriate :type
. Write a
fileset that matches its :source-files
and a rule to determine
target names from those — parameter :targets
. If you want the
targets grouped together into virtual target(s), add :collect
keyword. You should probably also add a :define-cleaner
that
removes generated targets.
Parameters :name
, :short-name
, :message
and :briefdoc
are all
fully presentational and thus not very important. But if you want to
write a nice and polished builder, investigate them too.
Adding commands and options
FIXME
Custom test runners
FIXME
Influential environment variables
A few environment variables can affect Eldev’s behavior.
EMACS
or ELDEV_EMACS
Use given Emacs executable (also for any child processes). If not
specified, this defaults to just emacs
, which is expected
somewhere in $PATH
.
ELDEV_LOCAL
Load Eldev Elisp code from given directory (usually a Git clone of source tree) instead of the normal bootstrapping from Melpa. Should not be needed normally, only when developing Eldev itself.
ELDEV_DIR
Directory where user’s configuration file, Eldev’s bootstrapping
files etc. are located, defaults to ~/.eldev
. Used by Eldev’s
own regression tests, should be of no interest for typical use.
See also
Some other build tools, projects and services you might want to use with or instead of Eldev:
-
Cask — the most established Emacs project management tool.
-
EMake — build tool that combines Elisp with GNU Make.
-
EVM — Emacs version manager; has special support for Travis CI.
-
Travis CI — continuous integration service, the most used one for Elisp projects; Eldev has additional support for it.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK