@@ -81,7 +81,7 @@ def build_link_map(ctx, entries):
8181 Returns:
8282 {type}`dict[str, dict[str, str|File]]` Mappings of venv paths to their
8383 backing files. The first key is a `VenvSymlinkKind` value.
84- The inner dict keys are venv paths relative to the kind's diretory . The
84+ The inner dict keys are venv paths relative to the kind's directory . The
8585 inner dict values are strings or Files to link to.
8686 """
8787
@@ -116,7 +116,10 @@ def build_link_map(ctx, entries):
116116 # If there's just one group, we can symlink to the directory
117117 if len (group ) == 1 :
118118 entry = group [0 ]
119- keep_kind_link_map [entry .venv_path ] = entry .link_to_path
119+ if entry .link_to_file :
120+ keep_kind_link_map [entry .venv_path ] = entry .link_to_file
121+ else :
122+ keep_kind_link_map [entry .venv_path ] = entry .link_to_path
120123 else :
121124 # Merge a group of overlapping prefixes
122125 _merge_venv_path_group (ctx , group , keep_kind_link_map )
@@ -170,7 +173,9 @@ def _merge_venv_path_group(ctx, group, keep_map):
170173 # TODO: Compute the minimum number of entries to create. This can't avoid
171174 # flattening the files depset, but can lower the number of materialized
172175 # files significantly. Usually overlaps are limited to a small number
173- # of directories.
176+ # of directories. Note that, when doing so, shared libraries need to
177+ # be symlinked directory, not the directory containing them, due to
178+ # symlink resolution semantics on Linux.
174179 for entry in group :
175180 prefix = entry .venv_path
176181 for file in entry .files .to_list ():
@@ -247,13 +252,27 @@ def get_venv_symlinks(ctx, files, package, version_str, site_packages_root):
247252 continue
248253 path = path .removeprefix (site_packages_root )
249254 dir_name , _ , filename = path .rpartition ("/" )
255+ runfiles_dir_name , _ , _ = runfiles_root_path (ctx , src .short_path ).partition ("/" )
256+
257+ if _is_linker_loaded_library (filename ):
258+ entry = VenvSymlinkEntry (
259+ kind = VenvSymlinkKind .LIB ,
260+ # todo: omit setting link_to_path
261+ link_to_path = paths .join (runfiles_dir_name , site_packages_root , filename ),
262+ link_to_file = src ,
263+ package = package ,
264+ version = version_str ,
265+ venv_path = path ,
266+ files = depset ([src ]),
267+ )
268+ venv_symlinks .append (entry )
269+ continue
250270
251271 if dir_name in dir_symlinks :
252272 # we already have this dir, this allows us to short-circuit since most of the
253273 # ctx.files.data might share the same directories as ctx.files.srcs
254274 continue
255275
256- runfiles_dir_name , _ , _ = runfiles_root_path (ctx , src .short_path ).partition ("/" )
257276 if dir_name :
258277 # This can be either:
259278 # * a directory with libs (e.g. numpy.libs, created by auditwheel)
@@ -276,6 +295,8 @@ def get_venv_symlinks(ctx, files, package, version_str, site_packages_root):
276295 files = depset ([src ]),
277296 )
278297 venv_symlinks .append (entry )
298+ else :
299+ fail ("hit" , src )
279300
280301 # Sort so that we encounter `foo` before `foo/bar`. This ensures we
281302 # see the top-most explicit package first.
@@ -310,6 +331,21 @@ def get_venv_symlinks(ctx, files, package, version_str, site_packages_root):
310331
311332 return venv_symlinks
312333
334+ def _is_linker_loaded_library (filename ):
335+ """Tells if a filename is one that `dlopen()` or the runtime linker handle.
336+
337+ Notably, it should return False for Python C extension modules.
338+ """
339+ if not filename .startswith ("lib" ):
340+ return False
341+ if filename .endswith ((".so" , ".dylib" )):
342+ return True
343+
344+ # Versioned library, e.g. libfoo.so.1.2.3
345+ if ".so." in filename :
346+ return True
347+ return False
348+
313349def _repo_relative_short_path (short_path ):
314350 # Convert `../+pypi+foo/some/file.py` to `some/file.py`
315351 if short_path .startswith ("../" ):
0 commit comments