""" Py2app support for project using sip, which basicly means PyQt and wrappers for other Qt-based libraries. This will include all C modules that might be used when you import a package using sip because we have no way to fine-tune this. The problem with SIP is that all inter-module depedencies (for example from PyQt4.Qt to PyQt4.QtCore) are handled in C code and therefore cannot be detected by the python code in py2app). """ # since pkg_resources.py lives next to this file, we need to disambiguate the import from __future__ import absolute_import import sys import glob import os import pkg_resources class Sip(object): def __init__(self): self.packages = None self.plugin_dir = None def config(self): if self.packages is not None: print("packages", self.packages) return self.packages import sipconfig, os try: from PyQt4 import pyqtconfig cfg = pyqtconfig.Configuration() qtdir = cfg.qt_lib_dir sipdir = os.path.dirname(cfg.pyqt_mod_dir) self.plugin_dir = os.path.join(cfg.qt_dir, 'plugins') except ImportError: from PyQt5.QtCore import QLibraryInfo qtdir = QLibraryInfo.location(QLibraryInfo.LibrariesPath) self.plugin_dir = QLibraryInfo.location(QLibraryInfo.PluginsPath) sipdir = os.path.dirname(sipconfig.__file__) if not os.path.exists(qtdir): print("sip: Qtdir %r does not exist"%(qtdir)) # half-broken installation? ignore. raise ImportError # Qt is GHETTO! dyld_library_path = os.environ.get('DYLD_LIBRARY_PATH', '').split(':') if qtdir not in dyld_library_path: dyld_library_path.insert(0, qtdir) os.environ['DYLD_LIBRARY_PATH'] = ':'.join(dyld_library_path) self.packages = set() for fn in os.listdir(sipdir): fullpath = os.path.join(sipdir, fn) if os.path.isdir(fullpath): self.packages.add(fn) if fn in ('PyQt4', 'PyQt5'): # PyQt4 and later has a nested structure, also import # subpackage to ensure everything get seen. for sub in os.listdir(fullpath): if ".py" not in sub: self.packages.add('%s.%s'%(fn, sub.replace(".so",""))) # Causes a python3-related syntax error (metaclass keyword), # and you probably don't need it: #if "PyQt4.uic" in self.packages and sys.version_info.major != 3: # print("WARNING: PyQt uic module found.") # print("avoid python3 metaclass syntax errors by adding 'PyQt4.uic' to your excludes option.") print("sip: packages: %s"%(self.packages,)) return self.packages def check(self, cmd, mf): try: packages = self.config() except ImportError: return None if 'PyQt4.uic' in packages: # PyQt4.uic contains subpackages with python 2 and python 3 # support. Exclude the variant that won't be ussed, this avoids # compilation errors on Python 2 (because some of the Python 3 # code is not valid Python 2 code) if sys.version_info[0] == 2: ref = 'PyQt4.uic.port_v3' else: ref = 'PyQt4.uic.port_v2' if 'PyQt5.uic' in packages: # ditto if sys.version_info[0] == 2: ref = 'PyQt5.uic.port_v3' else: ref = 'PyQt5.uic.port_v2' # Exclude... mf.lazynodes[ref] = None for pkg in packages: m = mf.findNode(pkg) if m is not None and m.filename is not None: break else: print("sip: No sip package used in application") return None mf.import_hook('sip', m) m = mf.findNode('sip') # naive inclusion of ALL sip packages # stupid C modules.. hate hate hate for pkg in packages: try: mf.import_hook(pkg, m) except ImportError as exc: print("WARNING: ImportError in sip recipe ignored: %s"%(exc,)) print(mf.findNode("PyQt4")) print(mf.findNode("PyQt5")) if mf.findNode('PyQt4') is not None or mf.findNode('PyQt5') is not None: resources = [pkg_resources.resource_filename('py2app', 'recipes/qt.conf')] for item in cmd.qt_plugins: if '/' not in item: item = item + '/*' if '*' in item: for path in glob.glob(os.path.join(self.plugin_dir, item)): resources.append((os.path.dirname('qt_plugins' + path[len(self.plugin_dir):]), [path])) else: resources.append((os.path.dirname(os.path.join('qt_plugins', item)), [os.path.join(self.plugin_dir, item)])) print("PyQt resources", resources) return dict(resources=resources) print("Return {}") return dict() check = Sip().check