This repository is a set of bazel build rules that allow you to write a moderately complex book in the Markdown text format, and produce EPUB and Kindle’s AZW3 formats from them. You can also produce a PDF format book, which allows you to preview the results slightly more convenient than by reading the resulting books.
The documentation is available in the following formats:
The build rules currently support pure Markdown formatting, LaTeX-style equations (though not cross-referencing, and in general the amount of LaTeX supported is somewhat limited).
This is not an officially supported Google product. Even though Google owns the copyright. I just happened to work there while I worked on this tool.
bazel_ebook and several of its dependencies are
published in my Bazel
registry rather than the Bazel Central Registry, so a project that
depends on it must declare both registries.
Add the registry to your .bazelrc file.
common --registry=https://raw.githubusercontent.com/filmil/bazel-registry/main
common --registry=https://bcr.bazel.build
Bazel consults registries in the order listed and takes the first one
that has the module, so the custom registry goes first:
that lets it override a module that also exists in the Bazel Central
Registry. This matches this repo’s own .bazelrc and
integration/.bazelrc.
Then declare the dependency in MODULE.bazel:
bazel_dep(name = "bazel_ebook", version = "2.0.15")
bazel_ebook_extension = use_extension(
"@bazel_ebook//:extensions.bzl",
"bazel_ebook_extension",
)
use_repo(
bazel_ebook_extension,
"pandoc_crossref",
"pandoc_ext_include_files",
)
Without the extra --registry line the build fails at
module resolution with
module bazel_ebook@... not found in registries.
If you are impatient to see the rules in action, check out an example book in the integration repository.
The easiest way to dig in is to run the following one-liner:
cd integration && bazel build //...
This will build all the examples for you to appreciate.
Check out a built example here.
Detailed, automatically generated documentation for all public API
items in the .bzl files is available below: * build/attrs.md * build/deps.md * build/pandoc.md * build/pandoc_metadata.md * build/providers.md * build/repositories.md * build/rules.md * build/script.md
The build rules are defined in the file build/rules.bzl. A quick list is here:
| Rule | Description |
|---|---|
asymptote(name, srcs, deps, output) |
This build rule converts Asymptote source files into
images that can be included in the book. This rule can take any
*.asy file in srcs and can depend on any
asymptote rule in deps. |
dot_png(name, srcs, deps, output) |
This build rule converts a Graphviz source files into PNG images
that can be included in the book. This rule can take any
*.dot file in srcs and can depend on any rule
in deps. The .dot file is laid out using the
graphviz program dot. |
drawtiming_png(name, srcs, deps, output, args) |
Typeset a timing diagram using drawtiming. |
ebook_epub(name, deps, metadata_xml, title_yaml, args, additional_inputs) |
This build rule assembles all markdown_lib rules in
sequece and produces a book named [name].epub |
ebook_kindle(name, deps, metadata_xml, title_yaml, args) |
This build rule assembles all markdown_lib rules in
sequence and produces a book named [name].azw3 |
ebook_pdf(name, deps, metadata_xml, title_yaml, args, additional_inputs) |
This build rule assembles all markdown_lib rules in
sequence and produces a book named [name].pdf |
markdown_lib(name, srcs, deps, additional_inputs) |
This build rule makes a library out of */md files.
deps may be any markdown_lib or
asymptote or other such rule, and those will be used
correctly. |
neato_png(name, srcs, deps, output) |
This build rule converts a Graphviz source files into PNG images
that can be included in the book. This rule can take any
*.dot file in srcs and can depend on any rule
in deps. The .dot file is laid out using the
graphviz program neato. |
pandoc_chunked_html(name, deps, metadata, toc, title, args, filters, additional_inputs) |
Use pandoc to convert the
markdown_lib deps listed into a set of chaptered HTML
resources. This is probably the best way to generate a set of
self-contained files. |
pandoc_standalone_html(name, deps, metadata, toc, title, args, filters, additional_inputs) |
Use pandoc to convert the
markdown_lib deps listed into a standalone HTML file. |
plantuml_png(name, srcs, deps, output) |
This build rule converts a PlantUML source files into PNG images
that can be included in the book. This rule can take any
PlantUML-formatted *.txt file in srcs and can
depend on any rule in deps. |
args: (list[string]): verbatim arguments
to be passed to the underlying program.deps: (list[Label]): dependency labels,
can be any generated targets.filters: (list[Label]): a list of pandoc filters to apply, in the order
that they need to appear in the pandoc command line.toc: (bool): whether to generate a table
of contents.metadata: (Label): A label representing a
YAML metadata file. Note that quite a few of these may be specified as
preamble to regular pandoc markdown.These build rules, of course, only explain to bazel how the ebook is to be built. The actual workhorses for building are Docker, pandoc, calibre, LaTeX, Graphviz, Asymptote, drawtiming and PlantUML.
I used automated coding assistance (Claude Code) to rework my prior setup requiring docker, into a plain, fully hermetic setup.
Tag and Release in
.github/workflows/tag-and-release.yml runs weekly and on
workflow_dispatch. It releases only when a commit landed
since the last tag, computes the version from the conventional-commit
titles since that tag, and pushes it. The release itself goes through
bazel-contrib’s release_ruleset.yaml: it runs
bazel test //... at the tag, has
.github/workflows/release_prep.sh build
bazel-ebook-<tag>.zip and the release notes, and
attests the archive’s provenance. A release then lists the archive and
bazel-ebook-<tag>.zip.intoto.jsonl.
The same run publishes the release to my Bazel registry as
a pull request, through
.github/workflows/publish-my-bcr.yml. When the repository
variable PUBLISH_TO_BCR is true, it also opens
a pull request against the Bazel Central Registry through
.github/workflows/publish-official-bcr.yml, with attested
MODULE.bazel and source.json, which is what
the BCR presubmit verifies with slsa-verifier. Only that
publish attests: two attesting publishes would overwrite each other’s
attestation files on the release. The variable stays false
until every dependency is on the Bazel Central Registry.
To check a release the way the BCR does, with the archive downloaded from the release:
slsa-verifier verify-github-attestation \
--attestation-path bazel-ebook-<tag>.zip.intoto.jsonl \
--source-uri github.com/filmil/bazel-ebook \
--builder-id https://github.com/bazel-contrib/.github/.github/workflows/release_ruleset.yaml \
bazel-ebook-<tag>.zip
There are a few constraints to note however: