2828import subprocess
2929import tempfile
3030import zipfile
31+ from os .path import dirname , join
3132
3233# runfiles-root-relative path
3334_STAGE2_BOOTSTRAP = "%stage2_bootstrap%"
4243
4344EXTRACT_ROOT = os .environ .get ("RULES_PYTHON_EXTRACT_ROOT" )
4445
46+
4547def print_verbose (* args , mapping = None , values = None ):
4648 if bool (os .environ .get ("RULES_PYTHON_BOOTSTRAP_VERBOSE" )):
4749 if mapping is not None :
@@ -111,7 +113,7 @@ def search_path(name):
111113 search_path = os .getenv ("PATH" , os .defpath ).split (os .pathsep )
112114 for directory in search_path :
113115 if directory :
114- path = os . path . join (directory , name )
116+ path = join (directory , name )
115117 if os .path .isfile (path ) and os .access (path , os .X_OK ):
116118 return path
117119 return None
@@ -132,7 +134,7 @@ def find_binary(runfiles_root, bin_name):
132134 # Use normpath() to convert slashes to os.sep on Windows.
133135 elif os .sep in os .path .normpath (bin_name ):
134136 # Case 3: Path is relative to the repo root.
135- return os . path . join (runfiles_root , bin_name )
137+ return join (runfiles_root , bin_name )
136138 else :
137139 # Case 4: Path has to be looked up in the search path.
138140 return search_path (bin_name )
@@ -154,10 +156,18 @@ def extract_zip(zip_path, dest_dir):
154156 dest_dir = get_windows_path_with_unc_prefix (dest_dir )
155157 with zipfile .ZipFile (zip_path ) as zf :
156158 for info in zf .infolist ():
159+ file_path = os .path .abspath (join (dest_dir , info .filename ))
160+ # If the file exists, it might be a symlink or read-only file from a previous extraction.
161+ # Unlink it first so zipfile.extract doesn't corrupt the symlink target or fail on read-only files.
162+ if os .path .lexists (file_path ) and not os .path .isdir (file_path ):
163+ try :
164+ os .unlink (file_path )
165+ except OSError :
166+ # On Windows, unlinking a read-only file fails.
167+ os .chmod (file_path , stat .S_IWRITE )
168+ os .unlink (file_path )
169+
157170 zf .extract (info , dest_dir )
158- # UNC-prefixed paths must be absolute/normalized. See
159- # https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation
160- file_path = os .path .abspath (os .path .join (dest_dir , info .filename ))
161171 # The Unix st_mode bits (see "man 7 inode") are stored in the upper 16
162172 # bits of external_attr.
163173 attrs = info .external_attr >> 16
@@ -181,10 +191,10 @@ def create_runfiles_root():
181191 extract_root = join (EXTRACT_ROOT , EXTRACT_DIR )
182192 else :
183193 extract_root = tempfile .mkdtemp ("" , "Bazel.runfiles_" )
184- extract_zip (os . path . dirname (__file__ ), extract_root )
194+ extract_zip (dirname (__file__ ), extract_root )
185195 # IMPORTANT: Later code does `rm -fr` on dirname(runfiles_root) -- it's
186196 # important that deletion code be in sync with this directory structure
187- return os . path . join (extract_root , "runfiles" )
197+ return join (extract_root , "runfiles" )
188198
189199
190200def execute_file (
@@ -223,9 +233,10 @@ def execute_file(
223233 try :
224234 subprocess_argv = [python_program ]
225235 if not EXTRACT_ROOT :
226- subprocess_argv .append (f"-XRULES_PYTHON_ZIP_DIR={ os . path . dirname (runfiles_root )} " )
236+ subprocess_argv .append (f"-XRULES_PYTHON_ZIP_DIR={ dirname (runfiles_root )} " )
227237 subprocess_argv .append (main_filename )
228238 subprocess_argv += args
239+ print_verbose ("subprocess argv:" , values = subprocess_argv )
229240 print_verbose ("subprocess env:" , mapping = env )
230241 print_verbose ("subprocess cwd:" , workspace )
231242 print_verbose ("subprocess argv:" , values = subprocess_argv )
@@ -236,7 +247,7 @@ def execute_file(
236247 # NOTE: dirname() is called because create_runfiles_root() creates a
237248 # sub-directory within a temporary directory, and we want to remove the
238249 # whole temporary directory.
239- extract_root = os . path . dirname (runfiles_root )
250+ extract_root = dirname (runfiles_root )
240251 print_verbose ("cleanup: rmtree: " , extract_root )
241252 shutil .rmtree (extract_root , True )
242253
@@ -306,7 +317,7 @@ def main():
306317 # See: https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONSAFEPATH
307318 new_env ["PYTHONSAFEPATH" ] = "1"
308319
309- main_filename = os . path . join (runfiles_root , main_rel_path )
320+ main_filename = join (runfiles_root , main_rel_path )
310321 main_filename = get_windows_path_with_unc_prefix (main_filename )
311322 assert os .path .exists (main_filename ), (
312323 "Cannot exec() %r: file not found." % main_filename
@@ -338,7 +349,7 @@ def main():
338349 # change directory to the right runfiles directory.
339350 # (So that the data files are accessible)
340351 if os .environ .get ("RUN_UNDER_RUNFILES" ) == "1" :
341- workspace = os . path . join (runfiles_root , _WORKSPACE_NAME )
352+ workspace = join (runfiles_root , _WORKSPACE_NAME )
342353
343354 sys .stdout .flush ()
344355 execute_file (
0 commit comments