@@ -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" )
@@ -618,89 +618,104 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
618618 },
619619 computed_substitutions = computed_subs ,
620620 )
621- site_packages_symlinks = _create_site_packages_symlinks (ctx , site_packages )
621+
622+ bin_dir = "bin"
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: todo
639645
640646 Returns:
641647 {type}`list[File]` list of the File symlink objects created.
642648 """
643649
644- # maps site-package symlink to the runfiles path it should point to
650+ # maps venv-relative path to the runfiles path it should point to
645651 entries = depset (
646652 # NOTE: Topological ordering is used so that dependencies closer to the
647653 # binary have precedence in creating their symlinks. This allows the
648654 # binary a modicum of control over the result.
649655 order = "topological" ,
650656 transitive = [
651- dep [PyInfo ].site_packages_symlinks
657+ dep [PyInfo ].venv_symlinks
652658 for dep in ctx .attr .deps
653659 if PyInfo in dep
654660 ],
655661 ).to_list ()
662+
656663 link_map = _build_link_map (entries )
664+ venv_files = []
665+ for kind , kind_map in link_map .items ():
666+ base = venv_dir_map [kind ]
667+ for venv_path , link_to in kind_map .items ():
668+ venv_link = ctx .actions .declare_symlink (paths .join (base , venv_path ))
669+ venv_link_rf_path = runfiles_root_path (ctx , venv_link .short_path )
670+ rel_path = relative_path (
671+ # dirname is necessary because a relative symlink is relative to
672+ # the directory the symlink resides within.
673+ from_ = paths .dirname (venv_link_rf_path ),
674+ to = link_to ,
675+ )
676+ ctx .actions .symlink (output = venv_link , target_path = rel_path )
677+ venv_files .append (venv_link )
657678
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
679+ return venv_files
671680
672681def _build_link_map (entries ):
682+ # dict[str kind, dict[str rel_path, str link_to_path]]
673683 link_map = {}
674- for link_to_runfiles_path , site_packages_path in entries :
675- if site_packages_path in link_map :
684+ for entry in entries :
685+ kind = entry .kind
686+ kind_map = link_map .setdefault (kind , {})
687+ if entry .venv_path in kind_map :
676688 # We ignore duplicates by design. The dependency closer to the
677689 # binary gets precedence due to the topological ordering.
678690 continue
679691 else :
680- link_map [ site_packages_path ] = link_to_runfiles_path
692+ kind_map [ entry . venv_path ] = entry . link_to_path
681693
682694 # An empty link_to value means to not create the site package symlink.
683695 # Because of the topological ordering, this allows binaries to remove
684696 # 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 )
697+ for kind , kind_map in link_map .items ():
698+ for dir_path , link_to in kind_map .items ():
699+ if not link_to :
700+ kind_map .pop (dir_path )
688701
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.
702+ # dict[str kind, dict[str rel_path, str link_to_path]]
691703 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 )
703704
705+ # Remove entries that would be a child path of a created symlink.
706+ # Earlier entries have precedence to match how exact matches are handled.
707+ for kind , kind_map in link_map .items ():
708+ keep_kind_map = keep_link_map .setdefault (kind , {})
709+ for _ in range (len (kind_map )):
710+ if not kind_map :
711+ break
712+ dirname , value = kind_map .popitem ()
713+ keep_kind_map [dirname ] = value
714+ prefix = dirname + "/" # Add slash to prevent /X matching /XY
715+ for maybe_suffix in kind_map .keys ():
716+ maybe_suffix += "/" # Add slash to prevent /X matching /XY
717+ if maybe_suffix .startswith (prefix ) or prefix .startswith (maybe_suffix ):
718+ kind_map .pop (maybe_suffix )
704719 return keep_link_map
705720
706721def _map_each_identity (v ):
0 commit comments