dist: support implicit namespace packages (as per PEP 420)

Fixes #778
This commit is contained in:
rdb 2019-12-11 15:28:01 +01:00
parent a503e9439e
commit c3052f3ae0

View File

@ -2221,6 +2221,10 @@ class Freezer:
return True return True
_PKG_NAMESPACE_DIRECTORY = object()
class PandaModuleFinder(modulefinder.ModuleFinder): class PandaModuleFinder(modulefinder.ModuleFinder):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
@ -2279,6 +2283,44 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
return None return None
def _dir_exists(self, path):
"""Returns True if the given directory exists, either on disk or inside
a wheel."""
if os.path.isdir(path):
return True
# Is there a zip file along the path?
dir, dirname = os.path.split(path.rstrip(os.path.sep + '/'))
fn = dirname
while dirname:
if os.path.isfile(dir):
# Okay, this is actually a file. Is it a zip file?
if dir in self._zip_files:
# Yes, and we've previously opened this.
zip = self._zip_files[dir]
elif zipfile.is_zipfile(dir):
zip = zipfile.ZipFile(dir)
self._zip_files[dir] = zip
else:
# It's a different kind of file. Stop looking.
return None
# (Most) zip files do not store directories; check instead for a
# file whose path starts with this directory name.
prefix = fn.replace(os.path.sep, '/') + '/'
for name in zip.namelist():
if name.startswith(prefix):
return True
return False
# Look at the parent directory.
dir, dirname = os.path.split(dir)
fn = os.path.join(dirname, fn)
return False
def load_module(self, fqname, fp, pathname, file_info): def load_module(self, fqname, fp, pathname, file_info):
"""Copied from ModuleFinder.load_module with fixes to handle sending bytes """Copied from ModuleFinder.load_module with fixes to handle sending bytes
to compile() for PY_SOURCE types. Sending bytes to compile allows it to to compile() for PY_SOURCE types. Sending bytes to compile allows it to
@ -2291,6 +2333,12 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
self.msgout(2, "load_module ->", m) self.msgout(2, "load_module ->", m)
return m return m
if type is _PKG_NAMESPACE_DIRECTORY:
m = self.add_module(fqname)
m.__code__ = compile('', '', 'exec')
m.__path__ = pathname
return m
if type == imp.PY_SOURCE: if type == imp.PY_SOURCE:
if fqname in overrideModules: if fqname in overrideModules:
# This module has a custom override. # This module has a custom override.
@ -2384,6 +2432,8 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
pass pass
# Look for the module on the search path. # Look for the module on the search path.
ns_dirs = []
for dir_path in path: for dir_path in path:
basename = os.path.join(dir_path, name.split('.')[-1]) basename = os.path.join(dir_path, name.split('.')[-1])
@ -2400,6 +2450,10 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
if self._open_file(init, mode): if self._open_file(init, mode):
return (None, basename, ('', '', imp.PKG_DIRECTORY)) return (None, basename, ('', '', imp.PKG_DIRECTORY))
# This may be a namespace package.
if self._dir_exists(basename):
ns_dirs.append(basename)
# It wasn't found through the normal channels. Maybe it's one of # It wasn't found through the normal channels. Maybe it's one of
# ours, or maybe it's frozen? # ours, or maybe it's frozen?
if not path: if not path:
@ -2408,6 +2462,11 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
# It's a frozen module. # It's a frozen module.
return (None, name, ('', '', imp.PY_FROZEN)) return (None, name, ('', '', imp.PY_FROZEN))
# If we found folders on the path with this module name without an
# __init__.py file, we should consider this a namespace package.
if ns_dirs and sys.version_info >= (3, 3):
return (None, ns_dirs, ('', '', _PKG_NAMESPACE_DIRECTORY))
raise ImportError(name) raise ImportError(name)
def find_all_submodules(self, m): def find_all_submodules(self, m):