@@ -670,8 +670,8 @@ def build(self) -> None:
670670"SPHINXERRORHANDLING=" ,
671671maketarget ,
672672 ])
673- run ([ "mkdir" , "-p" , self .log_directory ] )
674- run ([ " chgrp" , "-R" , self .group ,self . log_directory ] )
673+ self .log_directory . mkdir ( parents = True , exist_ok = True )
674+ chgrp ( self . log_directory , group = self .group ,recursive = True )
675675if self .includes_html :
676676setup_switchers (
677677self .switchers_content ,self .checkout / "Doc" / "build" / "html"
@@ -722,10 +722,7 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
722722else :
723723language_dir = self .www_root / self .language .tag
724724language_dir .mkdir (parents = True ,exist_ok = True )
725- try :
726- run (["chgrp" ,"-R" ,self .group ,language_dir ])
727- except subprocess .CalledProcessError as err :
728- logging .warning ("Can't change group of %s: %s" ,language_dir ,str (err ))
725+ chgrp (language_dir ,group = self .group ,recursive = True )
729726language_dir .chmod (0o775 )
730727target = language_dir / self .version .name
731728
@@ -734,22 +731,18 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
734731target .chmod (0o775 )
735732except PermissionError as err :
736733logging .warning ("Can't change mod of %s: %s" ,target ,str (err ))
737- try :
738- run (["chgrp" ,"-R" ,self .group ,target ])
739- except subprocess .CalledProcessError as err :
740- logging .warning ("Can't change group of %s: %s" ,target ,str (err ))
734+ chgrp (target ,group = self .group ,recursive = True )
741735
742736changed = []
743737if self .includes_html :
744738# Copy built HTML files to webroot (default /srv/docs.python.org)
745739changed = changed_files (self .checkout / "Doc" / "build" / "html" ,target )
746740logging .info ("Copying HTML files to %s" ,target )
747- run ([
748- "chown" ,
749- "-R" ,
750- ":" + self .group ,
741+ chgrp (
751742self .checkout / "Doc" / "build" / "html/" ,
752- ])
743+ group = self .group ,
744+ recursive = True
745+ )
753746run (["chmod" ,"-R" ,"o+r" ,self .checkout / "Doc" / "build" / "html" ])
754747run ([
755748"find" ,
@@ -775,20 +768,15 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
775768if not self .quick and (self .checkout / "Doc" / "dist" ).is_dir ():
776769# Copy archive files to /archives/
777770logging .debug ("Copying dist files." )
778- run ([
779- "chown" ,
780- "-R" ,
781- ":" + self .group ,
782- self .checkout / "Doc" / "dist" ,
783- ])
771+ chgrp (self .checkout / "Doc" / "dist" ,group = self .group ,recursive = True )
784772run ([
785773"chmod" ,
786774"-R" ,
787775"o+r" ,
788776self .checkout / "Doc" / "dist" ,
789777 ])
790778run (["mkdir" ,"-m" ,"o+rx" ,"-p" ,target / "archives" ])
791- run ([ "chown" , ":" + self . group , target / "archives" ] )
779+ chgrp ( target / "archives" , group = self . group )
792780run ([
793781"cp" ,
794782"-a" ,
@@ -888,6 +876,29 @@ def save_state(
888876logging .info ("Saved new rebuild state for %s: %s" ,key ,table .as_string ())
889877
890878
879+ def chgrp (path :Path ,/ ,group :int | str | None ,* ,recursive :bool = False )-> None :
880+ if sys .platform == "win32" :
881+ return
882+
883+ from grp import getgrnam
884+
885+ try :
886+ try :
887+ group_id = int (group )
888+ except ValueError :
889+ group_id = getgrnam (group )[2 ]
890+ except (LookupError ,TypeError ,ValueError ):
891+ return
892+
893+ try :
894+ os .chown (path ,- 1 ,group_id )
895+ if recursive :
896+ for p in path .rglob ("*" ):
897+ os .chown (p ,- 1 ,group_id )
898+ except OSError as err :
899+ logging .warning ("Can't change group of %s: %s" ,path ,str (err ))
900+
901+
891902def format_seconds (seconds :float )-> str :
892903hours ,remainder = divmod (seconds ,3600 )
893904minutes ,seconds = divmod (remainder ,60 )
@@ -1212,7 +1223,7 @@ def build_sitemap(
12121223sitemap_path = www_root / "sitemap.xml"
12131224sitemap_path .write_text (rendered_template + "\n " ,encoding = "UTF-8" )
12141225sitemap_path .chmod (0o664 )
1215- run ([ " chgrp" ,group , sitemap_path ] )
1226+ chgrp ( sitemap_path ,group = group )
12161227
12171228
12181229def build_404 (www_root :Path ,group :str )-> None :
@@ -1224,7 +1235,7 @@ def build_404(www_root: Path, group: str) -> None:
12241235not_found_file = www_root / "404.html"
12251236shutil .copyfile (HERE / "templates" / "404.html" ,not_found_file )
12261237not_found_file .chmod (0o664 )
1227- run ([ " chgrp" ,group , not_found_file ] )
1238+ chgrp ( not_found_file ,group = group )
12281239
12291240
12301241def copy_robots_txt (
@@ -1242,7 +1253,7 @@ def copy_robots_txt(
12421253robots_path = www_root / "robots.txt"
12431254shutil .copyfile (template_path ,robots_path )
12441255robots_path .chmod (0o775 )
1245- run ([ " chgrp" ,group , robots_path ] )
1256+ chgrp ( robots_path ,group = group )
12461257if not skip_cache_invalidation :
12471258purge (http ,"robots.txt" )
12481259
@@ -1310,7 +1321,7 @@ def symlink(
13101321# Link does not exist or points to the wrong target.
13111322link .unlink (missing_ok = True )
13121323link .symlink_to (directory )
1313- run ([ "chown" , "-h" , f": { group } " , str ( link )])
1324+ chgrp ( link , group = group ) # --no-dereference
13141325if not skip_cache_invalidation :
13151326surrogate_key = f"{ language_tag } /{ name } "
13161327purge_surrogate_key (http ,surrogate_key )