Skip to content

Fix sphinx incremental builds; add make serve to run sphinx-autobuild #1710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,35 @@ pytest tests/unit

### Building & Testing Documentation

#### Automatic Rebuild with Live Reload

You can build & preview documentation locally using the following steps.

Change into the doc directory:
```commandline
cd doc
```

Run the doc build to build the web page files, and host a webserver to preview:
```commandline
make serve
```

You can now open [http://localhost:8000](http://localhost:8000) in your browser to preview the docs.

The `build/html` directory will contain the generated website files. When you change source files,
it will automatically regenerate, and browser tabs will automatically refresh to show your updates.

If you suspect the automatic rebuilds are failing to detect changes, you can
run a simpler one-time build using the following instructions.

#### One-time build

Change into the doc directory:
```commandline
cd doc
```

Run the doc build to build the web page files:
```commandline
make html
Expand All @@ -105,4 +127,4 @@ python -m http.server -d build/html

You can now open [http://localhost:8000](http://localhost:8000) in your browser to preview the doc.

Be sure to re-run build & refresh to update after making changes!
Be sure to re-run build & refresh to update after making changes!
21 changes: 13 additions & 8 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXAUTOBUILD = sphinx-autobuild
PAPER =
BUILDDIR = build

# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(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/)
endif

# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
SPHINXAUTOBUILDOPTS = --watch ../arcade
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

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

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

serve:
$(SPHINXAUTOBUILD) $(SPHINXAUTOBUILDOPTS) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ dev = [
"furo",
"pyyaml==6.0",
"sphinx==6.1.3",
"sphinx-autobuild==2021.3.14",
"sphinx-copybutton==0.5.1",
"sphinx-sitemap==2.5.0",
# Intentionally kept at 2.3 until this bugfix is published: https://github.com/jdillard/sphinx-sitemap/pull/62
"sphinx-sitemap==2.3.0",
"wheel",
]

Expand Down
11 changes: 9 additions & 2 deletions util/create_resources_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@

Generate quick API indexes in Restructured Text Format for Sphinx documentation.
"""
import arcade
import sys
from pathlib import Path
from typing import List

sys.path.insert(0, str(Path(__file__).parent.resolve()))
sys.path.insert(0, str(Path(__file__).parent.parent.resolve()))
import arcade
from vfs import Vfs

MODULE_DIR = Path(__file__).parent.resolve()
ARCADE_ROOT = MODULE_DIR.parent
RESOURCE_DIR = ARCADE_ROOT / "arcade" / "resources"
Expand Down Expand Up @@ -121,7 +126,7 @@ def process_resource_files(out, file_list: List[Path]):


def resources():
out = OUT_FILE.open("w")
out = vfs.open(OUT_FILE, "w")

out.write(".. _resources:\n")
out.write("\n")
Expand All @@ -144,9 +149,11 @@ def resources():
out.close()
print("Done creating resources.rst")

vfs = Vfs()

def main():
resources()
vfs.write()


if __name__ == '__main__':
Expand Down
20 changes: 12 additions & 8 deletions util/update_quick_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import os
import re
from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.resolve()))
from vfs import Vfs

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

# print(package, title, api_file_name, full_api_file_name)

new_api_file = True
if os.path.isfile(full_api_file_name):
new_api_file = False
new_api_file = not vfs.exists(full_api_file_name)

api_file = open(full_api_file_name, "a")
api_file = vfs.open(full_api_file_name, "a")

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

vfs = Vfs()

def main():
clear_api_directory()

text_file = open(ROOT / "doc/api_docs/api/quick_index.rst", "w")
text_file = vfs.open(ROOT / "doc/api_docs/api/quick_index.rst", "w")
include_template(text_file)

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

text_file.close()

vfs.write()

print("Done creating quick_index.rst")


Expand Down
77 changes: 77 additions & 0 deletions util/vfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import os
from pathlib import Path
from typing import Union


class Vfs:
"""
Virtual filesystem: write files in-memory, then sync to real fs when done.
Used to avoid touching files that would not change.
This avoids invalidating sphinx cache and causing endless rebuild loops w/
sphinx-autobuild.
"""
def __init__(self):
self.files: dict[str, VirtualFile] = dict()
self.files_to_delete: set[Path] = set()

def delete_glob(self, directory: Union[str, Path], glob: str):
"""
Glob for all files on disk that were created by the previous build.
These files should be deleted if this build does not emit them.
Deletion will not be attempted until this Vfs is synced to disk.

Doing it this way allows us to leave the files untouched on disk if
this build would emit an identical file.
"""
path = Path(str(directory))
for p in path.glob('*.rst'):
self.files_to_delete.add(p)

def write(self):
"""
Sync all files of this Vfs to the real filesystem, and delete any files
from previous builds.
"""
file_paths = [file.path for file in self.files.values()]
for file in self.files.values():
file._write_to_disk()
for path in self.files_to_delete:
if not str(path) in file_paths:
print(f"Deleting {path}")
os.remove(path)

def exists(self, path: Union[str, Path]):
return str(path) in self.files

def open(self, path: Union[str, Path], mode: str):
path = str(path)
modes = set(mode)
if "b" in modes:
raise Exception("Binary mode not supported")
if "r" in modes:
raise Exception("Reading from VFS not supported.")
if "a" in modes and path in self.files:
return self.files[path]
self.files[path] = file = VirtualFile(path)
return file


class VirtualFile:
def __init__(self, path: str):
self.path = path
self.content = ''
def write(self, str: str):
self.content += str
def close(self):
pass
def _write_to_disk(self):
before = None
try:
with open(self.path, "r") as f:
before = f.read()
except:
pass
if before != self.content:
print(f"Writing {self.path}")
with open(self.path, "w") as f:
f.write(self.content)