diff options
-rw-r--r-- | .gitmodules | 24 | ||||
-rw-r--r-- | README.rst | 22 | ||||
-rw-r--r-- | checklibs.py | 318 | ||||
-rw-r--r-- | qtinfo.py | 4 | ||||
-rw-r--r-- | setup.py | 37 | ||||
m--------- | sources/pyside-examples | 0 | ||||
m--------- | sources/pyside-examples2 | 0 | ||||
m--------- | sources/pyside-tools2 | 0 | ||||
m--------- | sources/pyside2 | 13 | ||||
m--------- | sources/shiboken2 | 10 |
10 files changed, 387 insertions, 41 deletions
diff --git a/.gitmodules b/.gitmodules index e5567f5ab..f23e1853b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ -[submodule "sources/shiboken"] - path = sources/shiboken - url = https://github.com/PySide/shiboken.git -[submodule "sources/pyside"] - path = sources/pyside - url = https://github.com/PySide/pyside.git -[submodule "sources/pyside-tools"] - path = sources/pyside-tools - url = https://github.com/PySide/Tools.git -[submodule "sources/pyside-examples"] - path = sources/pyside-examples - url = https://github.com/PySide/Examples.git +[submodule "sources/pyside2"] + path = sources/pyside2 + url=https://github.com/PySide/pyside2.git +[submodule "sources/shiboken2"] + path = sources/shiboken2 + url=https://github.com/PySide/shiboken2.git +[submodule "sources/pyside-tools2"] + path = sources/pyside-tools2 + url=https://github.com/PySide/pyside-tools2.git +[submodule "sources/pyside-examples2"] + path = sources/pyside-examples2 + url=https://github.com/PySide/pyside-examples2.git diff --git a/README.rst b/README.rst index 809354c7f..26ab178e8 100644 --- a/README.rst +++ b/README.rst @@ -1,20 +1,20 @@ -====== -PySide -====== +======= +PySide2 +======= -.. image:: https://pypip.in/wheel/PySide/badge.png +.. image:: https://img.shields.io/pypi/wheel/pyside.svg :target: https://pypi.python.org/pypi/PySide/ :alt: Wheel Status -.. image:: https://pypip.in/download/PySide/badge.png +.. image:: https://img.shields.io/pypi/dm/pyside.svg :target: https://pypi.python.org/pypi/PySide/ :alt: Downloads -.. image:: https://pypip.in/version/PySide/badge.png +.. image:: https://img.shields.io/pypi/v/pyside.svg :target: https://pypi.python.org/pypi/PySide/ :alt: Latest Version -.. image:: https://pypip.in/license/PySide/badge.png +.. image:: https://binstar.org/asmeurer/pyside/badges/license.svg :target: https://pypi.python.org/pypi/PySide/ :alt: License @@ -27,6 +27,12 @@ PySide Introduction ============ +.. note:: + + This text is mostly from PySide 1.2.X. We need to update it when + PySide2 for Qt5 is more ready. + + PySide is the Python Qt bindings project, providing access the complete Qt 4.8 framework as well as to generator tools for rapidly generating bindings for any C++ libraries. @@ -45,7 +51,7 @@ PySide requires Python 2.6 or later and Qt 4.6 or better. .. note:: - Qt 5.x is currently not supported. + Qt 5.x is currently in the works. Installation ============ diff --git a/checklibs.py b/checklibs.py new file mode 100644 index 000000000..ff4d51ae8 --- /dev/null +++ b/checklibs.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python +# +# checklibs.py +# +# Check Mach-O dependencies. +# +# See http://www.entropy.ch/blog/Developer/2011/03/05/2011-Update-to-checklibs-Script-for-dynamic-library-dependencies.html +# +# Written by Marc Liyanage <http://www.entropy.ch> +# +# + +import subprocess, sys, re, os.path, optparse, collections +from pprint import pprint + + +class MachOFile: + + def __init__(self, image_path, arch, parent = None): + self.image_path = image_path + self._dependencies = [] + self._cache = dict(paths = {}, order = []) + self.arch = arch + self.parent = parent + self.header_info = {} + self.load_info() + self.add_to_cache() + + def load_info(self): + if not self.image_path.exists(): + return + self.load_header() + self.load_rpaths() + + def load_header(self): + # Get the mach-o header info, we're interested in the file type (executable, dylib) + cmd = 'otool -arch {0} -h "{1}"' + output = self.shell(cmd, [self.arch, self.image_path.resolved_path], fatal = True) + if not output: + print >> sys.stderr, 'Unable to load mach header for {0} ({1}), architecture mismatch? Use --arch option to pick architecture'.format(self.image_path.resolved_path, self.arch) + exit() + (keys, values) = output.splitlines()[2:] + self.header_info = dict(zip(keys.split(), values.split())) + + def load_rpaths(self): + output = self.shell('otool -arch {0} -l "{1}"', [self.arch, self.image_path.resolved_path], fatal = True) + load_commands = re.split('Load command (\d+)', output)[1:] # skip file name on first line + self._rpaths = [] + load_commands = collections.deque(load_commands) + while load_commands: + load_commands.popleft() # command index + command = load_commands.popleft().strip().splitlines() + if command[0].find('LC_RPATH') == -1: + continue + + path = re.findall('path (.+) \(offset \d+\)$', command[2])[0] + image_path = self.image_path_for_recorded_path(path) + image_path.rpath_source = self + self._rpaths.append(image_path) + + def ancestors(self): + ancestors = [] + parent = self.parent + while parent: + ancestors.append(parent) + parent = parent.parent + + return ancestors + + def self_and_ancestors(self): + return [self] + self.ancestors() + + def rpaths(self): + return self._rpaths + + def all_rpaths(self): + rpaths = [] + for image in self.self_and_ancestors(): + rpaths.extend(image.rpaths()) + return rpaths + + def root(self): + if not self.parent: + return self + return self.ancestors()[-1] + + def executable_path(self): + root = self.root() + if root.is_executable(): + return root.image_path + return None + + def filetype(self): + return long(self.header_info.get('filetype', 0)) + + def is_dylib(self): + return self.filetype() == MachOFile.MH_DYLIB + + def is_executable(self): + return self.filetype() == MachOFile.MH_EXECUTE + + def all_dependencies(self): + self.walk_dependencies() + return self.cache()['order'] + + def walk_dependencies(self, known = {}): + if known.get(self.image_path.resolved_path): + return + + known[self.image_path.resolved_path] = self + + for item in self.dependencies(): + item.walk_dependencies(known) + + def dependencies(self): + if not self.image_path.exists(): + return [] + + if self._dependencies: + return self._dependencies + + output = self.shell('otool -arch {0} -L "{1}"', [self.arch, self.image_path.resolved_path], fatal = True) + output = [line.strip() for line in output.splitlines()] + del(output[0]) + if self.is_dylib(): + del(output[0]) # In the case of dylibs, the first line is the id line + + self._dependencies = [] + for line in output: + match = re.match('^(.+)\s+(\(.+)\)$', line) + if not match: + continue + recorded_path = match.group(1) + image_path = self.image_path_for_recorded_path(recorded_path) + image = self.lookup_or_make_item(image_path) + self._dependencies.append(image) + + return self._dependencies + + # The root item holds the cache, all lower-level requests bubble up the parent chain + def cache(self): + if self.parent: + return self.parent.cache() + return self._cache + + def add_to_cache(self): + cache = self.cache() + cache['paths'][self.image_path.resolved_path] = self + cache['order'].append(self) + + def cached_item_for_path(self, path): + if not path: + return None + return self.cache()['paths'].get(path) + + def lookup_or_make_item(self, image_path): + image = self.cached_item_for_path(image_path.resolved_path) + if not image: # cache miss + image = MachOFile(image_path, self.arch, parent = self) + return image + + def image_path_for_recorded_path(self, recorded_path): + path = ImagePath(None, recorded_path) + + # handle @executable_path + if recorded_path.startswith(ImagePath.EXECUTABLE_PATH_TOKEN): + executable_image_path = self.executable_path() + if executable_image_path: + path.resolved_path = os.path.normpath(recorded_path.replace(ImagePath.EXECUTABLE_PATH_TOKEN, os.path.dirname(executable_image_path.resolved_path))) + + # handle @loader_path + elif recorded_path.startswith(ImagePath.LOADER_PATH_TOKEN): + path.resolved_path = os.path.normpath(recorded_path.replace(ImagePath.LOADER_PATH_TOKEN, os.path.dirname(self.image_path.resolved_path))) + + # handle @rpath + elif recorded_path.startswith(ImagePath.RPATH_TOKEN): + for rpath in self.all_rpaths(): + resolved_path = os.path.normpath(recorded_path.replace(ImagePath.RPATH_TOKEN, rpath.resolved_path)) + if os.path.exists(resolved_path): + path.resolved_path = resolved_path + path.rpath_source = rpath.rpath_source + break + + # handle absolute path + elif recorded_path.startswith('/'): + path.resolved_path = recorded_path + + return path + + def __repr__(self): + return str(self.image_path) + + def dump(self): + print self.image_path + for dependency in self.dependencies(): + print '\t{0}'.format(dependency) + + @staticmethod + def shell(cmd_format, args, fatal = False): + cmd = cmd_format.format(*args) + popen = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE) + output = popen.communicate()[0] + if popen.returncode and fatal: + print >> sys.stderr, 'Nonzero exit status for shell command "{0}"'.format(cmd) + sys.exit(1) + + return output + + @classmethod + def architectures_for_image_at_path(cls, path): + output = cls.shell('file "{}"', [path]) + file_architectures = re.findall(r' executable (\w+)', output) + ordering = 'x86_64 i386'.split() + file_architectures = sorted(file_architectures, lambda a, b: cmp(ordering.index(a), ordering.index(b))) + return file_architectures + + MH_EXECUTE = 0x2 + MH_DYLIB = 0x6 + MH_BUNDLE = 0x8 + + +# ANSI terminal coloring sequences +class Color: + HEADER = '\033[95m' + BLUE = '\033[94m' + GREEN = '\033[92m' + RED = '\033[91m' + ENDC = '\033[0m' + + @staticmethod + def red(string): + return Color.wrap(string, Color.RED) + + @staticmethod + def blue(string): + return Color.wrap(string, Color.BLUE) + + @staticmethod + def wrap(string, color): + return Color.HEADER + color + string + Color.ENDC + + +# This class holds path information for a mach-0 image file. It holds the path as it was recorded +# in the loading binary as well as the effective, resolved file system path. +# The former can contain @-replacement tokens. +# In the case where the recorded path contains an @rpath token that was resolved successfully, we also +# capture the path of the binary that supplied the rpath value that was used. +# That path itself can contain replacement tokens such as @loader_path. +class ImagePath: + + def __init__(self, resolved_path, recorded_path = None): + self.recorded_path = recorded_path + self.resolved_path = resolved_path + self.rpath_source = None + + def __repr__(self): + description = None + + if self.resolved_equals_recorded() or self.recorded_path == None: + description = self.resolved_path + else: + description = '{0} ({1})'.format(self.resolved_path, self.recorded_path) + + if (not self.is_system_location()) and (not self.uses_dyld_token()): + description = Color.blue(description) + + if self.rpath_source: + description += ' (rpath source: {0})'.format(self.rpath_source.image_path.resolved_path) + + if not self.exists(): + description += Color.red(' (missing)') + + return description + + def exists(self): + return self.resolved_path and os.path.exists(self.resolved_path) + + def resolved_equals_recorded(self): + return self.resolved_path and self.recorded_path and self.resolved_path == self.recorded_path + + def uses_dyld_token(self): + return self.recorded_path and self.recorded_path.startswith('@') + + def is_system_location(self): + system_prefixes = ['/System/Library', '/usr/lib'] + for prefix in system_prefixes: + if self.resolved_path and self.resolved_path.startswith(prefix): + return True + + EXECUTABLE_PATH_TOKEN = '@executable_path' + LOADER_PATH_TOKEN = '@loader_path' + RPATH_TOKEN = '@rpath' + + +# Command line driver +parser = optparse.OptionParser(usage = "Usage: %prog [options] path_to_mach_o_file") +parser.add_option("--arch", dest = "arch", help = "architecture", metavar = "ARCH") +parser.add_option("--all", dest = "include_system_libraries", help = "Include system frameworks and libraries", action="store_true") +(options, args) = parser.parse_args() + +if len(args) < 1: + parser.print_help() + sys.exit(1) + +archs = MachOFile.architectures_for_image_at_path(args[0]) +if archs and not options.arch: + print >> sys.stderr, 'Analyzing architecture {}, override with --arch if needed'.format(archs[0]) + options.arch = archs[0] + +toplevel_image = MachOFile(ImagePath(args[0]), options.arch) + +for dependency in toplevel_image.all_dependencies(): + if dependency.image_path.exists() and (not options.include_system_libraries) and dependency.image_path.is_system_location(): + continue + + dependency.dump() + print + @@ -33,6 +33,9 @@ class QtInfo(object): def getHeadersPath(self): return self.getProperty("QT_INSTALL_HEADERS") + def getDocsPath(self): + return self.getProperty("QT_INSTALL_DOCS") + def getProperty(self, prop_name): cmd = [self._qmake_path, "-query", prop_name] proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, shell=False) @@ -52,3 +55,4 @@ class QtInfo(object): imports_dir = property(getImportsPath) translations_dir = property(getTranslationsPath) headers_dir = property(getHeadersPath) + docs_dir = property(getDocsPath) @@ -33,7 +33,7 @@ submodules = { ["shiboken2", "qt5"], ["pyside2", "qt5"], ["pyside-tools2", "qt5"], - ["pyside-examples", "master"], + ["pyside-examples2", "qt5"], ], '1.3.0dev': [ ["shiboken", "master"], @@ -113,6 +113,12 @@ from utils import regenerate_qt_resources from utils import filter_match from utils import osx_localize_libpaths +# guess a close folder name for extensions +def get_extension_folder(ext): + maybe = list(map(lambda x:x[0], submodules[__version__])) + folder = difflib.get_close_matches(ext, maybe)[0] + return folder + # Declare options OPTION_DEBUG = has_option("debug") OPTION_RELWITHDEBINFO = has_option('relwithdebinfo') @@ -203,7 +209,7 @@ Use --list-versions option to get list of available versions""" % OPTION_VERSION if OPTION_NOEXAMPLES: # remove pyside-exampes from submodules so they will not be included for idx, item in enumerate(submodules[__version__]): - if item[0] == 'pyside-examples': + if item[0].startswith('pyside-examples'): del submodules[__version__][idx] @@ -236,7 +242,11 @@ for n in ["pyside_package", "build", "PySide-%s" % __version__]: d = os.path.join(script_dir, n) if os.path.isdir(d): print("Removing %s" % d) - rmtree(d) + try: + rmtree(d) + except Exception as e: + print('***** problem removing "{}"'.format(d)) + print('ignored error: {}'.format(e)) # Prepare package folders for pkg in ["pyside_package/PySide", "pyside_package/pysideuic"]: @@ -490,6 +500,7 @@ class pyside_build(_build): log.info("Qt qmake: %s" % self.qmake_path) log.info("Qt version: %s" % qtinfo.version) log.info("Qt bins: %s" % qtinfo.bins_dir) + log.info("Qt docs: %s" % qtinfo.docs_dir) log.info("Qt plugins: %s" % qtinfo.plugins_dir) log.info("-" * 3) log.info("OpenSSL libs: %s" % OPTION_OPENSSL) @@ -536,8 +547,7 @@ class pyside_build(_build): def build_extension(self, extension): # calculate the subrepos folder name - maybe = list(map(lambda x:x[0], submodules[__version__])) - folder = difflib.get_close_matches(extension, maybe)[0] + folder = get_extension_folder(extension) log.info("Building module %s..." % extension) @@ -550,9 +560,14 @@ class pyside_build(_build): return if os.path.exists(module_build_dir): log.info("Deleting module build folder %s..." % module_build_dir) - rmtree(module_build_dir) + try: + rmtree(module_build_dir) + except Exception as e: + print('***** problem removing "{}"'.format(module_build_dir)) + print('ignored error: {}'.format(e)) log.info("Creating module build folder %s..." % module_build_dir) - os.makedirs(module_build_dir) + if not os.path.exists(module_build_dir): + os.makedirs(module_build_dir) os.chdir(module_build_dir) module_src_dir = os.path.join(self.sources_dir, folder) @@ -564,6 +579,7 @@ class pyside_build(_build): "-DQT_QMAKE_EXECUTABLE=%s" % self.qmake_path, "-DBUILD_TESTS=%s" % self.build_tests, "-DDISABLE_DOCSTRINGS=True", + "-DQt5Help_DIR=%s" % self.qtinfo.docs_dir, "-DCMAKE_BUILD_TYPE=%s" % self.build_type, "-DCMAKE_INSTALL_PREFIX=%s" % self.install_dir, module_src_dir @@ -634,6 +650,7 @@ class pyside_build(_build): "py_version": self.py_version, "qt_version": self.qtinfo.version, "qt_bin_dir": self.qtinfo.bins_dir, + "qt_doc_dir": self.qtinfo.docs_dir, "qt_lib_dir": self.qtinfo.libs_dir, "qt_plugins_dir": self.qtinfo.plugins_dir, "qt_imports_dir": self.qtinfo.imports_dir, @@ -717,8 +734,9 @@ class pyside_build(_build): vars=vars) if not OPTION_NOEXAMPLES: # <sources>/pyside-examples/examples/* -> <setup>/PySide/examples + folder = get_extension_folder('pyside-examples') copydir( - "{sources_dir}/pyside-examples/examples", + "{sources_dir}/%s/examples" % folder, "{dist_dir}/PySide/examples", force=False, vars=vars) # Re-generate examples Qt resource files for Python 3 compatibility @@ -836,8 +854,9 @@ class pyside_build(_build): vars=vars) if not OPTION_NOEXAMPLES: # <sources>/pyside-examples/examples/* -> <setup>/PySide/examples + folder = get_extension_folder('pyside-examples') copydir( - "{sources_dir}/pyside-examples/examples", + "{sources_dir}/%s/examples" % folder, "{dist_dir}/PySide/examples", force=False, vars=vars) # Re-generate examples Qt resource files for Python 3 compatibility diff --git a/sources/pyside-examples b/sources/pyside-examples deleted file mode 160000 -Subproject df4f44b6aeb08f27da270392f5d9dc2e2f28e3a diff --git a/sources/pyside-examples2 b/sources/pyside-examples2 new file mode 160000 +Subproject 0d814764dd2d34553b4f4d8f34a0641457a21a7 diff --git a/sources/pyside-tools2 b/sources/pyside-tools2 -Subproject d7f2a9bc3d228884235f19e1e73d132e7db39de +Subproject f69bcb12713ca10752f0175f64ae5bcb3125fed diff --git a/sources/pyside2 b/sources/pyside2 -Subproject cd0f27e7219c64fe6a4a079892f75d1a6af726b +Subproject b8473ad41057eefa3f48faf02a9bad360200ad6 diff --git a/sources/shiboken2 b/sources/shiboken2 -Subproject af111f1634bbaea5e7685fc89ae14dc2c293337 +Subproject bfb1186f639d7ccd71e1f2baaf7bd342df4837e |