Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 806d7c9

Browse filesBrowse files
authored
gh-101100: Docs: Check Sphinx warnings and fail if improved (#106460)
1 parent 3372bcb commit 806d7c9
Copy full SHA for 806d7c9

File tree

Expand file treeCollapse file tree

5 files changed

+161
-116
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+161
-116
lines changed

‎.github/workflows/reusable-docs.yml

Copy file name to clipboardExpand all lines: .github/workflows/reusable-docs.yml
+10-17Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,28 @@ jobs:
2828
cache-dependency-path: 'Doc/requirements.txt'
2929
- name: 'Install build dependencies'
3030
run: make -C Doc/ venv
31-
- name: 'Build HTML documentation'
32-
run: make -C Doc/ SPHINXOPTS="-q" SPHINXERRORHANDLING="-W --keep-going" html
3331

34-
# Add pull request annotations for Sphinx nitpicks (missing references)
32+
# To annotate PRs with Sphinx nitpicks (missing references)
3533
- name: 'Get list of changed files'
3634
if: github.event_name == 'pull_request'
3735
id: changed_files
3836
uses: Ana06/get-changed-files@v2.2.0
3937
with:
4038
filter: "Doc/**"
4139
format: csv # works for paths with spaces
42-
- name: 'Build changed files in nit-picky mode'
43-
if: github.event_name == 'pull_request'
40+
- name: 'Build HTML documentation'
4441
continue-on-error: true
4542
run: |
4643
set -Eeuo pipefail
47-
# Mark files the pull request modified
48-
python Doc/tools/touch-clean-files.py --clean '${{ steps.changed_files.outputs.added_modified }}'
49-
# Build docs with the '-n' (nit-picky) option; convert warnings to annotations
50-
make -C Doc/ PYTHON=../python SPHINXOPTS="-q -n --keep-going" html 2>&1 |
51-
python Doc/tools/warnings-to-gh-actions.py
52-
53-
# Ensure some files always pass Sphinx nit-picky mode (no missing references)
54-
- name: 'Build known-good files in nit-picky mode'
44+
# Build docs with the '-n' (nit-picky) option; write warnings to file
45+
make -C Doc/ PYTHON=../python SPHINXOPTS="-q -n -W --keep-going -w sphinx-warnings.txt" html
46+
- name: 'Check warnings'
47+
if: github.event_name == 'pull_request'
5548
run: |
56-
# Mark files that must pass nit-picky
57-
python Doc/tools/touch-clean-files.py
58-
# Build docs with the '-n' (nit-picky) option, convert warnings to errors (-W)
59-
make -C Doc/ PYTHON=../python SPHINXOPTS="-q -n -W --keep-going" html 2>&1
49+
python Doc/tools/check-warnings.py \
50+
--check-and-annotate '${{ steps.changed_files.outputs.added_modified }}' \
51+
--fail-if-regression \
52+
--fail-if-improved
6053
6154
# This build doesn't use problem matchers or check annotations
6255
build_doc_oldest_supported_sphinx:

‎Doc/tools/.nitignore

Copy file name to clipboardExpand all lines: Doc/tools/.nitignore
+2-9Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# All RST files under Doc/ -- except these -- must pass Sphinx nit-picky mode,
2-
# as tested on the CI via touch-clean-files.py in doc.yml.
3-
# Add blank lines between files and keep them sorted lexicographically
4-
# to help avoid merge conflicts.
2+
# as tested on the CI via check-warnings.py in reusable-docs.yml.
3+
# Keep lines sorted lexicographically to help avoid merge conflicts.
54

65
Doc/c-api/allocation.rst
76
Doc/c-api/apiabiversion.rst
@@ -18,7 +17,6 @@ Doc/c-api/complex.rst
1817
Doc/c-api/conversion.rst
1918
Doc/c-api/datetime.rst
2019
Doc/c-api/descriptor.rst
21-
Doc/c-api/dict.rst
2220
Doc/c-api/exceptions.rst
2321
Doc/c-api/file.rst
2422
Doc/c-api/float.rst
@@ -48,7 +46,6 @@ Doc/c-api/typehints.rst
4846
Doc/c-api/typeobj.rst
4947
Doc/c-api/unicode.rst
5048
Doc/c-api/veryhigh.rst
51-
Doc/c-api/weakref.rst
5249
Doc/extending/embedding.rst
5350
Doc/extending/extending.rst
5451
Doc/extending/newtypes.rst
@@ -88,8 +85,6 @@ Doc/library/bdb.rst
8885
Doc/library/bisect.rst
8986
Doc/library/bz2.rst
9087
Doc/library/calendar.rst
91-
Doc/library/cgi.rst
92-
Doc/library/cmath.rst
9388
Doc/library/cmd.rst
9489
Doc/library/code.rst
9590
Doc/library/codecs.rst
@@ -105,7 +100,6 @@ Doc/library/contextlib.rst
105100
Doc/library/copy.rst
106101
Doc/library/csv.rst
107102
Doc/library/ctypes.rst
108-
Doc/library/curses.ascii.rst
109103
Doc/library/curses.rst
110104
Doc/library/datetime.rst
111105
Doc/library/dbm.rst
@@ -134,7 +128,6 @@ Doc/library/fractions.rst
134128
Doc/library/ftplib.rst
135129
Doc/library/functions.rst
136130
Doc/library/functools.rst
137-
Doc/library/getopt.rst
138131
Doc/library/getpass.rst
139132
Doc/library/gettext.rst
140133
Doc/library/graphlib.rst

‎Doc/tools/check-warnings.py

Copy file name to clipboard
+149Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Check the output of running Sphinx in nit-picky mode (missing references).
4+
"""
5+
import argparse
6+
import csv
7+
import os
8+
import re
9+
import sys
10+
from pathlib import Path
11+
12+
# Exclude these whether they're dirty or clean,
13+
# because they trigger a rebuild of dirty files.
14+
EXCLUDE_FILES = {
15+
"Doc/whatsnew/changelog.rst",
16+
}
17+
18+
# Subdirectories of Doc/ to exclude.
19+
EXCLUDE_SUBDIRS = {
20+
".env",
21+
".venv",
22+
"env",
23+
"includes",
24+
"venv",
25+
}
26+
27+
PATTERN = re.compile(r"(?P<file>[^:]+):(?P<line>\d+): WARNING: (?P<msg>.+)")
28+
29+
30+
def check_and_annotate(warnings: list[str], files_to_check: str) -> None:
31+
"""
32+
Convert Sphinx warning messages to GitHub Actions.
33+
34+
Converts lines like:
35+
.../Doc/library/cgi.rst:98: WARNING: reference target not found
36+
to:
37+
::warning file=.../Doc/library/cgi.rst,line=98::reference target not found
38+
39+
Non-matching lines are echoed unchanged.
40+
41+
see:
42+
https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message
43+
"""
44+
files_to_check = next(csv.reader([files_to_check]))
45+
for warning in warnings:
46+
if any(filename in warning for filename in files_to_check):
47+
if match := PATTERN.fullmatch(warning):
48+
print("::warning file={file},line={line}::{msg}".format_map(match))
49+
50+
51+
def fail_if_regression(
52+
warnings: list[str], files_with_expected_nits: set[str], files_with_nits: set[str]
53+
) -> int:
54+
"""
55+
Ensure some files always pass Sphinx nit-picky mode (no missing references).
56+
These are files which are *not* in .nitignore.
57+
"""
58+
all_rst = {
59+
str(rst)
60+
for rst in Path("Doc/").rglob("*.rst")
61+
if rst.parts[1] not in EXCLUDE_SUBDIRS
62+
}
63+
should_be_clean = all_rst - files_with_expected_nits - EXCLUDE_FILES
64+
problem_files = sorted(should_be_clean & files_with_nits)
65+
if problem_files:
66+
print("\nError: must not contain warnings:\n")
67+
for filename in problem_files:
68+
print(filename)
69+
for warning in warnings:
70+
if filename in warning:
71+
if match := PATTERN.fullmatch(warning):
72+
print(" {line}: {msg}".format_map(match))
73+
return -1
74+
return 0
75+
76+
77+
def fail_if_improved(
78+
files_with_expected_nits: set[str], files_with_nits: set[str]
79+
) -> int:
80+
"""
81+
We may have fixed warnings in some files so that the files are now completely clean.
82+
Good news! Let's add them to .nitignore to prevent regression.
83+
"""
84+
files_with_no_nits = files_with_expected_nits - files_with_nits
85+
if files_with_no_nits:
86+
print("\nCongratulations! You improved:\n")
87+
for filename in sorted(files_with_no_nits):
88+
print(filename)
89+
print("\nPlease remove from Doc/tools/.nitignore\n")
90+
return -1
91+
return 0
92+
93+
94+
def main() -> int:
95+
parser = argparse.ArgumentParser()
96+
parser.add_argument(
97+
"--check-and-annotate",
98+
help="Comma-separated list of files to check, "
99+
"and annotate those with warnings on GitHub Actions",
100+
)
101+
parser.add_argument(
102+
"--fail-if-regression",
103+
action="store_true",
104+
help="Fail if known-good files have warnings",
105+
)
106+
parser.add_argument(
107+
"--fail-if-improved",
108+
action="store_true",
109+
help="Fail if new files with no nits are found",
110+
)
111+
args = parser.parse_args()
112+
exit_code = 0
113+
114+
wrong_directory_msg = "Must run this script from the repo root"
115+
assert Path("Doc").exists() and Path("Doc").is_dir(), wrong_directory_msg
116+
117+
with Path("Doc/sphinx-warnings.txt").open() as f:
118+
warnings = f.read().splitlines()
119+
120+
cwd = str(Path.cwd()) + os.path.sep
121+
files_with_nits = {
122+
warning.removeprefix(cwd).split(":")[0]
123+
for warning in warnings
124+
if "Doc/" in warning
125+
}
126+
127+
with Path("Doc/tools/.nitignore").open() as clean_files:
128+
files_with_expected_nits = {
129+
filename.strip()
130+
for filename in clean_files
131+
if filename.strip() and not filename.startswith("#")
132+
}
133+
134+
if args.check_and_annotate:
135+
check_and_annotate(warnings, args.check_and_annotate)
136+
137+
if args.fail_if_regression:
138+
exit_code += fail_if_regression(
139+
warnings, files_with_expected_nits, files_with_nits
140+
)
141+
142+
if args.fail_if_improved:
143+
exit_code += fail_if_improved(files_with_expected_nits, files_with_nits)
144+
145+
return exit_code
146+
147+
148+
if __name__ == "__main__":
149+
sys.exit(main())

‎Doc/tools/touch-clean-files.py

Copy file name to clipboardExpand all lines: Doc/tools/touch-clean-files.py
-65Lines changed: 0 additions & 65 deletions
This file was deleted.

‎Doc/tools/warnings-to-gh-actions.py

Copy file name to clipboardExpand all lines: Doc/tools/warnings-to-gh-actions.py
-25Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.