From e0a4372160d9b1ab1550589784cc456b93d28cab Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 20 Feb 2018 16:10:49 +0100 Subject: Fix wheel packages on macOS to contain min correct deployment target Python's distutils does not know about the minimum deployment target of extensions, but only the version that was used when building the Python interpreter itself. This value is propagated to the final wheel name, and confuses users on which macOS version can the package be used. This patch computes the correct minimum deployment target, and forces distutils (and wheel) to use it in the wheel name. Unfortunately it requires a slightly dirty hack to circumvent a rigid check in distutils itself. Task-number: PYSIDE-612 Change-Id: I4380ee81840f9746b210579700cc5d69d3865810 Reviewed-by: Fredrik Averpil Reviewed-by: Cristian Maureira-Fredes Reviewed-by: Friedemann Kleint --- setup.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 20 deletions(-) (limited to 'setup.py') diff --git a/setup.py b/setup.py index 5918e2464..fc9b49255 100644 --- a/setup.py +++ b/setup.py @@ -202,6 +202,7 @@ from distutils.sysconfig import get_python_lib from distutils.spawn import find_executable from distutils.command.build import build as _build from distutils.command.build_ext import build_ext as _build_ext +from distutils.util import get_platform from setuptools import setup, Extension from setuptools.command.install import install as _install @@ -210,6 +211,13 @@ from setuptools.command.bdist_egg import bdist_egg as _bdist_egg from setuptools.command.develop import develop as _develop from setuptools.command.build_py import build_py as _build_py +wheel_module_exists = False +try: + from wheel.bdist_wheel import bdist_wheel as _bdist_wheel + wheel_module_exists = True +except ImportError: + pass + from qtinfo import QtInfo from utils import rmtree from utils import makefile @@ -522,6 +530,18 @@ class pyside_build_ext(_build_ext): def run(self): pass +if wheel_module_exists: + class pyside_build_wheel(_bdist_wheel): + def __init__(self, *args, **kwargs): + _bdist_wheel.__init__(self, *args, **kwargs) + + def finalize_options(self): + if sys.platform == 'darwin': + # Override the platform name to contain the correct minimum deployment target. + # This is used in the final wheel name. + self.plat_name = pyside_build.macos_plat_name() + _bdist_wheel.finalize_options(self) + # pyside_build_py and pyside_install_lib are reimplemented to preserve symlinks when # distutils / setuptools copy files to various directories through the different build stages. class pyside_build_py(_build_py): @@ -561,6 +581,22 @@ class pyside_build(_build): def __init__(self, *args, **kwargs): _build.__init__(self, *args, **kwargs) + def finalize_options(self): + os_name_backup = os.name + if sys.platform == 'darwin': + self.plat_name = pyside_build.macos_plat_name() + # This is a hack to circumvent the dubious check in distutils.commands.build -> + # finalize_options, which only allows setting the plat_name for windows NT. + # That is not the case for the wheel module though (which does allow setting plat_name), + # so we circumvent by faking the os name when finalizing the options, and then restoring + # the original os name. + os.name = "nt" + + _build.finalize_options(self) + + if sys.platform == 'darwin': + os.name = os_name_backup + def initialize_options(self): _build.initialize_options(self) self.make_path = None @@ -871,6 +907,26 @@ class pyside_build(_build): log.info("Skipped preparing and building packages.") log.info('*** Build completed') + @staticmethod + def macos_min_deployment_target(): + # If no explicit minimum deployment target is provided to setup.py, then use the current + # build OS version. Otherwise use the provided version. + current_os_version, _, _ = platform.mac_ver() + current_os_version = '.'.join(current_os_version.split('.')[:2]) + deployment_target = current_os_version + if OPTION_OSX_DEPLOYMENT_TARGET: + deployment_target = OPTION_OSX_DEPLOYMENT_TARGET + + return deployment_target + + @staticmethod + def macos_plat_name(): + deployment_target = pyside_build.macos_min_deployment_target() + # Example triple "macosx-10.12-x86_64". + plat = get_platform().split("-") + plat_name = "{}-{}-{}".format(plat[0], deployment_target, plat[2]) + return plat_name + def build_patchelf(self): if not sys.platform.startswith('linux'): return @@ -977,19 +1033,13 @@ class pyside_build(_build): latest_sdk_path = latest_sdk_path[0] cmake_cmd.append("-DCMAKE_OSX_SYSROOT={0}".format(latest_sdk_path)) - # If no explicit minimum deployment target is set, then use the current build OS - # version. Otherwise use the given version. + # Set macOS minimum deployment target (version). # This is required so that calling run_process -> distutils.spawn() does not - # set its own minimum deployment target environment variable, + # set its own minimum deployment target environment variable which is # based on the python interpreter sysconfig value. Doing so could break the # detected clang include paths for example. - current_os_version, _, _ = platform.mac_ver() - current_os_version = '.'.join(current_os_version.split('.')[:2]) - deployment_target = current_os_version - if OPTION_OSX_DEPLOYMENT_TARGET: - cmake_cmd.append("-DCMAKE_OSX_DEPLOYMENT_TARGET={0}" - .format(OPTION_OSX_DEPLOYMENT_TARGET)) - deployment_target = OPTION_OSX_DEPLOYMENT_TARGET + deployment_target = pyside_build.macos_min_deployment_target() + cmake_cmd.append("-DCMAKE_OSX_DEPLOYMENT_TARGET={0}".format(deployment_target)) os.environ['MACOSX_DEPLOYMENT_TARGET'] = deployment_target if not OPTION_SKIP_CMAKE: @@ -1587,6 +1637,18 @@ except IOError: README = CHANGES = '' +cmd_class_dict = { + 'build': pyside_build, + 'build_py': pyside_build_py, + 'build_ext': pyside_build_ext, + 'bdist_egg': pyside_bdist_egg, + 'develop': pyside_develop, + 'install': pyside_install, + 'install_lib': pyside_install_lib +} +if wheel_module_exists: + cmd_class_dict['bdist_wheel'] = pyside_build_wheel + setup( name = "PySide2", version = __version__, @@ -1639,16 +1701,7 @@ setup( 'pyside2-uic = PySide2.scripts.uic:main', ] }, - cmdclass = { - 'build': pyside_build, - 'build_py': pyside_build_py, - 'build_ext': pyside_build_ext, - 'bdist_egg': pyside_bdist_egg, - 'develop': pyside_develop, - 'install': pyside_install, - 'install_lib': pyside_install_lib - }, - + cmdclass = cmd_class_dict, # Add a bogus extension module (will never be built here since we are # overriding the build command to do it using cmake) so things like # bdist_egg will know that there are extension modules and will name the -- cgit v1.2.3 From 011b6ad1987f0dee3035e9803ece9fb2c1a0a1b2 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 21 Feb 2018 15:07:54 +0100 Subject: Fix setup.py install to work on macOS This change amends e0a4372160d9b1ab1550589784cc456b93d28cab to allow running setup.py install without getting exceptions from distutils. Specifically the problem was that distutils did some additional checks to make sure that plat_name == get_platform(), which will not be the case because of the modified minimum deployment target. The fix is to disable that check via a flag. Change-Id: I066c0d4b7a6b27fceaf094c15bace7aba3c873bb Reviewed-by: Friedemann Kleint --- setup.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'setup.py') diff --git a/setup.py b/setup.py index fc9b49255..523cb9a0d 100644 --- a/setup.py +++ b/setup.py @@ -500,6 +500,22 @@ class pyside_install(_install): def _init(self, *args, **kwargs): _install.__init__(self, *args, **kwargs) + def initialize_options (self): + _install.initialize_options(self) + + if sys.platform == 'darwin': + # Because we change the plat_name to include a correct deployment target on macOS + # distutils thinks we are cross-compiling, and throws an exception when trying to + # execute setup.py install. + # The check looks like this + #if self.warn_dir and build_plat != get_platform(): + # raise DistutilsPlatformError("Can't install when " + # "cross-compiling") + # Obviously get_platform will return the old deployment target. + # The fix is to disable the warn_dir flag, which was created for bdist_* derived classes + # to override, for similar cases. + self.warn_dir = False + def run(self): _install.run(self) log.info('*** Install completed') -- cgit v1.2.3 From 6c6991732355184698abeb12994149fe8b16727e Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 21 Feb 2018 18:15:10 +0100 Subject: Don't copy duplicate libraries on package installation This change makes sure to resolve symlinks and copy only one file for every dynamic shared library used (vs 3 before -> 2 symlinks + 1 actual library). This avoids duplicate files, as well as saves space when creating wheel (e.g. no 3 copies of standalone WebEngine library). This filtering happens copying from CMake install dir into pyside_package_dir (the symlinks are still present in the CMake dir, but that shouldn't harm anybody, and might be useful for users of shiboken only for example). Task-number: PYSIDE-495 Change-Id: I0fe454e16c6b254a8682aa7b1c702ec01a3064f0 Reviewed-by: Friedemann Kleint --- setup.py | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) (limited to 'setup.py') diff --git a/setup.py b/setup.py index 523cb9a0d..a07f5f758 100644 --- a/setup.py +++ b/setup.py @@ -1129,29 +1129,27 @@ class pyside_build(_build): print('setup.py/prepare_packages: ', e) raise - def get_built_pyside_modules(self, vars): - # Get list of built modules, so that we copy only required Qt libraries. + def get_built_pyside_config(self, vars): + # Get config that contains list of built modules, and SOVERSIONs of the built libraries. pyside_package_dir = vars['pyside_package_dir'] - built_modules_path = os.path.join(pyside_package_dir, "PySide2", "_built_modules.py") + config_path = os.path.join(pyside_package_dir, "PySide2", "_config.py") try: - with open(built_modules_path) as f: + with open(config_path) as f: scoped_locals = {} - code = compile(f.read(), built_modules_path, 'exec') + code = compile(f.read(), config_path, 'exec') exec(code, scoped_locals, scoped_locals) - return scoped_locals['built_modules'] + config = {} + config['built_modules'] = scoped_locals['built_modules'] + config['shiboken_library_soversion'] = scoped_locals['shiboken_library_soversion'] + config['pyside_library_soversion'] = scoped_locals['pyside_library_soversion'] + return config except IOError as e: - print("get_built_pyside_modules: Couldn't find file: {}.".format(built_modules_path)) + print("get_built_pyside_config: Couldn't find file: {}.".format(config_path)) raise def prepare_packages_posix(self, vars): executables = [] - if sys.platform.startswith('linux'): - so_ext = '.so' - so_star = so_ext + '*' - elif sys.platform == 'darwin': - so_ext = '.dylib' - so_star = so_ext # /shiboken2/doc/html/* -> /PySide2/docs/shiboken2 copydir( "{build_dir}/shiboken2/doc/html", @@ -1202,14 +1200,22 @@ class pyside_build(_build): ], recursive=False, vars=vars)) # /lib/lib* -> PySide2/ + config = self.get_built_pyside_config(vars) + def adjusted_lib_name(name, version): + postfix = '' + if sys.platform.startswith('linux'): + postfix = '.so.' + version + elif sys.platform == 'darwin': + postfix = '.' + version + '.dylib' + return name + postfix copydir( "{install_dir}/lib/", "{pyside_package_dir}/PySide2", filter=[ - "libpyside*" + so_star, - "libshiboken*" + so_star, + adjusted_lib_name("libpyside*", config['pyside_library_soversion']), + adjusted_lib_name("libshiboken*", config['shiboken_library_soversion']), ], - recursive=False, vars=vars) + recursive=False, vars=vars, force_copy_symlinks=True) # /share/PySide2/typesystems/* -> /PySide2/typesystems copydir( "{install_dir}/share/PySide2/typesystems", @@ -1241,7 +1247,7 @@ class pyside_build(_build): pyside_rcc_options) # Copy Qt libs to package if OPTION_STANDALONE: - vars['built_modules'] = self.get_built_pyside_modules(vars) + vars['built_modules'] = config['built_modules'] if sys.platform == 'darwin': self.prepare_standalone_package_osx(executables, vars) else: @@ -1358,13 +1364,15 @@ class pyside_build(_build): else: ignored_modules = [] if 'WebEngineWidgets' not in built_modules: - ignored_modules.extend(['*Qt5WebEngine*.dylib']) - accepted_modules = ['*Qt5*.dylib'] + ignored_modules.extend(['libQt5WebEngine*.dylib']) + if 'WebKit' not in built_modules: + ignored_modules.extend(['libQt5WebKit*.dylib']) + accepted_modules = ['libQt5*.5.dylib'] copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2/Qt/lib", filter=accepted_modules, ignore=ignored_modules, - recursive=True, vars=vars) + recursive=True, vars=vars, force_copy_symlinks=True) if 'WebEngineWidgets' in built_modules: copydir("{qt_lib_execs_dir}", "{pyside_package_dir}/PySide2/Qt/libexec", @@ -1405,7 +1413,7 @@ class pyside_build(_build): "{site_packages_dir}/PySide2", "{pyside_package_dir}/PySide2", vars=vars) - built_modules = self.get_built_pyside_modules(vars) + built_modules = self.get_built_pyside_config(vars)['built_modules'] if self.debug or self.build_type == 'RelWithDebInfo': # /pyside2/PySide2/*.pdb -> /PySide2 -- cgit v1.2.3 From 910759562818542c6e1f2d134d783807b45e09da Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 20 Feb 2018 17:24:55 +0100 Subject: Make sure wheel names contain both PySide2 and Qt version Task-number: PYSIDE-613 Change-Id: Iad411a46618d157b17f62b403591f348acd98a4e Reviewed-by: Christian Tismer --- setup.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) (limited to 'setup.py') diff --git a/setup.py b/setup.py index a07f5f758..abb53517a 100644 --- a/setup.py +++ b/setup.py @@ -214,6 +214,7 @@ from setuptools.command.build_py import build_py as _build_py wheel_module_exists = False try: from wheel.bdist_wheel import bdist_wheel as _bdist_wheel + from wheel.bdist_wheel import safer_name as _safer_name wheel_module_exists = True except ImportError: pass @@ -496,8 +497,22 @@ def prepareBuild(): global qtSrcDir qtSrcDir = qmakeOutput[0].rstrip() +def get_qt_version(computed_qtinfo = None): + if not computed_qtinfo: + qtinfo = QtInfo(QMAKE_COMMAND) + else: + qtinfo = computed_qtinfo + + qt_version = qtinfo.version + + if not qt_version: + log.error("Failed to query the Qt version with qmake %s" % self.qtinfo.qmake_command) + sys.exit(1) + + return qt_version + class pyside_install(_install): - def _init(self, *args, **kwargs): + def __init__(self, *args, **kwargs): _install.__init__(self, *args, **kwargs) def initialize_options (self): @@ -551,6 +566,20 @@ if wheel_module_exists: def __init__(self, *args, **kwargs): _bdist_wheel.__init__(self, *args, **kwargs) + @property + def wheel_dist_name(self): + # Slightly modified version of wheel's wheel_dist_name method, to add the Qt version + # as well. + # Example: PySide2-5.6-5.6.4-cp27-cp27m-macosx_10_10_intel.whl + # The PySide2 version is "5.6. The built against Qt version is "5.6.4. + qt_version = get_qt_version() + wheel_version = "{}-{}".format(__version__, qt_version) + components = (_safer_name(self.distribution.get_name()), + wheel_version) + if self.build_number: + components += (self.build_number,) + return '-'.join(components) + def finalize_options(self): if sys.platform == 'darwin': # Override the platform name to contain the correct minimum deployment target. @@ -792,10 +821,7 @@ class pyside_build(_build): self.qtinfo = QtInfo(QMAKE_COMMAND) qt_dir = os.path.dirname(OPTION_QMAKE) - qt_version = self.qtinfo.version - if not qt_version: - log.error("Failed to query the Qt version with qmake %s" % self.qtinfo.qmake_command) - sys.exit(1) + qt_version = get_qt_version(self.qtinfo) # Update the PATH environment variable update_env_path([py_scripts_dir, qt_dir]) -- cgit v1.2.3 From ed9f21a2ccafdee0e96493b99a249d88d5c4f21a Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 23 Feb 2018 14:02:57 +0100 Subject: Deprecate --iculib-url option The option is not being used anymore since commit c605d686f8cc4c8d370ec4d6260fca4b423f5526 , but we need to keep the name around, so that setup.py doesn't error out in Coin because we still pass that option. Change-Id: I6f3433d1feacd8940aa48ba7e1113fe44a19ed9b Reviewed-by: Friedemann Kleint --- setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'setup.py') diff --git a/setup.py b/setup.py index abb53517a..8a8970c68 100644 --- a/setup.py +++ b/setup.py @@ -294,7 +294,7 @@ OPTION_SKIP_MAKE_INSTALL = has_option("skip-make-install") OPTION_SKIP_PACKAGING = has_option("skip-packaging") OPTION_RPATH_VALUES = option_value("rpath") OPTION_QT_CONF_PREFIX = option_value("qt-conf-prefix") -OPTION_ICULIB = option_value("iculib-url") +OPTION_ICULIB = option_value("iculib-url") # Deprecated OPTION_VERBOSE_BUILD = has_option("verbose-build") # This is used automatically by distutils.command.install object, to specify final installation @@ -364,11 +364,7 @@ else: if OPTION_ICULIB: if not OPTION_STANDALONE: - print("--iculib-url is usable only when creating standalone wheel with --standalone switch") - sys.exit(1) - if sys.platform != "linux": - print("--iculib-url is usable only when creating standalone wheels in Linux") - sys.exit(1) + print("--iculib-url option is a no-op option and will be removed soon.") # Show available versions if OPTION_LISTVERSIONS: -- cgit v1.2.3