@@ -54,7 +54,7 @@ load(":flags.bzl", "BootstrapImplFlag", "VenvsUseDeclareSymlinkFlag")
54
54
load (":precompile.bzl" , "maybe_precompile" )
55
55
load (":py_cc_link_params_info.bzl" , "PyCcLinkParamsInfo" )
56
56
load (":py_executable_info.bzl" , "PyExecutableInfo" )
57
- load (":py_info.bzl" , "PyInfo" )
57
+ load (":py_info.bzl" , "PyInfo" , "VenvSymlinkKind" )
58
58
load (":py_internal.bzl" , "py_internal" )
59
59
load (":py_runtime_info.bzl" , "DEFAULT_STUB_SHEBANG" , "PyRuntimeInfo" )
60
60
load (":reexports.bzl" , "BuiltinPyInfo" , "BuiltinPyRuntimeInfo" )
@@ -543,6 +543,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
543
543
VenvsUseDeclareSymlinkFlag .get_value (ctx ) == VenvsUseDeclareSymlinkFlag .YES
544
544
)
545
545
recreate_venv_at_runtime = False
546
+ bin_dir = "{}/bin" .format (venv )
546
547
547
548
if not venvs_use_declare_symlink_enabled or not runtime .supports_build_time_venv :
548
549
recreate_venv_at_runtime = True
@@ -556,7 +557,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
556
557
# When the venv symlinks are disabled, the $venv/bin/python3 file isn't
557
558
# needed or used at runtime. However, the zip code uses the interpreter
558
559
# 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 ))
560
561
ctx .actions .write (interpreter , "actual:{}" .format (interpreter_actual_path ))
561
562
562
563
elif runtime .interpreter :
@@ -568,7 +569,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
568
569
# declare_symlink() is required to ensure that the resulting file
569
570
# in runfiles is always a symlink. An RBE implementation, for example,
570
571
# 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 ))
572
573
573
574
interpreter_actual_path = runfiles_root_path (ctx , runtime .interpreter .short_path )
574
575
rel_path = relative_path (
@@ -581,7 +582,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
581
582
ctx .actions .symlink (output = interpreter , target_path = rel_path )
582
583
else :
583
584
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 ))
585
586
ctx .actions .symlink (output = interpreter , target_path = runtime .interpreter_path )
586
587
interpreter_actual_path = runtime .interpreter_path
587
588
@@ -618,89 +619,104 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
618
619
},
619
620
computed_substitutions = computed_subs ,
620
621
)
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 )
622
628
623
629
return struct (
624
630
interpreter = interpreter ,
625
631
recreate_venv_at_runtime = recreate_venv_at_runtime ,
626
632
# Runfiles root relative path or absolute path
627
633
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 ,
629
635
# string; venv-relative path to the site-packages directory.
630
636
venv_site_packages = venv_site_packages ,
631
637
)
632
638
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 .
635
641
636
642
Args:
637
643
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.
639
646
640
647
Returns:
641
648
{type}`list[File]` list of the File symlink objects created.
642
649
"""
643
650
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
645
652
entries = depset (
646
653
# NOTE: Topological ordering is used so that dependencies closer to the
647
654
# binary have precedence in creating their symlinks. This allows the
648
655
# binary a modicum of control over the result.
649
656
order = "topological" ,
650
657
transitive = [
651
- dep [PyInfo ].site_packages_symlinks
658
+ dep [PyInfo ].venv_symlinks
652
659
for dep in ctx .attr .deps
653
660
if PyInfo in dep
654
661
],
655
662
).to_list ()
663
+
656
664
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 )
657
679
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
671
681
672
682
def _build_link_map (entries ):
683
+ # dict[str kind, dict[str rel_path, str link_to_path]]
673
684
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 :
676
689
# We ignore duplicates by design. The dependency closer to the
677
690
# binary gets precedence due to the topological ordering.
678
691
continue
679
692
else :
680
- link_map [ site_packages_path ] = link_to_runfiles_path
693
+ kind_map [ entry . venv_path ] = entry . link_to_path
681
694
682
695
# An empty link_to value means to not create the site package symlink.
683
696
# Because of the topological ordering, this allows binaries to remove
684
697
# 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 )
688
702
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]]
691
704
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 )
703
705
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 )
704
720
return keep_link_map
705
721
706
722
def _map_each_identity (v ):
0 commit comments