diff --git a/doc/changelog.rst b/doc/changelog.rst index 23a48c7..21bfcc9 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -4,6 +4,14 @@ Release history py2app 0.28 ----------- +* #413: Find dist-info in included pythonXX.zip + + By default the ``working_set`` of pkg_resources does not contain + distribution information from packages included in zip files, such + as the zipped-up stdlib + site-pakckages in py2app bundles. + + Add some monkey patching to apps using ``pkg_resources`` to fix this. + * Fix hard crash in "rtree" recipe when the package contents doesn't match the recipe expectations. diff --git a/py2app/recipes/setuptools.py b/py2app/recipes/setuptools.py index 7c223a0..72a4ffd 100644 --- a/py2app/recipes/setuptools.py +++ b/py2app/recipes/setuptools.py @@ -1,6 +1,46 @@ import sys +import textwrap import os +if sys.version_info[0] == 2: + from cStringIO import StringIO +else: + from io import StringIO + +PRESCRIPT=textwrap.dedent("""\ + import pkg_resources, zipimport, os + + def find_eggs_in_zip(importer, path_item, only=False): + print(f"override", path_item) + if importer.archive.endswith('.whl'): + # wheels are not supported with this finder + # they don't have PKG-INFO metadata, and won't ever contain eggs + return + + metadata = pkg_resources.EggMetadata(importer) + if metadata.has_metadata('PKG-INFO'): + yield Distribution.from_filename(path_item, metadata=metadata) + for subitem in metadata.resource_listdir(''): + if not only and pkg_resources._is_egg_path(subitem): + subpath = os.path.join(path_item, subitem) + dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) + for dist in dists: + yield dist + elif subitem.lower().endswith(('.dist-info', '.egg-info')): + print(f"nested", path_item) + subpath = os.path.join(path_item, subitem) + submeta = pkg_resources.EggMetadata(zipimport.zipimporter(subpath)) + submeta.egg_info = subpath + yield pkg_resources.Distribution.from_location(path_item, subitem, submeta) + + def _fixup_pkg_resources(): + pkg_resources.register_finder(zipimport.zipimporter, find_eggs_in_zip) + pkg_resources.working_set.entries = [] + list(map(pkg_resources.working_set.add_entry, sys.path)) + + _fixup_pkg_resources() +""") + def check(cmd, mf): m = mf.findNode("pkg_resources") @@ -43,4 +83,4 @@ def check(cmd, mf): if sys.version[0] != 2: expected_missing_imports.add("__builtin__") - return {"expected_missing_imports": expected_missing_imports} + return {"expected_missing_imports": expected_missing_imports, "prescripts": [StringIO(PRESCRIPT)]}