Skip to content

Commit 7e069e7

Browse files
committed
Fix doc incremental builds; add make serve to run sphinx-autobuild
1 parent c295d9d commit 7e069e7

File tree

5 files changed

+110
-18
lines changed

5 files changed

+110
-18
lines changed

doc/Makefile

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22
#
33

44
# You can set these variables from the command line.
5-
SPHINXOPTS =
6-
SPHINXBUILD = sphinx-build
7-
PAPER =
8-
BUILDDIR = build
5+
SPHINXOPTS =
6+
SPHINXBUILD = sphinx-build
7+
SPHINXAUTOBUILD = sphinx-autobuild
8+
PAPER =
9+
BUILDDIR = build
910

1011
# User-friendly check for sphinx-build
1112
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
1213
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
1314
endif
1415

1516
# Internal variables.
16-
PAPEROPT_a4 = -D latex_paper_size=a4
17-
PAPEROPT_letter = -D latex_paper_size=letter
18-
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
17+
PAPEROPT_a4 = -D latex_paper_size=a4
18+
PAPEROPT_letter = -D latex_paper_size=letter
19+
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
20+
SPHINXAUTOBUILDOPTS = --watch ../arcade
1921
# the i18n builder cannot share the environment and doctrees with the others
20-
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
22+
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
2123

2224
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
2325

@@ -56,6 +58,9 @@ html:
5658
@echo
5759
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
5860

61+
serve:
62+
$(SPHINXAUTOBUILD) $(SPHINXAUTOBUILDOPTS) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
63+
5964
dirhtml:
6065
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
6166
@echo

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ dev = [
5050
"furo",
5151
"pyyaml==6.0",
5252
"sphinx==6.1.3",
53+
"sphinx-autobuild==2021.3.14",
5354
"sphinx-copybutton==0.5.1",
54-
"sphinx-sitemap==2.5.0",
55+
"sphinx-sitemap==2.3.0",
5556
"wheel",
5657
]
5758

util/create_resources_listing.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
44
Generate quick API indexes in Restructured Text Format for Sphinx documentation.
55
"""
6+
import sys
67
import arcade
78
from pathlib import Path
89
from typing import List
910

11+
sys.path.insert(0, str(Path(__file__).parent.resolve()))
12+
from vfs import Vfs
13+
1014
MODULE_DIR = Path(__file__).parent.resolve()
1115
ARCADE_ROOT = MODULE_DIR.parent
1216
RESOURCE_DIR = ARCADE_ROOT / "arcade" / "resources"
@@ -121,7 +125,7 @@ def process_resource_files(out, file_list: List[Path]):
121125

122126

123127
def resources():
124-
out = OUT_FILE.open("w")
128+
out = vfs.open(OUT_FILE, "w")
125129

126130
out.write(".. _resources:\n")
127131
out.write("\n")
@@ -144,9 +148,11 @@ def resources():
144148
out.close()
145149
print("Done creating resources.rst")
146150

151+
vfs = Vfs()
147152

148153
def main():
149154
resources()
155+
vfs.write()
150156

151157

152158
if __name__ == '__main__':

util/update_quick_index.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import os
55
import re
66
from pathlib import Path
7+
import sys
8+
9+
sys.path.insert(0, str(Path(__file__).parent.resolve()))
10+
from vfs import Vfs
711

812
# The project root
913
ROOT = Path(__file__).parent.parent.resolve()
@@ -218,11 +222,9 @@ def process_directory(directory: Path, quick_index_file):
218222

219223
# print(package, title, api_file_name, full_api_file_name)
220224

221-
new_api_file = True
222-
if os.path.isfile(full_api_file_name):
223-
new_api_file = False
225+
new_api_file = not vfs.exists(full_api_file_name)
224226

225-
api_file = open(full_api_file_name, "a")
227+
api_file = vfs.open(full_api_file_name, "a")
226228

227229
if new_api_file:
228230
api_file.write(f".. _{api_file_name[:-4]}_api:")
@@ -324,15 +326,14 @@ def clear_api_directory():
324326
Delete the API files and make new ones
325327
"""
326328
directory = ROOT / "doc/api_docs/api"
327-
file_list = directory.glob('*.rst')
328-
for file in file_list:
329-
os.remove(file)
329+
vfs.delete_glob(str(directory), '*.rst')
330330

331+
vfs = Vfs()
331332

332333
def main():
333334
clear_api_directory()
334335

335-
text_file = open(ROOT / "doc/api_docs/api/quick_index.rst", "w")
336+
text_file = vfs.open(ROOT / "doc/api_docs/api/quick_index.rst", "w")
336337
include_template(text_file)
337338

338339
text_file.write("The arcade module\n")
@@ -371,6 +372,9 @@ def main():
371372
process_directory(ROOT / "arcade/tilemap", text_file)
372373

373374
text_file.close()
375+
376+
vfs.write()
377+
374378
print("Done creating quick_index.rst")
375379

376380

util/vfs.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import os
2+
from pathlib import Path
3+
4+
5+
class Vfs:
6+
"""
7+
Virtual filesystem: write files in-memory, then sync to real fs when done.
8+
Used to avoid touching files that would not change.
9+
This avoids invalidating sphinx cache and causing endless rebuild loops w/
10+
sphinx-autobuild.
11+
"""
12+
def __init__(self):
13+
self.files: dict[str, VirtualFile] = dict()
14+
self.files_to_delete: set[Path] = set()
15+
16+
def delete_glob(self, directory: str | Path, glob: str):
17+
"""
18+
Glob for all files on disk that were created by the previous build.
19+
These files should be deleted if this build does not emit them.
20+
Deletion will not be attempted until this Vfs is synced to disk.
21+
22+
Doing it this way allows us to leave the files untouched on disk if
23+
this build would emit an identical file.
24+
"""
25+
path = Path(str(directory))
26+
for p in path.glob('*.rst'):
27+
self.files_to_delete.add(p)
28+
29+
def write(self):
30+
"""
31+
Sync all files of this Vfs to the real filesystem, and delete any files
32+
from previous builds.
33+
"""
34+
file_paths = [file.path for file in self.files.values()]
35+
for file in self.files.values():
36+
file._write_to_disk()
37+
for path in self.files_to_delete:
38+
if not str(path) in file_paths:
39+
print(f"Deleting {path}")
40+
os.remove(path)
41+
42+
def exists(self, path: str | Path):
43+
return str(path) in self.files
44+
45+
def open(self, path: str | Path, mode: str):
46+
path = str(path)
47+
modes = set(mode)
48+
if "b" in modes:
49+
raise Exception("Binary mode not supported")
50+
if "r" in modes:
51+
raise Exception("Reading from VFS not supported.")
52+
if "a" in modes and path in self.files:
53+
return self.files[path]
54+
self.files[path] = file = VirtualFile(path)
55+
return file
56+
57+
58+
class VirtualFile:
59+
def __init__(self, path: str):
60+
self.path = path
61+
self.content = ''
62+
def write(self, str: str):
63+
self.content += str
64+
def close(self):
65+
pass
66+
def _write_to_disk(self):
67+
before = None
68+
try:
69+
with open(self.path, "r") as f:
70+
before = f.read()
71+
except:
72+
pass
73+
if before != self.content:
74+
print(f"Writing {self.path}")
75+
with open(self.path, "w") as f:
76+
f.write(self.content)

0 commit comments

Comments
 (0)