Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle C extensions not inside a package directory (at the root) #39

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions delocate/delocating.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from .pycompat import string_types
from .libsana import tree_libs, stripped_lib_dict, get_rp_stripper
from .tools import (set_install_name, zip2dir, dir2zip, validate_signature,
find_package_dirs, set_install_id, get_archs)
find_package_dirs, find_packages, set_install_id, get_archs,
dylibs_only)
from .tmpdirs import TemporaryDirectory
from .wheeltools import rewrite_record, InWheel

Expand Down Expand Up @@ -233,11 +234,6 @@ def _copy_required(lib_path, copy_filt_func, copied_libs):
copied_libs[required] = procd_requirings


def _dylibs_only(filename):
return (filename.endswith('.so') or
filename.endswith('.dylib'))


def filter_system_libs(libname):
return not (libname.startswith('/usr/lib') or
libname.startswith('/System'))
Expand Down Expand Up @@ -278,7 +274,7 @@ def delocate_path(tree_path, lib_path,
library.
"""
if lib_filt_func == "dylibs-only":
lib_filt_func = _dylibs_only
lib_filt_func = dylibs_only
if not exists(lib_path):
os.makedirs(lib_path)
lib_dict = tree_libs(tree_path, lib_filt_func)
Expand Down Expand Up @@ -359,7 +355,7 @@ def delocate_wheel(in_wheel,
library. The filenames in the keys are relative to the wheel root path.
"""
if lib_filt_func == "dylibs-only":
lib_filt_func = _dylibs_only
lib_filt_func = dylibs_only
in_wheel = abspath(in_wheel)
if out_wheel is None:
out_wheel = in_wheel
Expand All @@ -370,8 +366,12 @@ def delocate_wheel(in_wheel,
all_copied = {}
wheel_dir = realpath(pjoin(tmpdir, 'wheel'))
zip2dir(in_wheel, wheel_dir)
for package_path in find_package_dirs(wheel_dir):
lib_path = pjoin(package_path, lib_sdir)
for package_path, is_dir in find_packages(wheel_dir):
if is_dir:
lib_path = pjoin(package_path, lib_sdir)
else:
root_name = basename(package_path).split('.', 1)[0]
lib_path = pjoin(dirname(package_path), lib_sdir + root_name)
lib_path_exists = exists(lib_path)
copied_libs = delocate_path(package_path, lib_path,
lib_filt_func, copy_filt_func)
Expand Down
38 changes: 23 additions & 15 deletions delocate/libsana.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,28 @@
"""

import os
from os.path import join as pjoin, realpath
from os.path import join as pjoin, realpath, isfile

import warnings

from .tools import get_install_names, zip2dir, get_rpaths
from .tmpdirs import TemporaryDirectory


def _analyze_path(depending_libpath, lib_dict, filt_func):
if not filt_func is None and not filt_func(depending_libpath):
return
rpaths = get_rpaths(depending_libpath)
for install_name in get_install_names(depending_libpath):
lib_path = (install_name if install_name.startswith('@')
else realpath(install_name))
lib_path = resolve_rpath(lib_path, rpaths)
if lib_path in lib_dict:
lib_dict[lib_path][depending_libpath] = install_name
else:
lib_dict[lib_path] = {depending_libpath: install_name}


def tree_libs(start_path, filt_func=None):
""" Return analysis of library dependencies within `start_path`

Expand Down Expand Up @@ -48,20 +63,13 @@ def tree_libs(start_path, filt_func=None):
* http://matthew-brett.github.io/pydagogue/mac_runtime_link.html
"""
lib_dict = {}
for dirpath, dirnames, basenames in os.walk(start_path):
for base in basenames:
depending_libpath = realpath(pjoin(dirpath, base))
if not filt_func is None and not filt_func(depending_libpath):
continue
rpaths = get_rpaths(depending_libpath)
for install_name in get_install_names(depending_libpath):
lib_path = (install_name if install_name.startswith('@')
else realpath(install_name))
lib_path = resolve_rpath(lib_path, rpaths)
if lib_path in lib_dict:
lib_dict[lib_path][depending_libpath] = install_name
else:
lib_dict[lib_path] = {depending_libpath: install_name}
if isfile(start_path):
_analyze_path(start_path, lib_dict, filt_func)
else:
for dirpath, dirnames, basenames in os.walk(start_path):
for base in basenames:
depending_libpath = realpath(pjoin(dirpath, base))
_analyze_path(depending_libpath, lib_dict, filt_func)
return lib_dict


Expand Down
22 changes: 14 additions & 8 deletions delocate/tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import shutil

from ..tools import (back_tick, unique_by_index, ensure_writable, chmod_perms,
ensure_permissions, zip2dir, dir2zip, find_package_dirs,
ensure_permissions, zip2dir, dir2zip, find_packages,
cmp_contents, get_archs, lipo_fuse, replace_signature,
validate_signature, add_rpath)

Expand Down Expand Up @@ -152,25 +152,31 @@ def test_zip2():
assert_equal(os.stat(out_fname).st_mode & 0o777, permissions)


def test_find_package_dirs():
# Test utility for finding package directories
def test_find_packages():
# Test utility for finding packages directories
with InTemporaryDirectory():
os.mkdir('to_test')
a_dir = pjoin('to_test', 'a_dir')
b_dir = pjoin('to_test', 'b_dir')
c_dir = pjoin('to_test', 'c_dir')
for dir in (a_dir, b_dir, c_dir):
os.mkdir(dir)
assert_equal(find_package_dirs('to_test'), set([]))
assert_equal(find_packages('to_test'), set([]))
_write_file(pjoin(a_dir, '__init__.py'), "# a package")
assert_equal(find_package_dirs('to_test'), set([a_dir]))
assert_equal(find_packages('to_test'), set([(a_dir, True)]))
_write_file(pjoin(c_dir, '__init__.py'), "# another package")
assert_equal(find_package_dirs('to_test'), set([a_dir, c_dir]))
assert_equal(find_packages('to_test'), set([(a_dir, True), (c_dir, True)]))
# Not recursive
assert_equal(find_package_dirs('.'), set())
assert_equal(find_packages('.'), set())
_write_file(pjoin('to_test', '__init__.py'), "# base package")
# Also - strips '.' for current directory
assert_equal(find_package_dirs('.'), set(['to_test']))
assert_equal(find_packages('.'), set([('to_test', True)]))
# Additionally, find C Extension modules in the root
with InTemporaryDirectory():
_write_file('_so_mod.so', "# .so c extension")
assert_equal(find_packages('.'), set([('_so_mod.so', False)]))
_write_file('_dylib_mod.dylib', "# .dylib c extension")
assert_equal(find_packages('.'), set([('_so_mod.so', False), ('_dylib_mod.dylib', False)]))


def test_cmp_contents():
Expand Down
36 changes: 32 additions & 4 deletions delocate/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from subprocess import Popen, PIPE

import os
from os.path import join as pjoin, relpath, isdir, exists
from os.path import join as pjoin, relpath, isdir, isfile, exists
import zipfile
import re
import stat
import time
import warnings


class InstallNameError(Exception):
Expand Down Expand Up @@ -392,6 +393,23 @@ def dir2zip(in_dir, zip_fname):
def find_package_dirs(root_path):
""" Find python package directories in directory `root_path`

Parameters
----------
root_path : str
Directory to search for package subdirectories

Returns
-------
Set of strings where each is a subdirectory of `root_path`, containing
an ``__init__.py`` file. Paths prefixed by `root_path`
"""
warnings.warn("find_package_dirs() is deprecated, please use find_packages()", DeprecationWarning)
return [p for p, is_dir in find_packages(root_path) if is_dir]


def find_packages(root_path, lib_filt_func=None):
""" Find python packages in directory `root_path`

Parameters
----------
root_path : str
Expand All @@ -400,14 +418,19 @@ def find_package_dirs(root_path):
Returns
-------
package_sdirs : set
Set of strings where each is a subdirectory of `root_path`, containing
an ``__init__.py`` file. Paths prefixed by `root_path`
Set of (string, is_dir) tuples where each string is a subdirectory of
`root_path` containing an ``__init__.py`` file, or a C extension
single-file module directly in `root_path`, and is_dir indicates whether
the package is a subdirectory module or a single-file module. Paths
prefixed by `root_path`
"""
package_sdirs = set()
for entry in os.listdir(root_path):
fname = entry if root_path == '.' else pjoin(root_path, entry)
if isdir(fname) and exists(pjoin(fname, '__init__.py')):
package_sdirs.add(fname)
package_sdirs.add((fname, True))
elif isfile(fname) and dylibs_only(fname):
matthew-brett marked this conversation as resolved.
Show resolved Hide resolved
package_sdirs.add((fname, False))
return package_sdirs


Expand Down Expand Up @@ -528,3 +551,8 @@ def validate_signature(filename):

# This file's signature is invalid and needs to be replaced
replace_signature(filename, '-') # Replace with an ad-hoc signature


def dylibs_only(filename):
return (filename.endswith('.so') or
filename.endswith('.dylib'))