48
48
"filter_to_py_srcs" ,
49
49
"get_imports" ,
50
50
"is_bool" ,
51
+ "relative_path" ,
51
52
"runfiles_root_path" ,
52
53
"target_platform_has_any_constraint" ,
53
54
)
@@ -63,6 +64,7 @@ load(":reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo")
63
64
load (":rule_builders.bzl" , "ruleb" )
64
65
load (":toolchain_types.bzl" , "EXEC_TOOLS_TOOLCHAIN_TYPE" , "TARGET_TOOLCHAIN_TYPE" , TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE" )
65
66
load (":transition_labels.bzl" , "TRANSITION_LABELS" )
67
+ load (":venv_runfiles.bzl" , "create_venv_app_files" )
66
68
67
69
_py_builtins = py_internal
68
70
_EXTERNAL_PATH_PREFIX = "external"
@@ -499,37 +501,6 @@ def _create_zip_main(ctx, *, stage2_bootstrap, runtime_details, venv):
499
501
)
500
502
return output
501
503
502
- def relative_path (from_ , to ):
503
- """Compute a relative path from one path to another.
504
-
505
- Args:
506
- from_: {type}`str` the starting directory. Note that it should be
507
- a directory because relative-symlinks are relative to the
508
- directory the symlink resides in.
509
- to: {type}`str` the path that `from_` wants to point to
510
-
511
- Returns:
512
- {type}`str` a relative path
513
- """
514
- from_parts = from_ .split ("/" )
515
- to_parts = to .split ("/" )
516
-
517
- # Strip common leading parts from both paths
518
- n = min (len (from_parts ), len (to_parts ))
519
- for _ in range (n ):
520
- if from_parts [0 ] == to_parts [0 ]:
521
- from_parts .pop (0 )
522
- to_parts .pop (0 )
523
- else :
524
- break
525
-
526
- # Impossible to compute a relative path without knowing what ".." is
527
- if from_parts and from_parts [0 ] == ".." :
528
- fail ("cannot compute relative path from '%s' to '%s'" , from_ , to )
529
-
530
- parts = ([".." ] * len (from_parts )) + to_parts
531
- return paths .join (* parts )
532
-
533
504
# Create a venv the executable can use.
534
505
# For venv details and the venv startup process, see:
535
506
# * https://docs.python.org/3/library/venv.html
@@ -636,9 +607,9 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
636
607
VenvSymlinkKind .BIN : bin_dir ,
637
608
VenvSymlinkKind .LIB : site_packages ,
638
609
}
639
- venv_symlinks = _create_venv_symlinks (ctx , venv_dir_map )
610
+ venv_app_files = create_venv_app_files (ctx , ctx . attr . deps , venv_dir_map )
640
611
641
- files_without_interpreter = [pth , site_init ] + venv_symlinks
612
+ files_without_interpreter = [pth , site_init ] + venv_app_files
642
613
if pyvenv_cfg :
643
614
files_without_interpreter .append (pyvenv_cfg )
644
615
@@ -663,94 +634,6 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
663
634
),
664
635
)
665
636
666
- def _create_venv_symlinks (ctx , venv_dir_map ):
667
- """Creates symlinks within the venv.
668
-
669
- Args:
670
- ctx: current rule ctx
671
- venv_dir_map: mapping of VenvSymlinkKind constants to the
672
- venv path.
673
-
674
- Returns:
675
- {type}`list[File]` list of the File symlink objects created.
676
- """
677
-
678
- # maps venv-relative path to the runfiles path it should point to
679
- entries = depset (
680
- transitive = [
681
- dep [PyInfo ].venv_symlinks
682
- for dep in ctx .attr .deps
683
- if PyInfo in dep
684
- ],
685
- ).to_list ()
686
-
687
- link_map = _build_link_map (entries )
688
- venv_files = []
689
- for kind , kind_map in link_map .items ():
690
- base = venv_dir_map [kind ]
691
- for venv_path , link_to in kind_map .items ():
692
- venv_link = ctx .actions .declare_symlink (paths .join (base , venv_path ))
693
- venv_link_rf_path = runfiles_root_path (ctx , venv_link .short_path )
694
- rel_path = relative_path (
695
- # dirname is necessary because a relative symlink is relative to
696
- # the directory the symlink resides within.
697
- from_ = paths .dirname (venv_link_rf_path ),
698
- to = link_to ,
699
- )
700
- ctx .actions .symlink (output = venv_link , target_path = rel_path )
701
- venv_files .append (venv_link )
702
-
703
- return venv_files
704
-
705
- def _build_link_map (entries ):
706
- # dict[str package, dict[str kind, dict[str rel_path, str link_to_path]]]
707
- pkg_link_map = {}
708
-
709
- # dict[str package, str version]
710
- version_by_pkg = {}
711
-
712
- for entry in entries :
713
- link_map = pkg_link_map .setdefault (entry .package , {})
714
- kind_map = link_map .setdefault (entry .kind , {})
715
-
716
- if version_by_pkg .setdefault (entry .package , entry .version ) != entry .version :
717
- # We ignore duplicates by design.
718
- continue
719
- elif entry .venv_path in kind_map :
720
- # We ignore duplicates by design.
721
- continue
722
- else :
723
- kind_map [entry .venv_path ] = entry .link_to_path
724
-
725
- # An empty link_to value means to not create the site package symlink. Because of the
726
- # ordering, this allows binaries to remove entries by having an earlier dependency produce
727
- # empty link_to values.
728
- for link_map in pkg_link_map .values ():
729
- for kind , kind_map in link_map .items ():
730
- for dir_path , link_to in kind_map .items ():
731
- if not link_to :
732
- kind_map .pop (dir_path )
733
-
734
- # dict[str kind, dict[str rel_path, str link_to_path]]
735
- keep_link_map = {}
736
-
737
- # Remove entries that would be a child path of a created symlink.
738
- # Earlier entries have precedence to match how exact matches are handled.
739
- for link_map in pkg_link_map .values ():
740
- for kind , kind_map in link_map .items ():
741
- keep_kind_map = keep_link_map .setdefault (kind , {})
742
- for _ in range (len (kind_map )):
743
- if not kind_map :
744
- break
745
- dirname , value = kind_map .popitem ()
746
- keep_kind_map [dirname ] = value
747
- prefix = dirname + "/" # Add slash to prevent /X matching /XY
748
- for maybe_suffix in kind_map .keys ():
749
- maybe_suffix += "/" # Add slash to prevent /X matching /XY
750
- if maybe_suffix .startswith (prefix ) or prefix .startswith (maybe_suffix ):
751
- kind_map .pop (maybe_suffix )
752
- return keep_link_map
753
-
754
637
def _map_each_identity (v ):
755
638
return v
756
639
0 commit comments