docs: initial doc generation using Sphinx (#1489)

This lays the groundwork for using Sphinx to generate user-facing
documentation and
having it published on readthedocs. It integrates with Bazel Stardoc to
generate
MyST-flavored Markdown that Sphinx can process.

There are 4 basic pieces that are glued together:
1. `sphinx_docs`: This rule invokes Sphinx to generate e.g. html, latex,
etc
2. `sphinx_stardoc`: This rule invokes Stardoc to generate MyST-flavored
Markdown
     that Sphinx can process
3. `sphinx_build_binary`: This rule defines the Sphinx executable with
any necessary
dependencies (e.g. Sphinx extensions, like MyST) to process the docs in
(1)
4. `readthedocs_install`: This rule does the necessary steps to build
the docs and
put them into the location the readthedocs build process expects. This
is basically
just `cp -r`, but its cleaner to hide it behind a `bazel run` command
than have
     to put various shell in the readthedocs yaml config.

* Bump Bazel 6 requirement: 6.0.0 -> 6.20. This is necessary to support
  bzlmod and Stardoc.

Work towards #1332, #1484
diff --git a/sphinxdocs/readthedocs_install.py b/sphinxdocs/readthedocs_install.py
new file mode 100644
index 0000000..9b1f2a8
--- /dev/null
+++ b/sphinxdocs/readthedocs_install.py
@@ -0,0 +1,27 @@
+import os
+import pathlib
+import shutil
+import sys
+
+from python import runfiles
+
+
+def main(args):
+    if not args:
+        raise ValueError("Empty args: expected paths to copy")
+
+    if not (install_to := os.environ.get("READTHEDOCS_OUTPUT")):
+        raise ValueError("READTHEDOCS_OUTPUT environment variable not set")
+
+    install_to = pathlib.Path(install_to)
+
+    rf = runfiles.Create()
+    for doc_dir_runfiles_path in args:
+        doc_dir_path = pathlib.Path(rf.Rlocation(doc_dir_runfiles_path))
+        dest = install_to / doc_dir_path.name
+        print(f"Copying {doc_dir_path} to {dest}")
+        shutil.copytree(src=doc_dir_path, dst=dest, dirs_exist_ok=True)
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))