summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/docs/frontend.py
blob: c8d114b39f6fc2b35d31ee80f82f43e2f9be0b9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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", default=None, 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)