fix(local_runtime): Search for libs in sys._base_executable when available. (#3178)
Search directory for libraries should look in the same directory as
sys._base_executable. Since sys._base_executable may be unset, fallback
to sys.executable
Found this when trying to build using a venv for
[tensorstore](https://github.com/google/tensorstore) on Windows:
* Github CI uses nuget to download Python.
* Build sets up a Python venv.
The venv does not include all the lib directories required to link an
extension.
Fixes https://github.com/bazel-contrib/rules_python/issues/3172
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/python/private/get_local_runtime_info.py b/python/private/get_local_runtime_info.py
index ff3b0ae..d176b1a 100644
--- a/python/private/get_local_runtime_info.py
+++ b/python/private/get_local_runtime_info.py
@@ -23,7 +23,7 @@
_IS_DARWIN = sys.platform == "darwin"
-def _search_directories(get_config):
+def _search_directories(get_config, base_executable):
"""Returns a list of library directories to search for shared libraries."""
# There's several types of libraries with different names and a plethora
# of settings, and many different config variables to check:
@@ -53,19 +53,23 @@
if config_value and not config_value.endswith(multiarch):
lib_dirs.append(os.path.join(config_value, multiarch))
- if _IS_WINDOWS:
- # On Windows DLLs go in the same directory as the executable, while .lib
- # files live in the lib/ or libs/ subdirectory.
- lib_dirs.append(get_config("BINDIR"))
- lib_dirs.append(os.path.join(os.path.dirname(sys.executable)))
- lib_dirs.append(os.path.join(os.path.dirname(sys.executable), "lib"))
- lib_dirs.append(os.path.join(os.path.dirname(sys.executable), "libs"))
- elif not _IS_DARWIN:
- # On most systems the executable is in a bin/ directory and the libraries
- # are in a sibling lib/ directory.
- lib_dirs.append(
- os.path.join(os.path.dirname(os.path.dirname(sys.executable)), "lib")
- )
+ if not _IS_DARWIN:
+ for exec_dir in (
+ os.path.dirname(base_executable) if base_executable else None,
+ get_config("BINDIR"),
+ ):
+ if not exec_dir:
+ continue
+ if _IS_WINDOWS:
+ # On Windows DLLs go in the same directory as the executable, while .lib
+ # files live in the lib/ or libs/ subdirectory.
+ lib_dirs.append(exec_dir)
+ lib_dirs.append(os.path.join(exec_dir, "lib"))
+ lib_dirs.append(os.path.join(exec_dir, "libs"))
+ else:
+ # On most systems the executable is in a bin/ directory and the libraries
+ # are in a sibling lib/ directory.
+ lib_dirs.append(os.path.join(os.path.dirname(exec_dir), "lib"))
# Dedup and remove empty values, keeping the order.
lib_dirs = [v for v in lib_dirs if v]
@@ -126,7 +130,7 @@
return {k: None for k in lib_names}.keys()
-def _get_python_library_info():
+def _get_python_library_info(base_executable):
"""Returns a dictionary with the static and dynamic python libraries."""
config_vars = sysconfig.get_config_vars()
@@ -140,7 +144,7 @@
f"{sys.version_info.major}.{sys.version_info.minor}"
)
- search_directories = _search_directories(config_vars.get)
+ search_directories = _search_directories(config_vars.get, base_executable)
search_libnames = _search_library_names(config_vars.get)
def _add_if_exists(target, path):
@@ -187,13 +191,28 @@
}
+def _get_base_executable():
+ """Returns the base executable path."""
+ try:
+ if sys._base_executable: # pylint: disable=protected-access
+ return sys._base_executable # pylint: disable=protected-access
+ except AttributeError:
+ # Bug reports indicate sys._base_executable doesn't exist in some cases,
+ # but it's not clear why.
+ # See https://github.com/bazel-contrib/rules_python/issues/3172
+ pass
+ # The normal sys.executable is the next-best guess if sys._base_executable
+ # is missing.
+ return sys.executable
+
+
data = {
"major": sys.version_info.major,
"minor": sys.version_info.minor,
"micro": sys.version_info.micro,
"include": sysconfig.get_path("include"),
"implementation_name": sys.implementation.name,
- "base_executable": sys._base_executable,
+ "base_executable": _get_base_executable(),
}
-data.update(_get_python_library_info())
+data.update(_get_python_library_info(_get_base_executable()))
print(json.dumps(data))