Skip to content

Commit

Permalink
hooks: add run-time hook for findlibs
Browse files Browse the repository at this point in the history
Add run-time hook for `findlibs` that overrides `findlibs.find()`
function with custom implementation that gives precedence to
searching `sys._MEIPASS` and then immediately falling back to
`ctypes.util.find_library`.

The original implementation searches a set of hard-coded paths
before falling back to `ctypes.util.find_library`; those path
include Homebrew directory on macOS, so if that happens to be
installed on the run-time system, the original `findlibs.find()`
implementation ends up returning system-wide copy of the library
(if it is available) instead of the bundled one. Which, especially
on macOS, can lead to problems due to (lack of proper) signature
on the system-wide copy of the library when application itself
is signed with developer identity.
that searches a set of hard-coded paths before
  • Loading branch information
rokm committed Sep 12, 2024
1 parent 75509c4 commit eb27b5b
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 0 deletions.
1 change: 1 addition & 0 deletions _pyinstaller_hooks_contrib/rthooks.dat
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
'cryptography': ['pyi_rth_cryptography_openssl.py'],
'enchant': ['pyi_rth_enchant.py'],
'findlibs': ['pyi_rth_findlibs.py'],
'ffpyplayer': ['pyi_rth_ffpyplayer.py'],
'osgeo': ['pyi_rth_osgeo.py'],
'traitlets': ['pyi_rth_traitlets.py'],
Expand Down
52 changes: 52 additions & 0 deletions _pyinstaller_hooks_contrib/rthooks/pyi_rth_findlibs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#-----------------------------------------------------------------------------
# Copyright (c) 2024, PyInstaller Development Team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: Apache-2.0
#-----------------------------------------------------------------------------

# Override the findlibs.find() function to give precedence to sys._MEIPASS, followed by `ctypes.util.find_library`,
# and only then the hard-coded paths from the original implementation. The main aim here is to avoid loading libraries
# from Homebrew environment on macOS when it happens to be present at run-time and we have a bundled copy collected from
# the build system. This happens because we (try not to) modify `DYLD_LIBRARY_PATH`, and the original `findlibs.find()`
# implementation gives precedence to environment variables and several fixed/hard-coded locations, and uses
# `ctypes.util.find_library` as the final fallback...
def _pyi_rthook():
import sys
import os
import ctypes.util

import findlibs

_orig_find = getattr(findlibs, 'find', None)

def _pyi_find(lib_name, pkg_name=None):
pkg_name = pkg_name or lib_name
extension = findlibs.EXTENSIONS.get(sys.platform, ".so")

# First check sys._MEIPASS
fullname = os.path.join(sys._MEIPASS, "lib{}{}".format(lib_name, extension))
if os.path.isfile(fullname):
return fullname

# Fall back to `ctypes.util.find_library` (to give it precedence over hard-coded paths from original
# implementation).
lib = ctypes.util.find_library(lib_name)
if lib is not None:
return lib

# Finally, fall back to original implementation
if _orig_find is not None:
return _orig_find(lib_name, pkg_name)

return None

findlibs.find = _pyi_find


_pyi_rthook()
del _pyi_rthook
7 changes: 7 additions & 0 deletions news/799.new.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Add run-time hook for ``findlibs`` that overrides the ``findlibs.find``
function with custom implementation in order to ensure that the top-level
application directory is searched first. This prevents a system-wide
copy of the library being found and loaded instead of the bundled copy
when the system-wide copy happens to be available in one of fixed
locations that is scanned by the original implementation of ``findlibs.find``
(for example, Homebrew directory on macOS).
3 changes: 3 additions & 0 deletions tests/test_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2068,6 +2068,9 @@ def test_eccodes_gribapi(pyi_builder):
# - sys._MEIPASS/eccodes
# - sys._MEIPASS/eccodes.libs
# - sys._MEIPASS/eccodes/.dylibs
# as well as sys._MEIPASS itself (in case system-wide copy was collected into top-level application
# directory but is reported with full path instead of just basename due to our override of `findlibs.find()`
# via run-time hook).
if pathlib.PurePath(sys._MEIPASS) not in pathlib.PurePath(library_path).parents:
raise RuntimeError(
f"Shared library path {library_path} is not rooted in top-level application directory!"
Expand Down

0 comments on commit eb27b5b

Please sign in to comment.