pax_global_header00006660000000000000000000000064145743554770014537gustar00rootroot0000000000000052 comment=bc503ea5e51a519486837e7e269f568c3b65b7c4 find_libpython-0.4.0/000077500000000000000000000000001457435547700145505ustar00rootroot00000000000000find_libpython-0.4.0/.github/000077500000000000000000000000001457435547700161105ustar00rootroot00000000000000find_libpython-0.4.0/.github/workflows/000077500000000000000000000000001457435547700201455ustar00rootroot00000000000000find_libpython-0.4.0/.github/workflows/lint.yml000066400000000000000000000006271457435547700216430ustar00rootroot00000000000000name: Lint on: pull_request: branches: - master jobs: lint-pre-commit: runs-on: ubuntu-latest name: Run pre-commit steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Run pre-commit run: | pip install pre-commit pre-commit run -a --show-diff-on-failure find_libpython-0.4.0/.github/workflows/tests.yml000066400000000000000000000215131457435547700220340ustar00rootroot00000000000000name: Tests on: schedule: - cron: "10 16 * * 3" pull_request: branches: [ master ] workflow_dispatch: jobs: ubuntu-system-python: name: ${{ matrix.os }}-system-python runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-20.04, ubuntu-22.04] steps: - uses: actions/checkout@v3 - name: Install System Python run: | sudo apt install python3-dev python3-pip - name: Install Testing Requirements run: python3 -m pip install nox - name: Run Tests run: python3 -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true setup-python: name: ${{ matrix.os }}-setup-python runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [windows-latest, macos-latest, ubuntu-latest] steps: - name: Checkout project uses: actions/checkout@v3 - name: Install Python uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install Testing Requirements run: python -m pip install nox - name: Run tests run: python -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true anaconda: name: ${{ matrix.os }}-anaconda runs-on: ${{ matrix.os }} defaults: run: shell: bash -l {0} # setup-miniconda requires using login bash shells to activate env strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v3 - uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true auto-activate-base: true activate-environment: '' - name: Install Testing Requirements run: python -m pip install nox - name: Run Tests run: python -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true rhel7: name: rhel7-system-python runs-on: ubuntu-latest container: "centos:7" steps: - name: Install System Python and Git run: yum install -y python3-devel python3-pip python3 git - uses: actions/checkout@v3 - name: Install Testing Requirements run: python3 -m pip install nox - name: Run Tests run: python3 -m nox -e tests # we don't upload coverage here because codecov's uploader doesn't support OS's this old rhel8-system-python: name: rhel8-system-python runs-on: ubuntu-latest container: "almalinux:8" steps: - name: Install System Python and Git run: yum install -y python3-devel python3-pip python3 git - uses: actions/checkout@v3 - name: Install Testing Requirements run: python3 -m pip install nox - name: Run Tests run: python3 -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true rhel8-appstream-py38: name: rhel8-appstream-py38 runs-on: ubuntu-latest container: "almalinux:8" steps: - name: Install Python 3.8 and Git from AppStream run: yum install -y python38-devel python38-pip python38-pip-wheel python38 git - uses: actions/checkout@v3 - name: Install Testing Requirements run: python3.8 -m pip install nox - name: Run Tests run: python3.8 -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true rhel8-appstream-py39: name: rhel8-appstream-py39 runs-on: ubuntu-latest container: "almalinux:8" steps: - name: Install Python 3.9 and Git from AppStream run: yum install -y python39-devel python39-pip python39-pip-wheel python39 git - uses: actions/checkout@v3 - name: Install Testing Requirements run: python3.9 -m pip install nox - name: Run Tests run: python3.9 -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true rhel9-system-python: name: rhel9-system-python runs-on: ubuntu-latest container: "almalinux:9" steps: - name: Install System Python and Git run: yum install -y python3-devel python3-pip python3 git - uses: actions/checkout@v3 - name: Install Testing Requirements run: python3 -m pip install nox - name: Run Tests run: python3 -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true msys: name: ${{ matrix.msystem }}-system-python runs-on: windows-latest defaults: run: shell: msys2 {0} strategy: fail-fast: false matrix: msystem: - MSYS - MINGW64 steps: - name: Install msys2 (MINGW64) if: matrix.msystem == 'MINGW64' uses: msys2/setup-msys2@v2 with: msystem: MINGW64 install: >- mingw-w64-x86_64-toolchain mingw-w64-x86_64-python mingw-w64-x86_64-python-pip mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-python-wheel update: true - name: Install msys2 (MSYS) if: matrix.msystem == 'MSYS' uses: msys2/setup-msys2@v2 with: msystem: MSYS install: >- msys2-devel git python python-devel python-pip python-setuptools update: true - name: Install wheel package (MSYS) if: matrix.msystem == 'MSYS' run: pip install --no-build-isolation wheel - uses: actions/checkout@v3 - name: Install package run: pip install --no-build-isolation . - name: Print libpython run: | echo 'NAMES' find_libpython -v --candidate-names echo 'PATHS' find_libpython -v --candidate-paths echo 'LOCATION' find_libpython -v - name: Install Testing Requirements run: pip install --no-build-isolation pytest pytest-cov - name: Run Tests run: | pytest --cov --cov-branch tests/ pytest --cov --cov-branch --cov-append --doctest-modules $(python -c 'import find_libpython; print(find_libpython.__file__)') coverage xml -o coverage.xml - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true alpine: name: alpine-system-python runs-on: ubuntu-latest container: "alpine:latest" steps: - name: Update Packages run: | apk update apk upgrade - name: Install System Python and Git run: | apk add python3 python3-dev py3-pip py3-nox git - name: Download Source uses: actions/checkout@v3 - name: Run Tests run: | nox -e tests # - name: Upload to codecov # uses: codecov/codecov-action@v4 # with: # token: ${{ secrets.CODECOV_TOKEN }} # verbose: true homebrew: name: homebrew-system-python runs-on: macos-latest steps: - name: Install System Python and Git run: | brew install python git - name: Download Source uses: actions/checkout@v3 - name: Install Testing Requirements run: | python3 -m pip install nox - name: Run Tests run: | python3 -m nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true archlinux: name: archlinux-system-python runs-on: ubuntu-latest container: image: archlinux options: --privileged steps: - name: Install System Python and Git run: | pacman --noconfirm -Sy python python-pip git - name: Download Source uses: actions/checkout@v3 - name: Create and activate environment run: | python -m venv .venv . .venv/bin/activate echo PATH=$PATH >> $GITHUB_ENV - name: Install Testing Requirements run: | pip install nox - name: Run Tests run: | nox -e tests - name: Upload to codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true find_libpython-0.4.0/.gitignore000066400000000000000000000000271457435547700165370ustar00rootroot00000000000000__pycache__/ .coverage find_libpython-0.4.0/.pre-commit-config.yaml000066400000000000000000000006561457435547700210400ustar00rootroot00000000000000 repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.3.2" hooks: # Run the linter. - id: "ruff" args: - "--fix" - "--exit-non-zero-on-fix" # Run the formatter. - id: ruff-format - repo: "https://github.com/pre-commit/pre-commit-hooks" rev: "v4.5.0" hooks: - id: "trailing-whitespace" - id: "mixed-line-ending" args: - "--fix=lf" - id: "end-of-file-fixer" find_libpython-0.4.0/LICENSE000066400000000000000000000020721457435547700155560ustar00rootroot00000000000000 Copyright 2018, Takafumi Arakaki Copyright Kaleb Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. find_libpython-0.4.0/README.md000066400000000000000000000043341457435547700160330ustar00rootroot00000000000000# find_libpython A pypi project version of [this](https://gist.github.com/tkf/d980eee120611604c0b9b5fef5b8dae6) gist, which also appears within the [PyCall](https://github.com/JuliaPy/PyCall.jl/blob/master/deps/find_libpython.py) library. The library is designed to find the path to the libpython dynamic library for the current Python environment. It should work with many types of installations, whether it be conda-managed, system-managed, or otherwise. And it should function on Windows, Mac OS/OS X, and any Linux distribution. This code is useful in several contexts, including projects that embed a Python interpreter into another process, or Python library build systems. ## Usage `find_libpython` is both a script and a Python package. Usage as a script is useful in contexts like obtaining the path to libpython for linking in makefile-based build systems. It could also be used to determine the path to libpython for embedding a Python interpreter in a process written in another language. In that case the recommended usage is to simply call the script in a subprocess with no arguments and parse the output. ``` > find_libpython /home/kaleb/miniconda3/envs/test/lib/libpython3.8.so.1.0 ``` The full help message: ``` > find_libpython --help usage: find_libpython [-h] [-v] [--list-all | --candidate-names | --candidate-paths | --platform-info | --version] Locate libpython associated with this Python executable. options: -h, --help show this help message and exit -v, --verbose Print debugging information. --list-all Print list of all paths found. --candidate-names Print list of candidate names of libpython. --candidate-paths Print list of candidate paths of libpython. --platform-info Print information about the platform and exit. --version show program's version number and exit ``` Usage as a library might occur when you need to obtain the path to the library in a Python-based build system like distutils. It is recommended to use the `find_libpython` method which will return the path to libpython as a string, or `None` if it cannot be found. ```python >>> from find_libpython import find_libpython >>> find_libpython() '/home/kaleb/miniconda3/envs/test/lib/libpython3.8.so.1.0' ``` find_libpython-0.4.0/noxfile.py000066400000000000000000000026541457435547700165750ustar00rootroot00000000000000import os.path import nox test_reqs = ["pytest", "pytest-cov", "coverage"] pytest_cov_args = ["--cov=find_libpython", "--cov-branch"] coverage_file = "coverage.xml" @nox.session def tests(session): # install current module and runtime dependencies session.install(".") # install testing dependencies session.install(*test_reqs) # print info session.run( "python", "-m", "coverage", "run", "-m", "find_libpython", "-v", "--platform-info", ) session.run( "python", "-m", "coverage", "run", "-m", "find_libpython", "-v", "--candidate-names", ) session.run( "python", "-m", "coverage", "run", "-m", "find_libpython", "-v", "--candidate-paths", ) session.run("python", "-m", "coverage", "run", "-m", "find_libpython", "-v") # run pytest install_loc = session.run( "python", "-c", "import find_libpython; print(find_libpython.__file__)", silent=True, ) install_loc = os.path.dirname(install_loc.strip()) session.run("pytest", *pytest_cov_args, "tests/") session.run( "pytest", *pytest_cov_args, "--cov-append", "--doctest-modules", install_loc ) # create coverage report for upload session.run("coverage", "xml", "-o", coverage_file) find_libpython-0.4.0/pyproject.toml000066400000000000000000000005371457435547700174710ustar00rootroot00000000000000[build-system] requires = ["setuptools>=43", "wheel"] build-backend = "setuptools.build_meta" [tool.ruff] target-version = "py37" [tool.ruff.lint] select = [ "E", # pycodestyle errors "F", # pyflakes "I", # isort "UP", # pyupgrade "PL", # pylint ] ignore = [ "E501", # Line too long "PLR0912", # Too many branches ] find_libpython-0.4.0/setup.cfg000066400000000000000000000013301457435547700163660ustar00rootroot00000000000000[metadata] name = find_libpython version = attr: find_libpython.__version__ url = https://github.com/ktbarrett/find_libpython author = Takafumi Arakaki maintainer = Kaleb Barrett maintainer_email = dev.ktbarrett@gmail.com license = MIT description = Finds the libpython associated with your environment, wherever it may be hiding long_description = file: README.md long_description_content_type = text/markdown keywords = libpython classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Topic :: Software Development :: Libraries [options] packages = find_libpython package_dir = = src [options.entry_points] console_scripts = find_libpython = find_libpython:main find_libpython-0.4.0/setup.py000066400000000000000000000000461457435547700162620ustar00rootroot00000000000000from setuptools import setup setup() find_libpython-0.4.0/src/000077500000000000000000000000001457435547700153375ustar00rootroot00000000000000find_libpython-0.4.0/src/find_libpython/000077500000000000000000000000001457435547700203475ustar00rootroot00000000000000find_libpython-0.4.0/src/find_libpython/__init__.py000066400000000000000000000334231457435547700224650ustar00rootroot00000000000000""" Locate libpython associated with this Python executable. """ # License # # Copyright 2018, Takafumi Arakaki # Copyright Kaleb Barrett # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import ctypes import os import sys from ctypes.util import find_library as _find_library from logging import getLogger as _getLogger from sysconfig import get_config_var as _get_config_var from find_libpython._version import __version__ # noqa: F401 _logger = _getLogger("find_libpython") _is_apple = sys.platform == "darwin" _is_cygwin = sys.platform in ("msys", "cygwin") _is_mingw = sys.platform == "mingw" _is_windows = os.name == "nt" and not _is_mingw and not _is_cygwin _is_posix = os.name == "posix" _SHLIB_SUFFIX = _get_config_var("_SHLIB_SUFFIX") if _SHLIB_SUFFIX is None: if _is_windows: _SHLIB_SUFFIX = ".dll" else: _SHLIB_SUFFIX = ".so" if _is_apple: # _get_config_var("_SHLIB_SUFFIX") can be ".so" in macOS. # Let's not use the value from sysconfig. _SHLIB_SUFFIX = ".dylib" def _linked_libpython_unix(libpython): if not hasattr(libpython, "Py_GetVersion"): return None class Dl_info(ctypes.Structure): _fields_ = [ ("dli_fname", ctypes.c_char_p), ("dli_fbase", ctypes.c_void_p), ("dli_sname", ctypes.c_char_p), ("dli_saddr", ctypes.c_void_p), ] libdl = ctypes.CDLL(_find_library("dl")) libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)] libdl.dladdr.restype = ctypes.c_int dlinfo = Dl_info() retcode = libdl.dladdr( ctypes.cast(libpython.Py_GetVersion, ctypes.c_void_p), ctypes.pointer(dlinfo), ) if retcode == 0: # means error return None return os.path.realpath(dlinfo.dli_fname.decode()) def _library_name(name, suffix=_SHLIB_SUFFIX, _is_windows=_is_windows): """ Convert a file basename `name` to a library name (no "lib" and ".so" etc.) >>> _library_name("libpython3.7m.so") # doctest: +SKIP 'python3.7m' >>> _library_name("libpython3.7m.so", suffix=".so", _is_windows=False) 'python3.7m' >>> _library_name("libpython3.7m.dylib", suffix=".dylib", _is_windows=False) 'python3.7m' >>> _library_name("python37.dll", suffix=".dll", _is_windows=True) 'python37' """ if not _is_windows and name.startswith("lib"): name = name[len("lib") :] if suffix and name.endswith(suffix): name = name[: -len(suffix)] return name def _append_truthy(list, item): if item: list.append(item) def _uniquifying(items): """ Yield items while excluding the duplicates and preserving the order. >>> list(_uniquifying([1, 2, 1, 2, 3])) [1, 2, 3] """ seen = set() for x in items: if x not in seen: yield x seen.add(x) def _uniquified(func): """Wrap iterator returned from `func` by `_uniquifying`.""" from functools import wraps @wraps(func) def wrapper(*args, **kwds): return _uniquifying(func(*args, **kwds)) return wrapper def _get_proc_library(): pid = os.getpid() path = f"/proc/{pid}/maps" lines = open(path).readlines() for line in lines: path = line.split(" ", 5)[5].strip() if "libpython" in os.path.basename(path): if not os.path.isfile(path): continue yield path @_uniquified def candidate_names(suffix=_SHLIB_SUFFIX): """ Iterate over candidate file names of libpython. Yields ------ name : str Candidate name libpython. """ # Quoting configure.ac in the cpython code base: # "INSTSONAME is the name of the shared library that will be use to install # on the system - some systems like version suffix, others don't."" # # A typical INSTSONAME is 'libpython3.8.so.1.0' on Linux, or # 'Python.framework/Versions/3.9/Python' on MacOS. Due to the possible # version suffix we have to find the suffix within the filename. INSTSONAME = _get_config_var("INSTSONAME") if INSTSONAME and suffix in INSTSONAME: yield INSTSONAME LDLIBRARY = _get_config_var("LDLIBRARY") if LDLIBRARY and os.path.splitext(LDLIBRARY)[1] == suffix: yield LDLIBRARY LIBRARY = _get_config_var("LIBRARY") if LIBRARY and os.path.splitext(LIBRARY)[1] == suffix: yield LIBRARY DLLLIBRARY = _get_config_var("DLLLIBRARY") if DLLLIBRARY: yield DLLLIBRARY if _is_mingw: dlprefix = "lib" elif _is_windows or _is_cygwin: dlprefix = "" else: dlprefix = "lib" sysdata = dict( v=sys.version_info, # VERSION is X.Y in Linux/macOS and XY in Windows: VERSION=( _get_config_var("VERSION") or f"{sys.version_info.major}.{sys.version_info.minor}" ), ABIFLAGS=(_get_config_var("ABIFLAGS") or _get_config_var("abiflags") or ""), ) for stem in [ "python{VERSION}{ABIFLAGS}".format(**sysdata), "python{VERSION}".format(**sysdata), ]: yield dlprefix + stem + suffix def _linked_pythondll() -> str: # On Windows there is the `sys.dllhandle` attribute which is the # DLL Handle ID for the associated python.dll for the installation. # We can use the GetModuleFileName function to get the path to the # python.dll this way. # sys.dllhandle is an module ID, which is just a void* cast to an integer, # we turn it back into a pointer for the ctypes call dll_hmodule = ctypes.cast(sys.dllhandle, ctypes.c_void_p) # create a buffer for the return path of the maximum length of filepaths in Windows unicode interfaces # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation path_return_buffer = ctypes.create_unicode_buffer(32768) # GetModuleFileName sets the return buffer to the value of the path used to load the module. # We expect it to always be a normalized absolute path to python.dll. r = ctypes.windll.kernel32.GetModuleFileNameW( dll_hmodule, path_return_buffer, len(path_return_buffer) ) # The return value is the length of the returned string in unicode characters # if the size of the buffer (argument 3) is returned, the buffer was too small. # Don't know what else to do here but give up. if r == len(path_return_buffer): return None return path_return_buffer.value @_uniquified def candidate_paths(suffix=_SHLIB_SUFFIX): """ Iterate over candidate paths of libpython. Yields ------ path : str or None Candidate path to libpython. The path may not be a fullpath and may not exist. """ if _is_windows: yield _linked_pythondll() # List candidates for directories in which libpython may exist lib_dirs = [] _append_truthy(lib_dirs, _get_config_var("LIBPL")) _append_truthy(lib_dirs, _get_config_var("srcdir")) _append_truthy(lib_dirs, _get_config_var("LIBDIR")) # LIBPL seems to be the right config_var to use. It is the one # used in python-config when shared library is not enabled: # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57 # # But we try other places just in case. if _is_windows or _is_cygwin or _is_mingw: lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) else: lib_dirs.append( os.path.join(os.path.dirname(os.path.dirname(sys.executable)), "lib") ) # For macOS: _append_truthy(lib_dirs, _get_config_var("PYTHONFRAMEWORKPREFIX")) lib_dirs.append(sys.exec_prefix) lib_dirs.append(os.path.join(sys.exec_prefix, "lib")) lib_basenames = list(candidate_names(suffix=suffix)) if _is_posix and not _is_cygwin: for basename in lib_basenames: try: libpython = ctypes.CDLL(basename) except OSError: pass else: yield _linked_libpython_unix(libpython) try: yield from _get_proc_library() except OSError: _logger.debug("Unable to check /proc filesystem for libpython") for directory in lib_dirs: for basename in lib_basenames: yield os.path.join(directory, basename) # In macOS and Windows, ctypes.util.find_library returns a full path: for basename in lib_basenames: yield _find_library(_library_name(basename)) # Possibly useful links: # * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist # * https://github.com/Valloric/ycmd/issues/518 # * https://github.com/Valloric/ycmd/pull/519 def _normalize_path(path, suffix=_SHLIB_SUFFIX, _is_apple=_is_apple): """ Normalize shared library `path` to a real path. If `path` is not a full path, `None` is returned. If `path` does not exists, append `_SHLIB_SUFFIX` and check if it exists. Finally, the path is canonicalized by following the symlinks. Parameters ---------- path : str ot None A candidate path to a shared library. """ if not path: return None if not os.path.isabs(path): return None if os.path.isfile(path): return os.path.realpath(path) if os.path.isfile(path + suffix): return os.path.realpath(path + suffix) if _is_apple: return _normalize_path( _remove_suffix_apple(path), suffix=".so", _is_apple=False ) return None def _remove_suffix_apple(path): """ Strip off .so or .dylib. >>> _remove_suffix_apple("libpython.so") 'libpython' >>> _remove_suffix_apple("libpython.dylib") 'libpython' >>> _remove_suffix_apple("libpython3.7") 'libpython3.7' """ if path.endswith(".dylib"): return path[: -len(".dylib")] if path.endswith(".so"): return path[: -len(".so")] return path @_uniquified def _finding_libpython(): """ Iterate over existing libpython paths. The first item is likely to be the best one. Yields ------ path : str Existing path to a libpython. """ for path in candidate_paths(): _logger.debug("Candidate: %s", path) normalized = _normalize_path(path) if normalized: _logger.debug("Found: %s", normalized) yield normalized else: _logger.debug("Not found.") def find_libpython(): """ Return a path (`str`) to libpython or `None` if not found. Parameters ---------- path : str or None Existing path to the (supposedly) correct libpython. """ for path in _finding_libpython(): return os.path.realpath(path) def _print_all(items): for x in items: print(x) def _cli_find_libpython(cli_op, verbose): import logging # Importing `logging` module here so that using `logging.debug` # instead of `_logger.debug` outside of this function becomes an # error. if verbose: logging.basicConfig(format="%(levelname)s %(message)s", level=logging.DEBUG) if cli_op == "list-all": _print_all(_finding_libpython()) elif cli_op == "candidate-names": _print_all(candidate_names()) elif cli_op == "candidate-paths": _print_all(p for p in candidate_paths() if p and os.path.isabs(p)) elif cli_op == "platform-info": _log_platform_info() else: path = find_libpython() if path is None: return 1 print(path, end="") def _log_platform_info(): _logger.debug("_is_windows = %s", _is_windows) _logger.debug("_is_apple = %s", _is_apple) _logger.debug("_is_mingw = %s", _is_mingw) _logger.debug("_is_msys = %s", _is_cygwin) _logger.debug("_is_posix = %s", _is_posix) def main(args=None): import argparse parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "-v", "--verbose", action="store_true", help="Print debugging information." ) group = parser.add_mutually_exclusive_group() group.add_argument( "--list-all", action="store_const", dest="cli_op", const="list-all", help="Print list of all paths found.", ) group.add_argument( "--candidate-names", action="store_const", dest="cli_op", const="candidate-names", help="Print list of candidate names of libpython.", ) group.add_argument( "--candidate-paths", action="store_const", dest="cli_op", const="candidate-paths", help="Print list of candidate paths of libpython.", ) group.add_argument( "--platform-info", action="store_const", dest="cli_op", const="platform-info", help="Print information about the platform and exit.", ) group.add_argument( "--version", action="version", version=f"find_libpython {__version__}" ) ns = parser.parse_args(args) parser.exit(_cli_find_libpython(**vars(ns))) find_libpython-0.4.0/src/find_libpython/__main__.py000066400000000000000000000001071457435547700224370ustar00rootroot00000000000000from find_libpython import main if __name__ == "__main__": main() find_libpython-0.4.0/src/find_libpython/_version.py000066400000000000000000000000261457435547700225430ustar00rootroot00000000000000__version__ = "0.4.0" find_libpython-0.4.0/tests/000077500000000000000000000000001457435547700157125ustar00rootroot00000000000000find_libpython-0.4.0/tests/test_find_libpython.py000066400000000000000000000025351457435547700223400ustar00rootroot00000000000000import ctypes import sys import pytest from find_libpython import ( _get_proc_library, _is_cygwin, _is_posix, _linked_libpython_unix, find_libpython, ) try: ctypes.CDLL("") can_get_handle_to_main = True except OSError: print("Platform does not support opening the current process library") can_get_handle_to_main = False def test_find_libpython(): # find path path = find_libpython() # ensure we have a path assert path is not None # check to ensure it is a libpython share object lib = ctypes.CDLL(path) assert hasattr(lib, "Py_Initialize") # ensure it's the right version... lib.Py_GetVersion.restype = ctypes.c_char_p lib_version = lib.Py_GetVersion().decode().split()[0] curr_version = sys.version.split()[0] assert lib_version == curr_version @pytest.mark.skipif( not _is_posix or _is_cygwin or not can_get_handle_to_main, reason="only linux support this", ) def test_get_proc_library(): # Get current library libpython = ctypes.CDLL("") # Get library for reference path_linked = _linked_libpython_unix(libpython) # Get library from /proc method path_from_proc = next(_get_proc_library()) # Only compare paths if both return a library (not /usr/bin/python3.x) if "libpython" in path_linked: assert path_linked == path_from_proc