Skip to content

Commit 9822eee

Browse files
Recreate venv for each build + install from pylock.toml when available (#345)
Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent 3c18032 commit 9822eee

1 file changed

Lines changed: 64 additions & 38 deletions

File tree

build_docs.py

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ class DocBuilder:
641641
cpython_repo: Repository
642642
docs_by_version_content: bytes
643643
switchers_content: bytes
644+
built_venvs: set[Path]
644645
build_root: Path
645646
www_root: Path
646647
select_output: Literal["no-html", "only-html", "only-html-en"] | None
@@ -804,30 +805,49 @@ def build(self) -> None:
804805
def build_venv(self) -> None:
805806
"""Build a venv for the specific Python version.
806807
807-
So we can reuse them from builds to builds, while they contain
808-
different Sphinx versions.
808+
The venv is created at most once per run, reused by later builds
809+
of the same version, and removed at the end of the run: reusing
810+
a venv across runs can silently keep outdated packages, because
811+
pip considers a requirement satisfied when the installed version
812+
number matches, even if the requirement is a direct URL now
813+
pointing at different code.
809814
"""
815+
venv_name = self.build_meta.venv_name
816+
if self.select_output is not None:
817+
# Never share a venv with a concurrent differently-selected
818+
# build, which may recreate it mid-build.
819+
venv_name += f"-{self.select_output}"
820+
venv_path = self.build_root / venv_name
821+
if venv_path in self.built_venvs:
822+
self.venv = venv_path
823+
return
824+
810825
requirements = list(self.build_meta.dependencies)
811826
if self.includes_html:
812827
# opengraph previews
813828
requirements.append("matplotlib>=3")
814829

815-
venv_path = self.build_root / self.build_meta.venv_name
816-
venv.create(venv_path, symlinks=os.name != "nt", with_pip=True)
830+
venv.create(
831+
venv_path,
832+
symlinks=os.name != "nt",
833+
with_pip=True,
834+
clear=True,
835+
upgrade_deps=True,
836+
)
837+
python = venv_path / "bin" / "python"
838+
839+
if (self.checkout / "Doc" / "pylock.toml").is_file():
840+
requirements.remove("-rrequirements.txt")
841+
run(
842+
(python, "-m", "pip", "install", "-rpylock.toml"),
843+
cwd=self.checkout / "Doc",
844+
)
817845
run(
818-
(
819-
venv_path / "bin" / "python",
820-
"-m",
821-
"pip",
822-
"install",
823-
"--upgrade",
824-
"--upgrade-strategy=eager",
825-
self.theme,
826-
*requirements,
827-
),
846+
(python, "-m", "pip", "install", self.theme, *requirements),
828847
cwd=self.checkout / "Doc",
829848
)
830-
run((venv_path / "bin" / "python", "-m", "pip", "freeze", "--all"))
849+
run((python, "-m", "pip", "freeze", "--all"))
850+
self.built_venvs.add(venv_path)
831851
self.venv = venv_path
832852

833853
def setup_indexsidebar(self) -> None:
@@ -1263,30 +1283,36 @@ def build_docs(args: argparse.Namespace) -> int:
12631283
"https://github.com/python/cpython.git",
12641284
args.build_root / _checkout_name(args.select_output),
12651285
)
1266-
while todo:
1267-
build_props = todo.pop()
1268-
logging.root.handlers[0].setFormatter(
1269-
logging.Formatter(
1270-
f"%(asctime)s %(levelname)s {build_props.slug}: %(message)s"
1286+
built_venvs: set[Path] = set()
1287+
try:
1288+
while todo:
1289+
build_props = todo.pop()
1290+
logging.root.handlers[0].setFormatter(
1291+
logging.Formatter(
1292+
f"%(asctime)s %(levelname)s {build_props.slug}: %(message)s"
1293+
)
12711294
)
1272-
)
1273-
if sentry_sdk:
1274-
scope = sentry_sdk.get_isolation_scope()
1275-
scope.set_tag("version", build_props.version)
1276-
scope.set_tag("language", build_props.language)
1277-
cpython_repo.update()
1278-
builder = DocBuilder(
1279-
build_props,
1280-
cpython_repo,
1281-
docs_by_version_content,
1282-
switchers_content,
1283-
**vars(args),
1284-
)
1285-
built_successfully = builder.run(http, force_build=force_build)
1286-
if built_successfully:
1287-
build_succeeded.add(build_props.slug)
1288-
elif built_successfully is not None:
1289-
any_build_failed = True
1295+
if sentry_sdk:
1296+
scope = sentry_sdk.get_isolation_scope()
1297+
scope.set_tag("version", build_props.version)
1298+
scope.set_tag("language", build_props.language)
1299+
cpython_repo.update()
1300+
builder = DocBuilder(
1301+
build_props,
1302+
cpython_repo,
1303+
docs_by_version_content,
1304+
switchers_content,
1305+
built_venvs,
1306+
**vars(args),
1307+
)
1308+
built_successfully = builder.run(http, force_build=force_build)
1309+
if built_successfully:
1310+
build_succeeded.add(build_props.slug)
1311+
elif built_successfully is not None:
1312+
any_build_failed = True
1313+
finally:
1314+
for venv_path in built_venvs:
1315+
shutil.rmtree(venv_path, ignore_errors=True)
12901316

12911317
logging.root.handlers[0].setFormatter(
12921318
logging.Formatter("%(asctime)s %(levelname)s: %(message)s")

0 commit comments

Comments
 (0)