| import argparse | |
| import logging | |
| import os | |
| import subprocess | |
| import sys | |
| here = os.path.dirname(__file__) | |
| wpt_root = os.path.abspath(os.path.join(here, "..")) | |
| # Directories relative to the wpt root that we want to include in the docs | |
| # Sphinx doesn't support including files outside of docs/ so we temporarily symlink | |
| # these directories under docs/ whilst running the build. | |
| link_dirs = [ | |
| "tools/wptserve", | |
| "tools/certs", | |
| "tools/wptrunner", | |
| "tools/webtransport", | |
| "tools/third_party/pywebsocket3", | |
| ] | |
| logger = logging.getLogger() | |
| def link_source_dirs(): | |
| created = set() | |
| failed = [] | |
| for rel_path in link_dirs: | |
| rel_path = rel_path.replace("/", os.path.sep) | |
| src = os.path.join(wpt_root, rel_path) | |
| dest = os.path.join(here, rel_path) | |
| try: | |
| dest_dir = os.path.dirname(dest) | |
| if not os.path.exists(dest_dir): | |
| os.makedirs(dest_dir) | |
| created.add(dest_dir) | |
| if not os.path.exists(dest): | |
| os.symlink(src, dest, target_is_directory=True) | |
| else: | |
| if (not os.path.islink(dest) or | |
| os.path.join(os.path.dirname(dest), os.readlink(dest)) != src): | |
| # The file exists but it isn't a link or points at the wrong target | |
| raise OSError("File exists") | |
| except Exception as e: | |
| failed.append((dest, e)) | |
| else: | |
| created.add(dest) | |
| return created, failed | |
| def unlink_source_dirs(created): | |
| # Sort backwards in length to remove all files before getting to directory | |
| for path in sorted(created, key=lambda x: -len(x)): | |
| # This will also remove empty parent directories | |
| if not os.path.islink(path) and os.path.isdir(path): | |
| os.removedirs(path) | |
| else: | |
| os.unlink(path) | |
| def get_parser(): | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--type", default="html", help="Output type (default: html)") | |
| p.add_argument("--docker", action="store_true", | |
| help="Run inside the docs docker image") | |
| p.add_argument("--serve", nargs="?", const=8000, | |
| type=int, help="Run a server on the specified port (default: 8000)") | |
| return p | |
| def docker_build(tag="wpt:docs"): | |
| subprocess.check_call(["docker", | |
| "build", | |
| "--pull", | |
| "--tag", tag, | |
| here]) | |
| def docker_run(**kwargs): | |
| cmd = ["docker", "run"] | |
| cmd.extend(["--mount", | |
| "type=bind,source=%s,target=/app/web-platform-tests" % wpt_root]) | |
| if kwargs["serve"] is not None: | |
| serve = str(kwargs["serve"]) | |
| cmd.extend(["--expose", serve, "--publish", f"{serve}:{serve}"]) | |
| cmd.extend(["-w", "/app/web-platform-tests"]) | |
| if os.isatty(os.isatty(sys.stdout.fileno())): | |
| cmd.append("-it") | |
| cmd.extend(["wpt:docs", "./wpt"]) | |
| # /app/venv is created during docker build and is always active inside the | |
| # container. | |
| cmd.extend(["--venv", "/app/venv", "--skip-venv-setup"]) | |
| cmd.extend(["build-docs", "--type", kwargs["type"]]) | |
| if kwargs["serve"] is not None: | |
| cmd.extend(["--serve", str(kwargs["serve"])]) | |
| logger.debug(" ".join(cmd)) | |
| return subprocess.call(cmd) | |
| def build(_venv, **kwargs): | |
| if kwargs["docker"]: | |
| docker_build() | |
| return docker_run(**kwargs) | |
| out_dir = os.path.join(here, "_build") | |
| try: | |
| created, failed = link_source_dirs() | |
| if failed: | |
| failure_msg = "\n".join(f"{dest}: {err}" for (dest, err) in failed) | |
| logger.error(f"Failed to create source symlinks:\n{failure_msg}") | |
| sys.exit(1) | |
| if kwargs["serve"] is not None: | |
| executable = "sphinx-autobuild" | |
| extras = ["--port", str(kwargs["serve"]), | |
| "--host", "0.0.0.0", | |
| "--watch", os.path.abspath(os.path.join(here, os.pardir, "resources")), | |
| # Ignore changes to files specified with glob pattern | |
| "--ignore", "**/flycheck_*", | |
| "--ignore", "**/.*", | |
| "--ignore", "**/#*", | |
| "--ignore", "docs/frontend.py", | |
| "--ignore", "docs/Dockerfile"] | |
| else: | |
| executable = "sphinx-build" | |
| extras = [] | |
| cmd = [executable, "-n", "-v", "-b", kwargs["type"], "-j", "auto"] + extras + [here, out_dir] | |
| logger.debug(" ".join(cmd)) | |
| subprocess.check_call(cmd) | |
| finally: | |
| unlink_source_dirs(created) |