bazelbuild / bazelbuild/rules_rust
rustdoc: offer web server as a Bazel binary
- 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
Assessment
This issue has not been assessed yet.