@@ -10,13 +10,13 @@ import sys
1010import os
1111import subprocess
1212import uuid
13- # runfiles-relative path
1413# NOTE: The sentinel strings are split (e.g., "%stage2" + "_bootstrap%") so that
1514# the substitution logic won't replace them. This allows runtime detection of
1615# unsubstituted placeholders, which occurs when native py_binary is used in
1716# external repositories. In that case, we fall back to %main% which Bazel's
1817# native rule does substitute.
1918_STAGE2_BOOTSTRAP_SENTINEL = "%stage2" + "_bootstrap%"
19+ # runfiles-root-relative path
2020STAGE2_BOOTSTRAP = "%stage2_bootstrap%"
2121
2222# NOTE: The fallback logic from stage2_bootstrap to main is only present
@@ -165,6 +165,29 @@ def print_verbose(*args, mapping=None, values=None):
165165 else :
166166 print ("bootstrap: stage 1:" , * args , file = sys .stderr , flush = True )
167167
168+ def maybe_find_in_manifest (rf_root_path , show_lines = False ):
169+ if not os .environ .get ("RUNFILES_MANIFEST_FILE" ):
170+ return None
171+ if not os .path .exists (os .environ ["RUNFILES_MANIFEST_FILE" ]):
172+ return None
173+ manifest_path = os .environ ["RUNFILES_MANIFEST_FILE" ]
174+ print_verbose ("search manifest for:" , rf_root_path )
175+
176+ # Add trailing space to avoid suffix-string matching
177+ search_for_prefix = rf_root_path .encode ("utf8" ) + b" "
178+ # Use binary to avoid BOM issues on Windows
179+ with open (manifest_path , 'rb' ) as fp :
180+ for line in fp :
181+ if show_lines :
182+ print_verbose ("manifest line:" , repr (line ))
183+ # NOTE: This doesn't handle escaped manifest lines
184+ if line .startswith (search_for_prefix ):
185+ _ , _ , main_filename = line .partition (b" " )
186+ return main_filename .strip ().decode ("utf8" )
187+
188+ return None
189+
190+
168191def FindBinary (module_space , bin_name ):
169192 """Finds the real binary if it's not a normal absolute path."""
170193 if not bin_name :
@@ -180,7 +203,13 @@ def FindBinary(module_space, bin_name):
180203 # Use normpath() to convert slashes to os.sep on Windows.
181204 elif os .sep in os .path .normpath (bin_name ):
182205 # Case 3: Path is relative to the repo root.
183- return os .path .join (module_space , bin_name )
206+ full_path = os .path .join (module_space , bin_name )
207+ if os .path .exists (full_path ):
208+ return full_path
209+ full_path = maybe_find_in_manifest (bin_name , True )
210+ if not full_path :
211+ raise AssertionError (f"Unable to find Python: { bin_name } " )
212+ return full_path
184213 else :
185214 # Case 4: Path has to be looked up in the search path.
186215 return SearchPath (bin_name )
@@ -233,6 +262,19 @@ def FindModuleSpace(main_rel_path):
233262
234263 raise AssertionError ('Cannot find .runfiles directory for %s' % sys .argv [0 ])
235264
265+ def find_main_file (module_space , main_rel_path ):
266+ main_filename = os .path .join (module_space , main_rel_path )
267+ main_filename = GetWindowsPathWithUNCPrefix (main_filename )
268+
269+ if os .path .exists (main_filename ):
270+ return main_filename
271+
272+ main_filename = maybe_find_in_manifest (STAGE2_BOOTSTRAP )
273+ if main_filename :
274+ return main_filename
275+ raise AssertionError (f"Cannot find main filename: { main_rel_path } " )
276+
277+
236278def ExtractZip (zip_path , dest_dir ):
237279 """Extracts the contents of a zip file, preserving the unix file mode bits.
238280
@@ -420,8 +462,7 @@ def Main():
420462 # See: https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONSAFEPATH
421463 new_env ['PYTHONSAFEPATH' ] = '1'
422464
423- main_filename = os .path .join (module_space , main_rel_path )
424- main_filename = GetWindowsPathWithUNCPrefix (main_filename )
465+ main_filename = find_main_file (module_space , main_rel_path )
425466 assert os .path .exists (main_filename ), \
426467 'Cannot exec() %r: file not found.' % main_filename
427468 assert os .access (main_filename , os .R_OK ), \
0 commit comments