26
26
# We just put them in one place so its easy to tell which are used.
27
27
28
28
# Runfiles-relative path to the main Python source file.
29
- MAIN = "%main%"
29
+ # Empty if MAIN_MODULE is used
30
+ MAIN_PATH = "%main%"
31
+
32
+ # Module name to execute. Empty if MAIN is used.
33
+ MAIN_MODULE = "%main_module%"
30
34
31
35
# ===== Template substitutions end =====
32
36
@@ -249,7 +253,7 @@ def unresolve_symlinks(output_filename):
249
253
os .unlink (unfixed_file )
250
254
251
255
252
- def _run_py (main_filename , * , args , cwd = None ):
256
+ def _run_py_path (main_filename , * , args , cwd = None ):
253
257
# type: (str, str, list[str], dict[str, str]) -> ...
254
258
"""Executes the given Python file using the various environment settings."""
255
259
@@ -269,6 +273,11 @@ def _run_py(main_filename, *, args, cwd=None):
269
273
sys .argv = orig_argv
270
274
271
275
276
+ def _run_py_module (module_name ):
277
+ # Match `python -m` behavior, so modify sys.argv and the run name
278
+ runpy .run_module (module_name , alter_sys = True , run_name = "__main__" )
279
+
280
+
272
281
@contextlib .contextmanager
273
282
def _maybe_collect_coverage (enable ):
274
283
print_verbose_coverage ("enabled:" , enable )
@@ -356,64 +365,79 @@ def main():
356
365
print_verbose ("initial environ:" , mapping = os .environ )
357
366
print_verbose ("initial sys.path:" , values = sys .path )
358
367
359
- main_rel_path = MAIN
360
- if is_windows ():
361
- main_rel_path = main_rel_path .replace ("/" , os .sep )
362
-
363
- module_space = find_runfiles_root (main_rel_path )
364
- print_verbose ("runfiles root:" , module_space )
365
-
366
- # Recreate the "add main's dir to sys.path[0]" behavior to match the
367
- # system-python bootstrap / typical Python behavior.
368
- #
369
- # Without safe path enabled, when `python foo/bar.py` is run, python will
370
- # resolve the foo/bar.py symlink to its real path, then add the directory
371
- # of that path to sys.path. But, the resolved directory for the symlink
372
- # depends on if the file is generated or not.
373
- #
374
- # When foo/bar.py is a source file, then it's a symlink pointing
375
- # back to the client source directory. This means anything from that source
376
- # directory becomes importable, i.e. most code is importable.
377
- #
378
- # When foo/bar.py is a generated file, then it's a symlink pointing to
379
- # somewhere under bazel-out/.../bin, i.e. where generated files are. This
380
- # means only other generated files are importable (not source files).
381
- #
382
- # To replicate this behavior, we add main's directory within the runfiles
383
- # when safe path isn't enabled.
384
- if not getattr (sys .flags , "safe_path" , False ):
385
- prepend_path_entries = [
386
- os .path .join (module_space , os .path .dirname (main_rel_path ))
387
- ]
368
+ main_rel_path = None
369
+ # todo: things happen to work because find_runfiles_root
370
+ # ends up using stage2_bootstrap, and ends up computing the proper
371
+ # runfiles root
372
+ if MAIN_PATH :
373
+ main_rel_path = MAIN_PATH
374
+ if is_windows ():
375
+ main_rel_path = main_rel_path .replace ("/" , os .sep )
376
+
377
+ runfiles_root = find_runfiles_root (main_rel_path )
388
378
else :
389
- prepend_path_entries = []
379
+ runfiles_root = find_runfiles_root ("" )
380
+
381
+ print_verbose ("runfiles root:" , runfiles_root )
390
382
391
- runfiles_envkey , runfiles_envvalue = runfiles_envvar (module_space )
383
+ runfiles_envkey , runfiles_envvalue = runfiles_envvar (runfiles_root )
392
384
if runfiles_envkey :
393
385
os .environ [runfiles_envkey ] = runfiles_envvalue
394
386
395
- main_filename = os .path .join (module_space , main_rel_path )
396
- main_filename = get_windows_path_with_unc_prefix (main_filename )
397
- assert os .path .exists (main_filename ), (
398
- "Cannot exec() %r: file not found." % main_filename
399
- )
400
- assert os .access (main_filename , os .R_OK ), (
401
- "Cannot exec() %r: file not readable." % main_filename
402
- )
387
+ if MAIN_PATH :
388
+ # Recreate the "add main's dir to sys.path[0]" behavior to match the
389
+ # system-python bootstrap / typical Python behavior.
390
+ #
391
+ # Without safe path enabled, when `python foo/bar.py` is run, python will
392
+ # resolve the foo/bar.py symlink to its real path, then add the directory
393
+ # of that path to sys.path. But, the resolved directory for the symlink
394
+ # depends on if the file is generated or not.
395
+ #
396
+ # When foo/bar.py is a source file, then it's a symlink pointing
397
+ # back to the client source directory. This means anything from that source
398
+ # directory becomes importable, i.e. most code is importable.
399
+ #
400
+ # When foo/bar.py is a generated file, then it's a symlink pointing to
401
+ # somewhere under bazel-out/.../bin, i.e. where generated files are. This
402
+ # means only other generated files are importable (not source files).
403
+ #
404
+ # To replicate this behavior, we add main's directory within the runfiles
405
+ # when safe path isn't enabled.
406
+ if not getattr (sys .flags , "safe_path" , False ):
407
+ prepend_path_entries = [
408
+ os .path .join (runfiles_root , os .path .dirname (main_rel_path ))
409
+ ]
410
+ else :
411
+ prepend_path_entries = []
412
+
413
+ main_filename = os .path .join (runfiles_root , main_rel_path )
414
+ main_filename = get_windows_path_with_unc_prefix (main_filename )
415
+ assert os .path .exists (main_filename ), (
416
+ "Cannot exec() %r: file not found." % main_filename
417
+ )
418
+ assert os .access (main_filename , os .R_OK ), (
419
+ "Cannot exec() %r: file not readable." % main_filename
420
+ )
403
421
404
- sys .stdout .flush ()
422
+ sys .stdout .flush ()
405
423
406
- sys .path [0 :0 ] = prepend_path_entries
424
+ sys .path [0 :0 ] = prepend_path_entries
425
+ else :
426
+ main_filename = None
407
427
408
428
if os .environ .get ("COVERAGE_DIR" ):
409
429
import _bazel_site_init
430
+
410
431
coverage_enabled = _bazel_site_init .COVERAGE_SETUP
411
432
else :
412
433
coverage_enabled = False
413
434
414
435
with _maybe_collect_coverage (enable = coverage_enabled ):
415
- # The first arg is this bootstrap, so drop that for the re-invocation.
416
- _run_py (main_filename , args = sys .argv [1 :])
436
+ if MAIN_PATH :
437
+ # The first arg is this bootstrap, so drop that for the re-invocation.
438
+ _run_py_path (main_filename , args = sys .argv [1 :])
439
+ else :
440
+ _run_py_module (MAIN_MODULE )
417
441
sys .exit (0 )
418
442
419
443
0 commit comments