metropolis/handbook: init
This is the beginning of the Metropolis Handbook, the main end-user
documentation of Metropolis.
It is built using mdbook, and currently contains only the default
content from `mdbook init`.
Future work: start writing the handbook, build in CI, publish in CI.
Change-Id: I81753350215b2f7aabc17925eadfd20706e1fdb5
Reviewed-on: https://review.monogon.dev/c/monogon/+/202
Reviewed-by: Leopold Schabel <leo@nexantic.com>
diff --git a/metropolis/handbook/.gitignore b/metropolis/handbook/.gitignore
new file mode 100644
index 0000000..7585238
--- /dev/null
+++ b/metropolis/handbook/.gitignore
@@ -0,0 +1 @@
+book
diff --git a/metropolis/handbook/BUILD.bazel b/metropolis/handbook/BUILD.bazel
new file mode 100644
index 0000000..72c3490
--- /dev/null
+++ b/metropolis/handbook/BUILD.bazel
@@ -0,0 +1,7 @@
+load("//metropolis/handbook:defs.bzl", "mdbook_html")
+
+mdbook_html(
+ name = "handbook",
+ srcs = glob(["src/**/*.md"]),
+ title = "Metropolis Handbook",
+)
diff --git a/metropolis/handbook/README.md b/metropolis/handbook/README.md
new file mode 100644
index 0000000..0864428
--- /dev/null
+++ b/metropolis/handbook/README.md
@@ -0,0 +1,38 @@
+Metropolis Handbook
+===
+
+This directory contains the sources of the Metropolis Handbook end-user documentation.
+
+Layout
+---
+
+Everything within `//monogon/handbook/src` will be used to generate documentation with [mdbook](https://rust-lang.github.io/mdBook/format/index.html).
+
+Compared to upstream mdbook we do not have a static `book.toml` file, one is instead generated as part of the build process. See the definition of the `//metropolis/handbook` target to change some of the options.
+
+Building
+---
+
+ bazel build //metropolis/handbook
+
+Then, you can visit the following file in your browser:
+
+ bazel-bin/metropolis/handbook/handbook/index.html
+
+To view the built documentation.
+
+Interactive editing
+---
+
+For faster edit/check loops of the handbook, you can use `ibazel`:
+
+ ibazel build //metropolis/handbook
+
+This will automatically rebuild the handbook any time some source changes.
+
+You will still need to manually refresh your browser to see any changes. This could be made better, if needed, by injecting some [ibazel-compatible live reload javascript](https://github.com/bazelbuild/bazel-watcher/blob/84cab6f15f64850fb972ea88701e634c8b611301/example_client/example_client.go#L24) to automatically reload the page on changes, or by adding a target which launches `mdbook serve`.
+
+Publishing
+---
+
+We currently do not build the handbook automatically and/or publish it anywhere.
diff --git a/metropolis/handbook/defs.bzl b/metropolis/handbook/defs.bzl
new file mode 100644
index 0000000..c875980
--- /dev/null
+++ b/metropolis/handbook/defs.bzl
@@ -0,0 +1,111 @@
+def _mdbook_html_impl(ctx):
+ # We will be generating our own book.toml based on this rule's configuration.
+ #
+ # We do this because:
+ # - The book.toml must contain a reference to the source files of the
+ # generated book. This only works as long as the source files are not
+ # generated by Bazel.
+ # - The book.toml file effectively describes a build, so it makes sense to
+ # port that over to be fully managed by the Bazel rule for mdbook. This
+ # makes things more consistent with the rest of our Bazel usage, at the
+ # expense of slightly deviating from how upstream does things.
+ #
+ # We emit the toml into `book.toml` because that's what mdbook needs.
+ # However, instead of emitting it in a subdirectory named after this
+ # target, we do that in `${target}_`. This is so that we can use the target
+ # name as a directory containing the actual generated HTML files, making
+ # the life of developers using this rule a bit easier.
+ out_book_toml = ctx.actions.declare_file(ctx.attr.name + "_/book.toml")
+
+ # Find root of given handbook from srcs - there must be exactly one
+ # SUMMARY.md and the parent directory of that is the root.
+ summary = None
+ for f in ctx.files.srcs:
+ if not f.path.endswith('/SUMMARY.md'):
+ continue
+ if summary != None:
+ fail("More then one SUMMARY.md provided.")
+ summary = f
+ if summary == None:
+ fail("No SUMMARY.md provided in srcs.")
+
+ # We now have the SUMMARY.md file from which we can figure out the source
+ # directory of the book. However, mdbook takes a source root path relative
+ # to the book.toml file, not one relative to the current working
+ # directory... Thus, we need to prepend a list of '../' elements that bring
+ # mdbook down from the book.toml location back into the workspace execution
+ # root, which is where our SUMMARY.md path is itself rooted.
+ #
+ # For example, if book.toml lives in:
+ # execroot/dev_source_monogon/bazel-out/k8-fastbuild/bin/metropolis/handbook/handbook_/book.toml
+ # Then we will need to prepend:
+ # ../../../../../../../
+ # To get back to execroot/.
+ prepend = len(out_book_toml.path.split('/')) - 1
+ src_dir_path = ('../' * prepend) + summary.dirname
+
+ # Generate book.toml.
+ # Bazel does not have a toml library. We abuse JSON encoding to get
+ # serialized list/string data as an acceptable substitute to building a
+ # fully self-standing toml serializer for Bazel.
+ book_toml_contents = [
+ "[book]",
+ "title = {}".format(json.encode(ctx.attr.title)),
+ "authors = {}".format(json.encode(ctx.attr.authors)),
+ "language = {}".format(json.encode(ctx.attr.language)),
+ "multilingual = false",
+ "src = {}".format(json.encode(src_dir_path)),
+ ]
+ ctx.actions.write(
+ output = out_book_toml,
+ content = "\n".join(book_toml_contents)
+ )
+
+ out_dir = ctx.actions.declare_directory(ctx.attr.name)
+ # We also have to prepend the out dir path, for the same reasons for which
+ # we prepend src_dir_path above.
+ out_dir_path = ('../' * prepend) + out_dir.path
+ ctx.actions.run(
+ executable = ctx.executable._mdbook,
+ arguments = [
+ "build",
+ "-d", out_dir_path,
+ out_book_toml.dirname,
+ ],
+ inputs = ctx.files.srcs + [ out_book_toml ],
+ outputs = [ out_dir ],
+ )
+ return [
+ DefaultInfo(
+ files = depset([out_dir]),
+ )
+ ]
+
+mdbook_html = rule(
+ doc = "Build an mdbook source root into HTML files.",
+ implementation = _mdbook_html_impl,
+ attrs = {
+ "title": attr.string(
+ doc = "The title of the generated book.",
+ ),
+ "authors": attr.string_list(
+ default = ["Monogon Project Authors"],
+ doc = "The authors of the generated book.",
+ ),
+ "language": attr.string(
+ default = "en",
+ doc = "The language of the generated book.",
+ ),
+ "srcs": attr.label_list(
+ allow_files = True,
+ doc = "The sources of the generated book. Exaclty one file must be named SUMMARY.md, and that file's location will be used to determine the root of the book sources.",
+ ),
+
+ "_mdbook": attr.label(
+ doc = "The mdbook tool.",
+ executable = True,
+ cfg = "host",
+ default = "@dev_source_monogon//third_party/rust:cargo_bin_mdbook",
+ ),
+ },
+)
diff --git a/metropolis/handbook/src/SUMMARY.md b/metropolis/handbook/src/SUMMARY.md
new file mode 100644
index 0000000..7390c82
--- /dev/null
+++ b/metropolis/handbook/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)
diff --git a/metropolis/handbook/src/chapter_1.md b/metropolis/handbook/src/chapter_1.md
new file mode 100644
index 0000000..6786da2
--- /dev/null
+++ b/metropolis/handbook/src/chapter_1.md
@@ -0,0 +1,2 @@
+# Chapter 1
+