#
#
#
This repository provides a Bazel integration for Emacs Lisp; see the Bazel homepage for more information about Bazel, and see the Emacs Lisp manual. It is modeled after the rules definitions for other languages, like the C++ rules.
This is not an officially supported Google product.
To use rules_elisp
, you need Bazel. For
installation instructions, see Installing
Bazel. This repository supports all full Bazel releases starting with Bazel
6.4.0. You’ll also need a recent C/C++ compiler (GCC or Clang on GNU/Linux and
macOS, Visual C++ 2019 on Windows) and at least Python 3.10. For further
instructions how to use Bazel on Windows, see
Installing Bazel on Windows.
This repository generally supports the two most recent major versions of Emacs. Currently, the supported versions are Emacs 28 and Emacs 29. Once Emacs 30 is released, support for Emacs 28 will be dropped.
If you’re using Bzlmod, add a snippet like the following to your MODULE.bazel
file:
bazel_dep(name = "phst_rules_elisp")
git_override(
module_name = "phst_rules_elisp",
remote = "https://github.com/phst/rules_elisp.git",
commit = "30e571b6e69be2dcc782325834792d79c245d30d",
)
See the Bzlmod documentation for background information.
Alternatively, if you’re not using Bzlmod, add a snippet like the following to
your Bazel WORKSPACE
file:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "phst_rules_elisp",
integrity = "sha384-QVJcZ4h8j8t9fjaddWCGssqBjUexRReKR53Th9XG1xPYXN/2eiWP1Is5pIfElsA5",
strip_prefix = "rules_elisp-30e571b6e69be2dcc782325834792d79c245d30d",
urls = [
"https://github.com/phst/rules_elisp/archive/30e571b6e69be2dcc782325834792d79c245d30d.zip", # 2024-02-04
],
)
load(
"@phst_rules_elisp//elisp:repositories.bzl",
"rules_elisp_dependencies",
"rules_elisp_toolchains",
)
rules_elisp_dependencies()
rules_elisp_toolchains()
load("@bazel_features//:deps.bzl", "bazel_features_deps")
bazel_features_deps()
See the Bazel
documentation on WORKSPACE dependencies for background information. Since
rules_elisp
depends on rules_python
, you’ll also need to add a dependency on
the latter. See
the
rules_python
documentation.
Then you can use the elisp_library
, elisp_binary
, and elisp_test
rules.
See *Symbols defined in =//emacs:defs.bzl= and the examples in the
examples
directory for details. Note that the C++ code used by rules_elisp
requires at least C++17, but Bazel still compiles with C++11 by default. See
the
Abseil FAQ how to correctly change the C++ standard for your project.
The Emacs Lisp rules by default only add the repository root directories to the
load path; see info:elisp#Library Search. However, many Emacs Lisp
libraries assume that their immediate parent directory is present in the load
path. To support such libraries, the elisp_library
rule supports an optional
load_path
attribute. You can specify additional load path directories using
this attribute. Relative directories are relative to the Bazel package
directory; absolute directories are relative to the repository root. A typical
use case is to specify load_path = ["."]
to add the current package to the
load path.
This repository also includes a library to access runfiles; see
the Bazel documentation on
runfiles. To use it, add a build dependency on
@phst_rules_elisp//elisp/runfiles
.
Use the function elisp/runfiles/rlocation
to map a runfile name to a
filename in the local filesystem. For more advanced use cases, see the
class elisp/runfiles/runfiles
.
The library also provides a file name handler for runfiles,
elisp/runfiles/file-handler
. It uses the prefix /bazel-runfile:
.
By defining elisp_proto_library
rules, you can use protocol buffers in Emacs
Lisp; see https://developers.google.com/protocol-buffers. To make a
protocol buffer definition available to Emacs Lisp, you first need a
proto_library
rule; see
https://bazel.build/reference/be/protocol-buffer#proto_library. You can
either use an existing proto_library
rule or add your own. Then, add an
elisp_proto_library
rule that references the proto_library
rule. Normally,
the name of the proto_library
rule ends in _proto
, and the name of the
corresponding elisp_proto_library
rule has the same prefix and ends in
_elisp_proto
. For example:
proto_library(
name = "my_proto",
srcs = ["my.proto"],
)
elisp_proto_library(
name = "my_elisp_proto",
deps = [":my_proto"],
)
You can then use the elisp_proto_library
rule in the same way as a normal
elisp_library
rule, i.e., depend on it in other elisp_library
,
elisp_binary
, or elisp_test
rules. The name of the Emacs Lisp feature for
the library is the same as the name of the original .proto
file, relative to
its repository root. For example, if the above BUILD file is in a package named
mypackage
, you would load the protocol buffer library using (require
'mypackage/my.proto)
.
Emacs Lisp protocol buffer bindings contain Emacs Lisp equivalents for all
message and enumeration types defined in the underlying protocol buffer
definition files. Because Emacs Lisp doesn’t have namespaces, the names of all
defined entities are the full names of the corresponding protocol buffer
descriptors, including the protocol buffer package name (which is often
different from the Bazel package name), but with dots (.
) replaced with
slashes (/
), because dots are special characters in Emacs Lisp; see
info:elisp#Symbol Type. For example, the Lisp name of the protocol buffer
message type google.protobuf.Duration
is google/protobuf/Duration
, and the
Lisp name of the protocol buffer enumeration value
google.protobuf.FieldDescriptorProto.TYPE_BOOL
is
google/protobuf/FieldDescriptorProto/TYPE_BOOL
.
When accessing protocol buffer message fields, Emacs translates field values to
and from Lisp data types; see info:elisp#Programming Types. For scalar
types, the translation uses appropriate matching types, i.e., numbers and
strings. Boolean values are translated to either nil
or t
. The bytes
type is represented using unibyte strings; see <a href=”info:elisp#Text
Representations”>info:elisp#Text
Representations. Accessing string and byte fields always creates copies; this
means that changing the return value using aset
will not modify the original
protocol buffer message.
The situation is a bit more complex for submessage fields, repeated fields, and
map fields. Emacs represents values of these fields using specialized types.
For submessage fields, these types are again generated protocol buffer message
types. For repeated and map fields, Emacs uses the types elisp/proto/array
and elisp/proto/map
, respectively. Message, array, and map objects can be
mutable or immutable; attempting to modify an immutable object signals an error.
The Lisp representations of these types are opaque structure-like types. Their
implementation is maintained internally, and you shouldn’t try to access or
modify it directly. Rather, the Emacs Lisp library elisp/proto/proto
contains
the following facilities to use and manipulate these types.
It should be noted that protocol buffer arrays and maps are not “full” types.
You can’t use them as replacement types for vectors or hash tables because
there’s no way to create objects of these types from scratch. You can only
obtain new objects by accessing protocol buffer message fields. This is also
the reason why these types don’t provide implementations of seq-into
,
seq-concatenate
or map-into
that would return new protocol buffer arrays and
maps.
Another important difference between these types and the standard Emacs Lisp types is that protocol buffer arrays and maps are strongly-typed: all their elements have the same type, which is determined when creating the object. For example, you can’t add a string value to a protocol buffer array holding integers.
The Lisp representation of protocol buffer enumerations are plain Lisp constants. See info:elisp#Defining Variables. Their values are just the integral values of the corresponding enumerators.
Whenever Emacs needs to convert a Lisp value to a protocol buffer field value,
an array element, or a map key or value, it accepts values that are compatible
with the destination type. For example, you can use an integer to set a
floating-point protocol buffer message field. Setting a Boolean field accepts
any non-~nil~ value as true
. Setting a repeated field accepts lists, vectors,
and any other generalized sequence type. Setting a map field accepts
hash-tables, association lists, and any other generalized map type.
The nanos
field remains uninitialized.
For every protocol buffer message type, the generated library will also contain
a function {{{code({{{var(type)}}}-new)}}} that you can use as a shorthand for
elisp/proto/make
. For example, you could also write the above example as
(require 'duration_proto)
(google/protobuf/Duration-new :seconds 3600)
To check whether an object is a protocol buffer message object of a given type, the generated libraries contain predicate functions like {{{code({{{var(type)}}}-p)}}}. For example, to test whether an object is a duration protocol buffer message, you can write
(require 'duration_proto)
(google/protobuf/Duration-p object)
You can also use the Common Lisp type predicates like cl-typep
or
cl-check-type
with protocol buffer message objects. See
info:cl#Type Predicates.
The functions described in this section retrieve and manipulate message fields. They all accept a message object as first argument and a field name as second argument. The field name is a plain symbol denoting the unqualified field name.
Instead of specifying plain field names, you can also specify
{{{code(({{{var(field)}}} {{{var(pattern)}}}))}}} pairs. These match the field
value against {{{var(pattern)}}}, which is again a pcase
pattern. For
example, the following code tests whether a duration is strictly positive:
(pcase message
((or (elisp/proto google/protobuf/Duration
(seconds (and (pred cl-plusp) seconds))
nanos)
(elisp/proto google/protobuf/Duration
(seconds (and 0 seconds))
(nanos (and (pred cl-plusp) nanos))))
(message "Duration with %d seconds and %d nanoseconds is positive"
seconds nanos)))
A {{{var(field)}}} construct that is a plain symbol is thus the same as {{{code(({{{var(field)}}} {{{var(field)}}}))}}}.
The primary purpose of protocol buffers is data serialization. The Emacs Lisp protocol buffer bindings support all three major forms of protocol buffer serialization: binary, JSON, and text. However, currently the textual protocol buffer representation can only be generated, not parsed. Since none of the serialized forms are self-describing, you have to explicitly pass the desired message type to the parsing functions.
You can customize the behavior of the parsing and serialization functions to some extend with optional keyword arguments. These are the most common keyword arguments:
:allow-partial
- This keyword argument affects how missing required fields are handled: by default, they cause an error to be signaled, but if the keyword argument is non-~nil~, they are silently ignored, and the result might not be fully initialized.
:discard-unknown
- This keyword argument affects how unknown fields are handled: by default, they cause an error to be signaled, but if the keyword argument is non-~nil~, they are silently ignored.
:deterministic
- If this keyword argument is non-~nil~, serialization functions attempt to produce slightly more deterministic output; however, this attempt is best-effort, since protocol buffer serialization is not guaranteed to be deterministic.
Other keyword arguments are described in the main body of the function definitions below.
You can print a human-readable representation of protocol buffer messages,
arrays, and maps using the functions cl-prin1
, cl-prin1-to-string
, or
cl-print-to-string-with-limit
. However, these objects don’t have a read
syntax; see info:elisp#Printed Representation. Using plain Emacs functions
like print
will result in a representation that’s not very human-readable; see
info:elisp#Read and Print.
The Emacs Lisp protocol buffer bindings contain some dedicated support for a few well-known message types. These are predefined types which are used frequently; see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf.
This section describes a few additional functions that deal with arrays and maps.
The following functions work on ranges in an array. A range is defined by a
start index and an end index; the start index is included in the range, but the
end index is not. If either index is negative, it’s treated as counting from
the end; this facilitates things like addressing the last five elements of an
array. If the end index is left out or nil
, the length of the array is used
instead; this means that passing only a start index addresses the entire
remaining subarray starting at that index. These are exactly the same
conventions that the functions substring
and seq-subseq
use. See
info:elisp#Creating Strings, and see info:elisp#Sequence Functions.