Skip to content

phst/rules_elisp

master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Bazel rules for Emacs Lisp

#

#

#

Introduction

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.

Requirements

To use rules_elisp, you need Bazel. For installation instructions, see Installing Bazel. This repository supports all full Bazel releases starting with Bazel 5.1.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.9. For further instructions how to use Bazel on Windows, see Installing Bazel on Windows. On Windows, you’ll also need MinGW-w64 and MSYS2 to build Emacs; see the file nt/INSTALL.W64 in the Emacs source repository.

This repository generally supports the two most recent major versions of Emacs. Currently, the supported versions are Emacs 27 and Emacs 28. Once Emacs 29 is released, support for Emacs 27 will be dropped.

Usage

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",
    sha256 = "4961dcc5ed58ec4609d64eae8d7aceb9c0b97089e3ad89e86e1f77dce202273f",
    strip_prefix = "rules_elisp-90db75fcd486c6c4d1cadbe33cd4a33aee233597",
    urls = [
        "https://github.com/phst/rules_elisp/archive/90db75fcd486c6c4d1cadbe33cd4a33aee233597.zip",  # 2022-06-23
    ],
)

load(
    "@phst_rules_elisp//elisp:repositories.bzl",
    "rules_elisp_dependencies",
    "rules_elisp_toolchains",
)

rules_elisp_dependencies()

rules_elisp_toolchains()

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.

Load path management

The Emacs Lisp rules by default only add the workspace 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 workspace root. A typical use case is to specify load_path = ["."] to add the current package to the load path.

Runfiles

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

Protocol buffers

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 consists of the package containing the proto_library rule and its name within the package, joined with a slash character (/). 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.

Accessing protocol buffer message fields

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)}}}))}}}.

Parsing and serializing protocol buffer messages

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.

Well-known protocol buffer types

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.

Specialized array and map functions

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.

Symbols defined in //elisp:defs.bzl

Symbols defined in //elisp:repositories.bzl

Symbols defined in //emacs:defs.bzl

Indices

Concept index

Symbol index

Type index