bazelbuild / bazelbuild/rules_rust

rustdoc: offer web server as a Bazel binary

Open
#472 8 comments 0 reactions 0 assignees View on GitHub
enhancement needs-triage rustdoc
Dominant language
Starlark
Stars
843
Forks
651
Avg merge
2d 18h
Merged PRs (30d)
15

Description

A `rust_doc` rule spits out a zip file, but that’s not convenient for
actually consulting the docs. It would be great for `rust_doc` to
provide a Bazel binary that can be run to spin up a web server.

Here is one approach. From the Bazel rule, provide this Python file:

```python
import mimetypes
import os
import sys
from wsgiref import simple_server
import zipfile

def main():
(webfiles_zip_name, default_crate, *args) = sys.argv[1:]
port = 8000
if args:
port = int(args[0])

webfiles = os.path.join(os.path.dirname(__file__), webfiles_zip_name)
data = {}
with open(webfiles, "rb") as fp:
with zipfile.ZipFile(fp) as zp:
for path in zp.namelist():
data[path] = zp.read(path)
sys.stderr.write("Read %d files from %s\n" % (len(data), webfiles_zip_name))

default_path = "/%s/index.html" % default_crate

def app(environ, start_response):
p = environ.get("PATH_INFO", "/").lstrip("/")
if not p:
start_response("302 Found", [("Location", default_path)])
yield b"302 Found\n"
return
if p.endswith("/"):
p += "index.html"
blob = data.get(p)
if not blob:
start_response("404 Not Found", [])
yield b"404 Not Found\n"
return
(mime_type, encoding) = mimetypes.guess_type(p)
headers = []
headers.append(("Content-Type", mime_type))
if encoding is not None:
headers.append(("Content-Encoding", encoding))
start_response("200 OK", headers)
yield blob

server = simple_server.make_server("", port, app)
# Find which port was actually bound, in case user requested port 0.
real_port = server.socket.getsockname()[1]
msg = "Serving %s docs on port %d\n" % (default_crate, real_port)
sys.stderr.write(msg)
try:
server.serve_forever()
except KeyboardInterrupt:
print()

if __name__ == "__main__":
main()
```

Then generate a `py_binary` build target:

```starlark
# Assuming `rust_doc(name = "mylib_doc", dep = ":mylib")`, generate:

py_binary(
name = "mylib_doc_server",
srcs = ["mylib_doc_server.py"],
args = [
"mylib_doc.zip", # output of `rust_doc` rule
"mylib", # default crate
],
data = [":mylib_doc.zip"], # output of `rust_doc` rule, again
python_version = "PY3",
srcs_version = "PY3",
)
```

Then, users can `bazel run :mylib_doc_server` to start a server, and
optionally pass a port argument. Or, use `ibazel` instead of `bazel`,
and then whenever you change one of the Rust sources, the Rustdoc will
automatically recompile and the server will automatically restart.

This depends on #471; without that fix, this will be harder.

Thoughts? Are you open to this change? Is it okay to use Python here?
Note that the above simple server only uses the Python standard library.
Happy to contribute a PR, and I license all this code as Apache 2.0.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.