Skip to content

Commit 430c1d9

Browse files
committed
Use a config file for easier discoverability.
It was previously "hidden" deep in the py file.
1 parent 3497b2b commit 430c1d9

File tree

2 files changed

+180
-70
lines changed

2 files changed

+180
-70
lines changed

build_docs.py

+88-70
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
"""Build the Python docs for various branches and various languages.
44
5-
Without any arguments builds docs for all active versions configured in the
6-
global VERSIONS list and all languages configured in the LANGUAGES list.
5+
Without any arguments builds docs for all active versions and
6+
languages configured in the config.ini file.
77
88
-q selects "quick build", which means to build only HTML.
99
@@ -20,6 +20,7 @@
2020
"""
2121

2222
from argparse import ArgumentParser
23+
import configparser
2324
from contextlib import suppress
2425
from dataclasses import dataclass
2526
import filecmp
@@ -41,6 +42,7 @@
4142
from pathlib import Path
4243
from string import Template
4344
from textwrap import indent
45+
from typing import Iterable
4446

4547
import zc.lockfile
4648
import jinja2
@@ -154,14 +156,14 @@ def filter(versions, branch=None):
154156
return [v for v in versions if v.status not in ("EOL", "security-fixes")]
155157

156158
@staticmethod
157-
def current_stable():
159+
def current_stable(versions):
158160
"""Find the current stable cPython version."""
159-
return max([v for v in VERSIONS if v.status == "stable"], key=Version.as_tuple)
161+
return max([v for v in versions if v.status == "stable"], key=Version.as_tuple)
160162

161163
@staticmethod
162-
def current_dev():
164+
def current_dev(versions):
163165
"""Find the current de cPython version."""
164-
return max(VERSIONS, key=Version.as_tuple)
166+
return max(versions, key=Version.as_tuple)
165167

166168
@property
167169
def picker_label(self):
@@ -172,7 +174,7 @@ def picker_label(self):
172174
return f"pre ({self.name})"
173175
return self.name
174176

175-
def setup_indexsidebar(self, dest_path):
177+
def setup_indexsidebar(self, versions, dest_path):
176178
"""Build indexsidebar.html for Sphinx."""
177179
with open(
178180
HERE / "templates" / "indexsidebar.html", encoding="UTF-8"
@@ -183,7 +185,7 @@ def setup_indexsidebar(self, dest_path):
183185
sidebar_template.render(
184186
current_version=self,
185187
versions=sorted(
186-
VERSIONS, key=lambda v: version_to_tuple(v.name), reverse=True
188+
versions, key=lambda v: version_to_tuple(v.name), reverse=True
187189
),
188190
)
189191
)
@@ -195,7 +197,6 @@ def __gt__(self, other):
195197
return self.as_tuple() > other.as_tuple()
196198

197199

198-
199200
@dataclass(frozen=True, order=True)
200201
class Language:
201202
tag: str
@@ -206,23 +207,6 @@ class Language:
206207
html_only: bool = False
207208

208209

209-
# EOL and security-fixes are not automatically built, no need to remove them
210-
# from the list, this way we can still rebuild them manually as needed.
211-
#
212-
# Please keep the list in reverse-order for ease of editing.
213-
VERSIONS = [
214-
Version("3.13", branch="origin/main", status="in development"),
215-
Version("3.12", branch="origin/3.12", status="stable"),
216-
Version("3.11", branch="origin/3.11", status="stable"),
217-
Version("3.10", branch="origin/3.10", status="security-fixes"),
218-
Version("3.9", branch="origin/3.9", status="security-fixes"),
219-
Version("3.8", branch="origin/3.8", status="security-fixes"),
220-
Version("3.7", tag="3.7", status="EOL"),
221-
Version("3.6", tag="3.6", status="EOL"),
222-
Version("3.5", tag="3.5", status="EOL"),
223-
Version("2.7", tag="2.7", status="EOL"),
224-
]
225-
226210
XELATEX_DEFAULT = (
227211
"-D latex_engine=xelatex",
228212
"-D latex_elements.inputenc=",
@@ -285,22 +269,6 @@ class Language:
285269
r"-D latex_elements.fontenc=\\usepackage{xeCJK}",
286270
)
287271

288-
LANGUAGES = {
289-
Language("en", "en", "English", True, XELATEX_DEFAULT),
290-
Language("es", "es", "Spanish", True, XELATEX_WITH_FONTSPEC),
291-
Language("fr", "fr", "French", True, XELATEX_WITH_FONTSPEC),
292-
Language("id", "id", "Indonesian", False, XELATEX_DEFAULT),
293-
Language("it", "it", "Italian", False, XELATEX_DEFAULT),
294-
Language("ja", "ja", "Japanese", True, LUALATEX_FOR_JP),
295-
Language("ko", "ko", "Korean", True, XELATEX_FOR_KOREAN),
296-
Language("pl", "pl", "Polish", False, XELATEX_DEFAULT),
297-
Language("pt-br", "pt_BR", "Brazilian Portuguese", True, XELATEX_DEFAULT),
298-
Language("tr", "tr", "Turkish", True, XELATEX_DEFAULT),
299-
Language("uk", "uk", "Ukrainian", False, XELATEX_DEFAULT, html_only=True),
300-
Language("zh-cn", "zh_CN", "Simplified Chinese", True, XELATEX_WITH_CJK),
301-
Language("zh-tw", "zh_TW", "Traditional Chinese", True, XELATEX_WITH_CJK),
302-
}
303-
304272

