-
Notifications
You must be signed in to change notification settings - Fork 0
/
make_common.py
182 lines (152 loc) · 5.79 KB
/
make_common.py
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT OR Apache-2.0
# SPDX-FileCopyrightText: The Ferrocene Developers
# Convenience script to build Sphinx books, including setting up a Python
# virtual environment to install Sphinx into (removing the need to manage
# dependencies globally). Each book should have a `make.py` script that updates
# the submodules, import this shared module, and calls the main function here.
from pathlib import Path
import argparse
import subprocess
import venv
import sys
def build_docs(root, env, builder, clear, serve, debug):
dest = root / "build"
args = ["-b", builder, "-d", dest / "doctrees"]
if debug:
# Disable parallel builds and show exceptions in debug mode.
#
# We can't show exceptions in parallel mode because in parallel mode
# all exceptions will be swallowed up by Python's multiprocessing.
# That's also why we don't show exceptions outside of debug mode.
args += ["-j", "1", "-T"]
else:
# Enable parallel builds:
args += ["-j", "auto"]
if clear:
args.append("-E")
if serve:
args += ["--watch", root / "exts", "--watch", root / "themes"]
else:
# Error out at the *end* of the build if there are warnings:
args += ["-W", "--keep-going"]
commit = current_git_commit(root)
if commit is not None:
args += ["-D", f"html_theme_options.commit={commit}"]
try:
subprocess.run(
[
env.bin("sphinx-autobuild" if serve else "sphinx-build"),
*args,
root / "src",
dest / builder,
],
check=True,
)
except KeyboardInterrupt:
exit(1)
except subprocess.CalledProcessError:
print("\nhint: if you see an exception, pass --debug to see the full traceback")
exit(1)
return dest / builder
def build_linkchecker(root):
repo = root / ".linkchecker"
src = repo / "src" / "tools" / "linkchecker"
bin = src / "target" / "release" / "linkchecker"
if not src.is_dir():
subprocess.run(["git", "init", repo], check=True)
def git(args):
subprocess.run(["git", *args], cwd=repo, check=True)
# Avoid fetching blobs unless needed by the sparse checkout
git(["remote", "add", "origin", "https://github.com/rust-lang/rust"])
git(["config", "remote.origin.promisor", "true"])
git(["config", "remote.origin.partialCloneFilter", "blob:none"])
# Checkout only the linkchecker tool rather than the whole repo
git(["config", "core.sparsecheckout", "true"])
with open(repo / ".git" / "info" / "sparse-checkout", "w") as f:
f.write("/src/tools/linkchecker/")
# Avoid fetching the whole history
git(["fetch", "--depth=1", "origin", "master"])
git(["checkout", "master"])
if not bin.is_file():
subprocess.run(["cargo", "build", "--release"], cwd=src, check=True)
return bin
def current_git_commit(root):
try:
return (
subprocess.run(
["git", "rev-parse", "HEAD"],
check=True,
stdout=subprocess.PIPE,
)
.stdout.decode("utf-8")
.strip()
)
# `git` executable missing from the system
except FileNotFoundError:
print("warning: failed to detect git commit: missing executable git")
return
# `git` returned an error (git will print the actual error to stderr)
except subprocess.CalledProcessError:
print("warning: failed to detect git commit: git returned an error")
return
class VirtualEnv:
def __init__(self, root, path):
self.path = path
self.requirements = root / "shared" / "requirements.txt"
self.installed_requirements = path / "installed-requirements.txt"
if not self.up_to_date():
self.create()
def bin(self, name):
if sys.platform == "win32":
return self.path / "scripts" / name
else:
return self.path / "bin" / name
def up_to_date(self):
if self.installed_requirements.exists():
expected = self.requirements.read_bytes()
installed = self.installed_requirements.read_bytes()
if expected == installed:
return True
return False
def create(self):
venv.EnvBuilder(clear=True, symlinks=True, with_pip=True).create(self.path)
subprocess.run(
[self.bin("pip"), "install", "-r", self.requirements, "--require-hashes"],
check=True,
)
self.installed_requirements.write_bytes(self.requirements.read_bytes())
def main(root):
root = Path(root)
parser = argparse.ArgumentParser()
parser.add_argument(
"-c", "--clear", help="disable incremental builds", action="store_true"
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-s",
"--serve",
help="start a local server with live reload",
action="store_true",
)
group.add_argument(
"--check-links", help="Check whether all links are valid", action="store_true"
)
group.add_argument(
"--xml", help="Generate Sphinx XML rather than HTML", action="store_true"
)
group.add_argument(
"--debug",
help="Debug mode for the extensions, showing exceptions",
action="store_true",
)
args = parser.parse_args()
env = VirtualEnv(root, root / ".venv")
rendered = build_docs(
root, env, "xml" if args.xml else "html", args.clear, args.serve, args.debug
)
if args.check_links:
linkchecker = build_linkchecker(root)
if subprocess.run([linkchecker, rendered]).returncode != 0:
print("error: linkchecker failed")
exit(1)