@@ -54,7 +54,7 @@ load(":flags.bzl", "BootstrapImplFlag", "VenvsUseDeclareSymlinkFlag")
5454load (":precompile.bzl" , "maybe_precompile" )
5555load (":py_cc_link_params_info.bzl" , "PyCcLinkParamsInfo" )
5656load (":py_executable_info.bzl" , "PyExecutableInfo" )
57- load (":py_info.bzl" , "PyInfo" )
57+ load (":py_info.bzl" , "PyInfo" , "VenvSymlinkKind" )
5858load (":py_internal.bzl" , "py_internal" )
5959load (":py_runtime_info.bzl" , "DEFAULT_STUB_SHEBANG" , "PyRuntimeInfo" )
6060load (":reexports.bzl" , "BuiltinPyInfo" , "BuiltinPyRuntimeInfo" )
@@ -543,6 +543,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
543543 VenvsUseDeclareSymlinkFlag .get_value (ctx ) == VenvsUseDeclareSymlinkFlag .YES
544544 )
545545 recreate_venv_at_runtime = False
546+ bin_dir = "{}/bin" .format (venv )
546547
547548 if not venvs_use_declare_symlink_enabled or not runtime .supports_build_time_venv :
548549 recreate_venv_at_runtime = True
@@ -556,7 +557,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
556557 # When the venv symlinks are disabled, the $venv/bin/python3 file isn't
557558 # needed or used at runtime. However, the zip code uses the interpreter
558559 # File object to figure out some paths.
559- interpreter = ctx .actions .declare_file ("{}/bin/ {}" .format (venv , py_exe_basename ))
560+ interpreter = ctx .actions .declare_file ("{}/{}" .format (bin_dir , py_exe_basename ))
560561 ctx .actions .write (interpreter , "actual:{}" .format (interpreter_actual_path ))
561562
562563 elif runtime .interpreter :
@@ -568,7 +569,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
568569 # declare_symlink() is required to ensure that the resulting file
569570 # in runfiles is always a symlink. An RBE implementation, for example,
570571 # may choose to write what symlink() points to instead.
571- interpreter = ctx .actions .declare_symlink ("{}/bin/ {}" .format (venv , py_exe_basename ))
572+ interpreter = ctx .actions .declare_symlink ("{}/{}" .format (bin_dir , py_exe_basename ))
572573
573574 interpreter_actual_path = runfiles_root_path (ctx , runtime .interpreter .short_path )
574575 rel_path = relative_path (
@@ -581,7 +582,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
581582 ctx .actions .symlink (output = interpreter , target_path = rel_path )
582583 else :
583584 py_exe_basename = paths .basename (runtime .interpreter_path )
584- interpreter = ctx .actions .declare_symlink ("{}/bin/ {}" .format (venv , py_exe_basename ))
585+ interpreter = ctx .actions .declare_symlink ("{}/{}" .format (bin_dir , py_exe_basename ))
585586 ctx .actions .symlink (output = interpreter , target_path = runtime .interpreter_path )
586587 interpreter_actual_path = runtime .interpreter_path
587588
@@ -618,89 +619,104 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
618619 },
619620 computed_substitutions = computed_subs ,
620621 )
621- site_packages_symlinks = _create_site_packages_symlinks (ctx , site_packages )
622+
623+ venv_dir_map = {
624+ VenvSymlinkKind .BIN : bin_dir ,
625+ VenvSymlinkKind .LIB : site_packages ,
626+ }
627+ venv_symlinks = _create_venv_symlinks (ctx , venv_dir_map )
622628
623629 return struct (
624630 interpreter = interpreter ,
625631 recreate_venv_at_runtime = recreate_venv_at_runtime ,
626632 # Runfiles root relative path or absolute path
627633 interpreter_actual_path = interpreter_actual_path ,
628- files_without_interpreter = [pyvenv_cfg , pth , site_init ] + site_packages_symlinks ,
634+ files_without_interpreter = [pyvenv_cfg , pth , site_init ] + venv_symlinks ,
629635 # string; venv-relative path to the site-packages directory.
630636 venv_site_packages = venv_site_packages ,
631637 )
632638
633- def _create_site_packages_symlinks (ctx , site_packages ):
634- """Creates symlinks within site-packages .
639+ def _create_venv_symlinks (ctx , venv_dir_map ):
640+ """Creates symlinks within the venv .
635641
636642 Args:
637643 ctx: current rule ctx
638- site_packages: runfiles-root-relative path to the site-packages directory
644+ venv_dir_map: mapping of VenvSymlinkKind constants to the
645+ venv path.
639646
640647 Returns:
641648 {type}`list[File]` list of the File symlink objects created.
642649 """
643650
644- # maps site-package symlink to the runfiles path it should point to
651+ # maps venv-relative path to the runfiles path it should point to
645652 entries = depset (
646653 # NOTE: Topological ordering is used so that dependencies closer to the
647654 # binary have precedence in creating their symlinks. This allows the
648655 # binary a modicum of control over the result.
649656 order = "topological" ,
650657 transitive = [
651- dep [PyInfo ].site_packages_symlinks
658+ dep [PyInfo ].venv_symlinks
652659 for dep in ctx .attr .deps
653660 if PyInfo in dep
654661 ],
655662 ).to_list ()
663+
656664 link_map = _build_link_map (entries )
665+ venv_files = []
666+ for kind , kind_map in link_map .items ():
667+ base = venv_dir_map [kind ]
668+ for venv_path , link_to in kind_map .items ():
669+ venv_link = ctx .actions .declare_symlink (paths .join (base , venv_path ))
670+ venv_link_rf_path = runfiles_root_path (ctx , venv_link .short_path )
671+ rel_path = relative_path (
672+ # dirname is necessary because a relative symlink is relative to
673+ # the directory the symlink resides within.
674+ from_ = paths .dirname (venv_link_rf_path ),
675+ to = link_to ,
676+ )
677+ ctx .actions .symlink (output = venv_link , target_path = rel_path )
678+ venv_files .append (venv_link )
657679
658- sp_files = []
659- for sp_dir_path , link_to in link_map .items ():
660- sp_link = ctx .actions .declare_symlink (paths .join (site_packages , sp_dir_path ))
661- sp_link_rf_path = runfiles_root_path (ctx , sp_link .short_path )
662- rel_path = relative_path (
663- # dirname is necessary because a relative symlink is relative to
664- # the directory the symlink resides within.
665- from_ = paths .dirname (sp_link_rf_path ),
666- to = link_to ,
667- )
668- ctx .actions .symlink (output = sp_link , target_path = rel_path )
669- sp_files .append (sp_link )
670- return sp_files
680+ return venv_files
671681
672682def _build_link_map (entries ):
683+ # dict[str kind, dict[str rel_path, str link_to_path]]
673684 link_map = {}
674- for link_to_runfiles_path , site_packages_path in entries :
675- if site_packages_path in link_map :
685+ for entry in entries :
686+ kind = entry .kind
687+ kind_map = link_map .setdefault (kind , {})
688+ if entry .venv_path in kind_map :
676689 # We ignore duplicates by design. The dependency closer to the
677690 # binary gets precedence due to the topological ordering.
678691 continue
679692 else :
680- link_map [ site_packages_path ] = link_to_runfiles_path
693+ kind_map [ entry . venv_path ] = entry . link_to_path
681694
682695 # An empty link_to value means to not create the site package symlink.
683696 # Because of the topological ordering, this allows binaries to remove
684697 # entries by having an earlier dependency produce empty link_to values.
685- for sp_dir_path , link_to in link_map .items ():
686- if not link_to :
687- link_map .pop (sp_dir_path )
698+ for kind , kind_map in link_map .items ():
699+ for dir_path , link_to in kind_map .items ():
700+ if not link_to :
701+ kind_map .pop (dir_path )
688702
689- # Remove entries that would be a child path of a created symlink.
690- # Earlier entries have precedence to match how exact matches are handled.
703+ # dict[str kind, dict[str rel_path, str link_to_path]]
691704 keep_link_map = {}
692- for _ in range (len (link_map )):
693- if not link_map :
694- break
695- dirname , value = link_map .popitem ()
696- keep_link_map [dirname ] = value
697-
698- prefix = dirname + "/" # Add slash to prevent /X matching /XY
699- for maybe_suffix in link_map .keys ():
700- maybe_suffix += "/" # Add slash to prevent /X matching /XY
701- if maybe_suffix .startswith (prefix ) or prefix .startswith (maybe_suffix ):
702- link_map .pop (maybe_suffix )
703705
706+ # Remove entries that would be a child path of a created symlink.
707+ # Earlier entries have precedence to match how exact matches are handled.
708+ for kind , kind_map in link_map .items ():
709+ keep_kind_map = keep_link_map .setdefault (kind , {})
710+ for _ in range (len (kind_map )):
711+ if not kind_map :
712+ break
713+ dirname , value = kind_map .popitem ()
714+ keep_kind_map [dirname ] = value
715+ prefix = dirname + "/" # Add slash to prevent /X matching /XY
716+ for maybe_suffix in kind_map .keys ():
717+ maybe_suffix += "/" # Add slash to prevent /X matching /XY
718+ if maybe_suffix .startswith (prefix ) or prefix .startswith (maybe_suffix ):
719+ kind_map .pop (maybe_suffix )
704720 return keep_link_map
705721
706722def _map_each_identity (v ):
0 commit comments