@@ -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