305273
def run(cmd, cwd=None) -> subprocess.CompletedProcess:
306274
"""Like subprocess.run, with logging before and after the command execution."""
@@ -446,7 +414,9 @@ def edit(file: Path):
446414
temporary.rename(file)
447415

448416

449-
def setup_switchers(html_root: Path):
417+
def setup_switchers(
418+
versions: Iterable[Version], languages: Iterable[Language], html_root: Path
419+
):
450420
"""Setup cross-links between cpython versions:
451421
- Cross-link various languages in a language switcher
452422
- Cross-link various versions in a version switcher
@@ -464,7 +434,7 @@ def setup_switchers(html_root: Path):
464434
sorted(
465435
[
466436
(language.tag, language.name)
467-
for language in LANGUAGES
437+
for language in languages
468438
if language.in_prod
469439
]
470440
)
@@ -475,7 +445,7 @@ def setup_switchers(html_root: Path):
475445
[
476446
(version.name, version.picker_label)
477447
for version in sorted(
478-
VERSIONS,
448+
versions,
479449
key=lambda v: version_to_tuple(v.name),
480450
reverse=True,
481451
)
@@ -499,7 +469,13 @@ def setup_switchers(html_root: Path):
499469
ofile.write(line)
500470

501471

502-
def build_robots_txt(www_root: Path, group, skip_cache_invalidation):
472+
def build_robots_txt(
473+
versions: Iterable[Version],
474+
languages: Iterable[Language],
475+
www_root: Path,
476+
group,
477+
skip_cache_invalidation,
478+
):
503479
"""Disallow crawl of EOL versions in robots.txt."""
504480
if not www_root.exists():
505481
logging.info("Skipping robots.txt generation (www root does not even exists).")
@@ -509,15 +485,17 @@ def build_robots_txt(www_root: Path, group, skip_cache_invalidation):
509485
template = jinja2.Template(template_file.read())
510486
with open(robots_file, "w", encoding="UTF-8") as robots_txt_file:
511487
robots_txt_file.write(
512-
template.render(languages=LANGUAGES, versions=VERSIONS) + "\n"
488+
template.render(languages=languages, versions=versions) + "\n"
513489
)
514490
robots_file.chmod(0o775)
515491
run(["chgrp", group, robots_file])
516492
if not skip_cache_invalidation:
517493
requests.request("PURGE", "https://docs.python.org/robots.txt")
518494

519495

520-
def build_sitemap(www_root: Path, group):
496+
def build_sitemap(
497+
versions: Iterable[Version], languages: Iterable[Language], www_root: Path, group
498+
):
521499
"""Build a sitemap with all live versions and translations."""
522500
if not www_root.exists():
523501
logging.info("Skipping sitemap generation (www root does not even exists).")
@@ -526,7 +504,7 @@ def build_sitemap(www_root: Path, group):
526504
template = jinja2.Template(template_file.read())
527505
sitemap_file = www_root / "sitemap.xml"
528506
sitemap_file.write_text(
529-
template.render(languages=LANGUAGES, versions=VERSIONS) + "\n", encoding="UTF-8"
507+
template.render(languages=languages, versions=versions) + "\n", encoding="UTF-8"
530508
)
531509
sitemap_file.chmod(0o664)
532510
run(["chgrp", group, sitemap_file])
@@ -631,8 +609,9 @@ def parse_args():
631609
parser.add_argument(
632610
"--languages",
633611
nargs="*",
634-
default={language.tag for language in LANGUAGES},
635-
help="Language translation, as a PEP 545 language tag like" " 'fr' or 'pt-br'.",
612+
default="all",
613+
help="Language translation, as a PEP 545 language tag like" " 'fr' or 'pt-br'. "
614+
"Use 'all' to build all of them (it's the default behavior).",
636615
metavar="fr",
637616
)
638617
parser.add_argument(
@@ -677,7 +656,9 @@ class DocBuilder:
677656
"""Builder for a cpython version and a language."""
678657

679658
version: Version
659+
versions: Iterable[Version]
680660
language: Language
661+
languages: Iterable[Language]
681662
build_root: Path
682663
www_root: Path
683664
quick: bool
@@ -783,7 +764,7 @@ def build(self):
783764
if self.version.status in ("in development", "pre-release")
784765
else "stable"
785766
)
786-
+ ("" if self.full_build else "-html")
767+
+ ("" if self.full_build else "-html")
787768
)
788769
logging.info("Running make %s", maketarget)
789770
python = self.venv / "bin" / "python"
@@ -799,7 +780,8 @@ def build(self):
799780
]
800781
)
801782
self.version.setup_indexsidebar(
802-
self.checkout / "Doc" / "tools" / "templates" / "indexsidebar.html"
783+
self.versions,
784+
self.checkout / "Doc" / "tools" / "templates" / "indexsidebar.html",
803785
)
804786
run(
805787
[
@@ -817,7 +799,9 @@ def build(self):
817799
)
818800
run(["mkdir", "-p", self.log_directory])
819801
run(["chgrp", "-R", self.group, self.log_directory])
820-
setup_switchers(self.checkout / "Doc" / "build" / "html")
802+
setup_switchers(
803+
self.versions, self.languages, self.checkout / "Doc" / "build" / "html"
804+
)
821805
logging.info(
822806
"Build done for version: %s, language: %s",
823807
self.version.name,
@@ -990,30 +974,32 @@ def symlink(www_root: Path, language: Language, directory: str, name: str, group
990974
purge_path(www_root, link)
991975

992976

993-
def major_symlinks(www_root: Path, group):
977+
def major_symlinks(
978+
www_root: Path, group, versions: Iterable[Version], languages: Iterable[Language]
979+
):
994980
"""Maintains the /2/ and /3/ symlinks for each languages.
995981
996982
Like:
997983
- /3/ → /3.9/
998984
- /fr/3/ → /fr/3.9/
999985
- /es/3/ → /es/3.9/
1000986
"""
1001-
current_stable = Version.current_stable().name
1002-
for language in LANGUAGES:
987+
current_stable = Version.current_stable(versions).name
988+
for language in languages:
1003989
symlink(www_root, language, current_stable, "3", group)
1004990
symlink(www_root, language, "2.7", "2", group)
1005991

1006992

1007-
def dev_symlink(www_root: Path, group):
993+
def dev_symlink(www_root: Path, group, versions, languages):
1008994
"""Maintains the /dev/ symlinks for each languages.
1009995
1010996
Like:
1011997
- /dev/ → /3.11/
1012998
- /fr/dev/ → /fr/3.11/
1013999
- /es/dev/ → /es/3.11/
10141000
"""
1015-
current_dev = Version.current_dev().name
1016-
for language in LANGUAGES:
1001+
current_dev = Version.current_dev(versions).name
1002+
for language in languages:
10171003
symlink(www_root, language, current_dev, "dev", group)
10181004

10191005

@@ -1051,14 +1037,44 @@ def purge_path(www_root: Path, path: Path):
10511037
run(["curl", "-XPURGE", f"https://docs.python.org/{{{','.join(to_purge)}}}"])
10521038

10531039

1040+
def parse_config():
1041+
config = configparser.ConfigParser()
1042+
config.read(HERE / "config.ini")
1043+
versions, languages = [], []
1044+
for name, section in config.items():
1045+
if section.get("status"): # It's a version
1046+
versions.append(
1047+
Version(
1048+
name,
1049+
status=section["status"],
1050+
branch=section.get("branch"),
1051+
tag=section.get("tag"),
1052+
)
1053+
)
1054+
if section.get("name"): # It's a language
1055+
languages.append(
1056+
Language(
1057+
name,
1058+
section.get("iso639_tag", name),
1059+
section["name"],
1060+
section.getboolean("in_prod", True),
1061+
sphinxopts=globals()[section.get("sphinxopts", "XELATEX_DEFAULT")],
1062+
html_only=section.get("html_only", False),
1063+
)
1064+
)
1065+
return versions, languages
1066+
1067+
10541068
def build_docs(args) -> bool:
10551069
"""Build all docs (each languages and each versions)."""
1056-
languages_dict = {language.tag: language for language in LANGUAGES}
1057-
versions = Version.filter(VERSIONS, args.branch)
1058-
languages = [languages_dict[tag] for tag in args.languages]
1059-
del args.languages
1060-
del args.branch
1061-
todo = list(product(versions, languages))
1070+
versions, languages = parse_config()
1071+
languages_dict = {language.tag: language for language in languages}
1072+
todo = list(
1073+
product(
1074+
Version.filter(versions, args.branch),
1075+
[languages_dict[tag] for tag in args.languages],
1076+
)
1077+
)
10621078
all_built_successfully = True
10631079
while todo:
10641080
version, language = todo.pop()
@@ -1068,11 +1084,13 @@ def build_docs(args) -> bool:
10681084
scope.set_tag("language", language.tag)
10691085
builder = DocBuilder(version, language, **vars(args))
10701086
all_built_successfully &= builder.run()
1071-
build_sitemap(args.www_root, args.group)
1087+
build_sitemap(versions, languages, args.www_root, args.group)
10721088
build_404(args.www_root, args.group)
1073-
build_robots_txt(args.www_root, args.group, args.skip_cache_invalidation)
1074-
major_symlinks(args.www_root, args.group)
1075-
dev_symlink(args.www_root, args.group)
1089+
build_robots_txt(
1090+
versions, languages, args.www_root, args.group, args.skip_cache_invalidation
1091+
)
1092+
major_symlinks(args.www_root, args.group, versions, languages)
1093+
dev_symlink(args.www_root, args.group, versions, languages)
10761094
proofread_canonicals(args.www_root, args.skip_cache_invalidation)
10771095

10781096
return all_built_successfully

0 commit comments

Comments
 (0)