@@ -475,7 +475,7 @@ WARNING: Target: {}
475475 # The interpreter is added this late in the process so that it isn't
476476 # added to the zipped files.
477477 if venv and venv .interpreter :
478- extra_runfiles = extra_runfiles .merge (ctx . runfiles ([ venv .interpreter ]) )
478+ extra_runfiles = extra_runfiles .merge (venv .interpreter_runfiles )
479479 return struct (
480480 # depset[File] of additional files that should be included as default
481481 # outputs.
@@ -524,25 +524,103 @@ def _create_zip_main(ctx, *, stage2_bootstrap, runtime_details, venv):
524524# * https://github.com/python/cpython/blob/main/Modules/getpath.py
525525# * https://github.com/python/cpython/blob/main/Lib/site.py
526526def _create_venv (ctx , output_prefix , imports , runtime_details , add_runfiles_root_to_sys_path , extra_deps ):
527- venv = "_{}.venv" .format (output_prefix .lstrip ("_" ))
528-
529- # The pyvenv.cfg file must be present to trigger the venv site hooks.
530- # Because it's paths are expected to be absolute paths, we can't reliably
531- # put much in it. See https://github.com/python/cpython/issues/83650
532- pyvenv_cfg = ctx .actions .declare_file ("{}/pyvenv.cfg" .format (venv ))
533- ctx .actions .write (pyvenv_cfg , "" )
527+ venv_root = "_{}.venv" .format (output_prefix .lstrip ("_" ))
528+ runtime = runtime_details .effective_runtime
529+ if runtime .interpreter :
530+ interpreter_actual_path = runfiles_root_path (ctx , runtime .interpreter .short_path )
531+ else :
532+ interpreter_actual_path = runtime .interpreter_path
534533
535- is_bootstrap_script = BootstrapImplFlag .get_value (ctx ) == BootstrapImplFlag .SCRIPT
536534 is_windows = target_platform_has_any_constraint (ctx , ctx .attr ._windows_constraints )
535+ if is_windows :
536+ venv_details = _create_venv_windows (ctx , venv_root = venv_root ,
537+ interpreter_actual_path = interpreter_actual_path ,
538+ runtime = runtime ,
539+ )
540+ else :
541+ venv_details = _create_venv_unixy (ctx , venv_root = venv_root ,
542+ interpreter_actual_path = interpreter_actual_path ,
543+ runtime = runtime ,
544+ )
537545
538- create_full_venv = True
546+ site_packages = "{}/{}" .format (venv_root , venv_details .venv_site_packages )
547+
548+ pth = ctx .actions .declare_file ("{}/bazel.pth" .format (site_packages ))
549+ ctx .actions .write (pth , "import _bazel_site_init\n " )
550+
551+ site_init = ctx .actions .declare_file ("{}/_bazel_site_init.py" .format (site_packages ))
552+ computed_subs = ctx .actions .template_dict ()
553+ computed_subs .add_joined ("%imports%" , imports , join_with = ":" , map_each = _map_each_identity )
554+ ctx .actions .expand_template (
555+ template = runtime .site_init_template ,
556+ output = site_init ,
557+ substitutions = {
558+ "%add_runfiles_root_to_sys_path%" : add_runfiles_root_to_sys_path ,
559+ "%coverage_tool%" : _get_coverage_tool_runfiles_path (ctx , runtime ),
560+ "%import_all%" : "True" if read_possibly_native_flag (ctx , "python_import_all_repositories" ) else "False" ,
561+ "%site_init_runfiles_path%" : runfiles_root_path (ctx , site_init .short_path ),
562+ "%workspace_name%" : ctx .workspace_name ,
563+ },
564+ computed_substitutions = computed_subs ,
565+ )
566+
567+ venv_dir_map = {
568+ VenvSymlinkKind .BIN : venv_details .bin_dir ,
569+ VenvSymlinkKind .LIB : site_packages ,
570+ }
571+ venv_app_files = create_venv_app_files (
572+ ctx ,
573+ deps = collect_deps (ctx , extra_deps ),
574+ venv_dir_map = venv_dir_map ,
575+ )
576+
577+ files_without_interpreter = [pth , site_init ] + venv_app_files .venv_files
578+ if venv_details .pyvenv_cfg :
579+ files_without_interpreter .append (venv_details .pyvenv_cfg )
580+
581+ return struct (
582+ # File or None; the `bin/python3` executable in the venv.
583+ # None if a full venv isn't created.
584+ interpreter = venv_details .interpreter ,
585+ # Files in the venv that need to be created for the interpreter to work
586+ interpreter_runfiles = venv_details .interpreter_runfiles ,
587+ # bool; True if the venv should be recreated at runtime
588+ recreate_venv_at_runtime = venv_details .recreate_venv_at_runtime ,
589+ # Runfiles root relative path or absolute path
590+ interpreter_actual_path = interpreter_actual_path ,
591+ files_without_interpreter = files_without_interpreter ,
592+ # string; venv-relative path to the site-packages directory.
593+ venv_site_packages = venv_details .venv_site_packages ,
594+ # string; runfiles-root relative path to venv root.
595+ venv_root = runfiles_root_path (
596+ ctx ,
597+ paths .join (
598+ py_internal .get_label_repo_runfiles_path (ctx .label ),
599+ venv_root ,
600+ ),
601+ ),
602+ # venv files for user library dependencies (files that are specific
603+ # to the executable bootstrap and python runtime aren't here).
604+ # `root_symlinks` should be used, otherwise, with symlinks files always go
605+ # to `_main` prefix, and binaries from non-root module become broken.
606+ lib_runfiles = ctx .runfiles (
607+ root_symlinks = venv_app_files .runfiles_symlinks ,
608+ ),
609+ )
539610
611+ def _create_venv_unixy (ctx , * , venv_root , runtime , interpreter_actual_path ):
612+ interpreter_runfiles = builders .RunfilesBuilder ()
613+ if runtime .interpreter :
614+ interpreter_actual_path = runfiles_root_path (ctx , runtime .interpreter .short_path )
615+ else :
616+ interpreter_actual_path = runtime .interpreter_path
617+
618+ is_bootstrap_script = BootstrapImplFlag .get_value (ctx ) == BootstrapImplFlag .SCRIPT
619+ create_full_venv = True
540620 # The legacy build_python_zip codepath (enabled by default on windows) isn't
541621 # compatible with full venv.
542622 # TODO: Use non-build_python_zip codepath for Windows
543- if is_windows :
544- create_full_venv = True
545- elif not rp_config .bazel_8_or_later and not is_bootstrap_script :
623+ if not rp_config .bazel_8_or_later and not is_bootstrap_script :
546624 # Full venv for Bazel 7 + system_python is disabled because packaging
547625 # it using build_python_zip=true or rules_pkg breaks.
548626 # * Using build_python_zip=true breaks because the legacy zipapp support
@@ -558,24 +636,18 @@ def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root
558636 # The pyvenv.cfg file must be present to trigger the venv site hooks.
559637 # Because it's paths are expected to be absolute paths, we can't reliably
560638 # put much in it. See https://github.com/python/cpython/issues/83650
561- pyvenv_cfg = ctx .actions .declare_file ("{}/pyvenv.cfg" .format (venv ))
639+ pyvenv_cfg = ctx .actions .declare_file ("{}/pyvenv.cfg" .format (venv_root ))
562640 ctx .actions .write (pyvenv_cfg , "" )
563641 else :
564642 pyvenv_cfg = None
565- runtime = runtime_details .effective_runtime
566643
567644 venvs_use_declare_symlink_enabled = (
568645 VenvsUseDeclareSymlinkFlag .get_value (ctx ) == VenvsUseDeclareSymlinkFlag .YES
569646 )
570- recreate_venv_at_runtime = False
571-
572- if runtime .interpreter :
573- interpreter_actual_path = runfiles_root_path (ctx , runtime .interpreter .short_path )
574- else :
575- interpreter_actual_path = runtime .interpreter_path
576647
577- bin_dir = "{}/bin" . format ( venv )
648+ recreate_venv_at_runtime = False
578649
650+ bin_dir = "{}/bin" .format (venv_root )
579651 if create_full_venv :
580652 # Some wrappers around the interpreter (e.g. pyenv) use the program
581653 # name to decide what to do, so preserve the name.
@@ -604,7 +676,6 @@ def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root
604676 from_ = paths .dirname (runfiles_root_path (ctx , interpreter .short_path )),
605677 to = interpreter_actual_path ,
606678 )
607-
608679 ctx .actions .symlink (output = interpreter , target_path = rel_path )
609680 else :
610681 interpreter = ctx .actions .declare_symlink ("{}/{}" .format (bin_dir , py_exe_basename ))
@@ -627,67 +698,75 @@ def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root
627698 version += "t"
628699
629700 venv_site_packages = "lib/python{}/site-packages" .format (version )
630- site_packages = "{}/{}" .format (venv , venv_site_packages )
631- pth = ctx .actions .declare_file ("{}/bazel.pth" .format (site_packages ))
632- ctx .actions .write (pth , "import _bazel_site_init\n " )
633-
634- site_init = ctx .actions .declare_file ("{}/_bazel_site_init.py" .format (site_packages ))
635- computed_subs = ctx .actions .template_dict ()
636- computed_subs .add_joined ("%imports%" , imports , join_with = ":" , map_each = _map_each_identity )
637- ctx .actions .expand_template (
638- template = runtime .site_init_template ,
639- output = site_init ,
640- substitutions = {
641- "%add_runfiles_root_to_sys_path%" : add_runfiles_root_to_sys_path ,
642- "%coverage_tool%" : _get_coverage_tool_runfiles_path (ctx , runtime ),
643- "%import_all%" : "True" if read_possibly_native_flag (ctx , "python_import_all_repositories" ) else "False" ,
644- "%site_init_runfiles_path%" : runfiles_root_path (ctx , site_init .short_path ),
645- "%workspace_name%" : ctx .workspace_name ,
646- },
647- computed_substitutions = computed_subs ,
701+ return _venv_details (
702+ pyvenv_cfg = pyvenv_cfg ,
703+ venv_site_packages = venv_site_packages ,
704+ bin_dir = bin_dir ,
705+ recreate_venv_at_runtime = recreate_venv_at_runtime ,
706+ interpreter_runfiles = interpreter_runfiles .build (ctx ),
648707 )
649708
650- venv_dir_map = {
651- VenvSymlinkKind .BIN : bin_dir ,
652- VenvSymlinkKind .LIB : site_packages ,
653- }
654- venv_app_files = create_venv_app_files (
655- ctx ,
656- deps = collect_deps (ctx , extra_deps ),
657- venv_dir_map = venv_dir_map ,
658- )
709+ def _create_venv_windows (ctx , * , venv_root , runtime , interpreter_actual_path ):
710+ interpreter_runfiles = builders .RunfilesBuilder ()
659711
660- files_without_interpreter = [pth , site_init ] + venv_app_files .venv_files
661- if pyvenv_cfg :
662- files_without_interpreter .append (pyvenv_cfg )
712+ # Some wrappers around the interpreter (e.g. pyenv) use the program
713+ # name to decide what to do, so preserve the name.
714+ py_exe_basename = paths .basename (interpreter_actual_path )
715+ bin_dir = "{}/Scripts" .format (venv_root )
716+ if runtime .interpreter :
717+ interpreter = ctx .actions .declare_file ("{}/{}" .format (bin_dir , py_exe_basename ))
718+ interpreter_runfiles .add (interpreter )
719+ ctx .actions .symlink (output = interpreter , target_file = runtime .interpreter )
720+ for f in runtime .venv_bin_files :
721+ venv_path = "{}/{}" .format (bin_dir , f .basename )
722+ venv_file = ctx .actions .declare_file (venv_path )
723+ ctx .actions .symlink (output = venv_file , target_file = f )
724+ interpreter_runfiles .add (venv_file )
725+ else :
726+ interpreter = ctx .actions .declare_symlink ("{}/{}" .format (bin_dir , py_exe_basename ))
727+ interpreter_runfiles .add (interpreter )
728+ ctx .actions .symlink (output = interpreter , target_path = runtime .interpreter_path )
729+
730+ # See site.py logic: Windows uses a version/build agnostic site-packages path
731+ venv_site_packages = "Lib/site-packages"
732+
733+ return _venv_details (
734+ interpreter = interpreter ,
735+ pyvenv_cfg = None ,
736+ venv_site_packages = venv_site_packages ,
737+ bin_dir = bin_dir ,
738+ recreate_venv_at_runtime = True ,
739+ interpreter_runfiles = interpreter_runfiles .build (ctx ),
740+ )
663741
742+ def _venv_details (* ,
743+ interpreter ,
744+ pyvenv_cfg ,
745+ venv_site_packages ,
746+ bin_dir ,
747+ recreate_venv_at_runtime ,
748+ interpreter_runfiles ,
749+ ):
750+ """Helper to create a struct of platform-specific venv details."""
664751 return struct (
665- # File or None; the `bin/python3` executable in the venv.
666- # None if a full venv isn't created.
752+ # File; the `bin/python` executable (or equivalent) within the venv.
667753 interpreter = interpreter ,
668- # bool; True if the venv should be recreated at runtime
669- recreate_venv_at_runtime = recreate_venv_at_runtime ,
670- # Runfiles root relative path or absolute path
671- interpreter_actual_path = interpreter_actual_path ,
672- files_without_interpreter = files_without_interpreter ,
673- # string; venv-relative path to the site-packages directory.
754+ # File|None; the pyvenv.cfg file, if any. May be none, in which case,
755+ # it's expected that one will be created at runtime.
756+ pyvenv_cfg = pyvenv_cfg ,
757+ # str; venv-relative path to the site-packages directory
674758 venv_site_packages = venv_site_packages ,
675- # string; runfiles-root relative path to venv root.
676- venv_root = runfiles_root_path (
677- ctx ,
678- paths .join (
679- py_internal .get_label_repo_runfiles_path (ctx .label ),
680- venv ,
681- ),
682- ),
683- # venv files for user library dependencies (files that are specific
684- # to the executable bootstrap and python runtime aren't here).
685- # `root_symlinks` should be used, otherwise, with symlinks files always go
686- # to `_main` prefix, and binaries from non-root module become broken.
687- lib_runfiles = ctx .runfiles (
688- root_symlinks = venv_app_files .runfiles_symlinks ,
689- ),
759+ # str; ctx-relative path to the venv's bin directory.
760+ bin_dir = bin_dir ,
761+ # bool; True if the venv needs to be recreated at runtime (because the
762+ # build-time construction isn't sufficient). False if the build-time
763+ # constructed venv is sufficient.
764+ recreate_venv_at_runtime = recreate_venv_at_runtime ,
765+ # runfiles; runfiles for interpreter-specific files in the venv.
766+ interpreter_runfiles = interpreter_runfiles ,
690767 )
768+ )
769+
691770
692771def _map_each_identity (v ):
693772 return v
@@ -777,6 +856,7 @@ def _create_stage1_bootstrap(
777856 else :
778857 resolve_python_binary_at_runtime = "1"
779858
859+
780860 subs = {
781861 "%interpreter_args%" : "\n " .join (ctx .attr .interpreter_args ),
782862 "%is_zipfile%" : "1" if is_for_zip else "0" ,
0 commit comments