From 9990d41284e2e4df482daf67533226c52f1b9765 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Mon, 27 Jan 2020 11:32:55 +0800 Subject: [PATCH 1/3] fix up osx compiles --- osxmakebundle.sh | 21 -- ozw-admin.pri | 8 +- ozwadmin-main/ozwadmin-main.pro | 5 +- scripts/macdeployqtfix.py | 395 ++++++++++++++++++++++++++++++++ 4 files changed, 406 insertions(+), 23 deletions(-) delete mode 100755 osxmakebundle.sh create mode 100755 scripts/macdeployqtfix.py diff --git a/osxmakebundle.sh b/osxmakebundle.sh deleted file mode 100755 index e52fea9..0000000 --- a/osxmakebundle.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -if [ $# -lt 3 ]; then - echo "Please Provide the path to the App Bundle and OZW Config Directory and QT Directories" - exit 1 -fi -if [ ! -f $1/Contents/Frameworks/libqt-openzwave.1.dylib ]; then - echo "$1/Contents/Frameworks/libqt-openzwave.1.dylib doens't exist" - exit 1 -fi -if [ ! -f $2/manufacturer_specific.xml ]; then - echo "$2/manufacturer_specific.xml doesn't exist" - exit 1 -fi -if [ ! -f $3/bin/macdeployqt ]; then - echo "$3/bin/macdeployqt does't exist" - exit 1 -fi -PATH=`otool -L $1/Contents/Frameworks/libqt-openzwave.1.dylib | grep libopenzwave | awk '{print $1}'` -/usr/bin/install_name_tool -change $PATH "@rpath/${PATH##*/}" $1/Contents/Frameworks/libqt-openzwave.1.dylib -/bin/cp -r $2 $1/Contents/Resources/config/ -echo "To Finish Please Run '$3/bin/macdeployqt $1 -verbose=2 -always-overwrite'" diff --git a/ozw-admin.pri b/ozw-admin.pri index bf1d17d..de6b153 100644 --- a/ozw-admin.pri +++ b/ozw-admin.pri @@ -1,5 +1,11 @@ top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) +unix { + macx { + QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 + } + !macx: QMAKE_CXXFLAGS += -Wno-deprecated-copy +} unix { CONFIG-= no-pkg-config @@ -100,7 +106,7 @@ unix { QTOZW_INCLUDE_PATH += $$absolute_path($$top_srcdir/../qt-openzwave/qt-openzwavedatabase/include/) QTOZW_LIBS="-L$$QTOZW_LIB_PATH" QTOZW_LIBS+="-lqt-openzwave" - QTOZW_LIBS+="-L$$absolute_path($$OTOZW_LIB_PATH/../qt-openzwavedatabase/)" + QTOZW_LIBS+="-L$$absolute_path($$QTOZW_LIB_PATH/../qt-openzwavedatabase/)" QTZOW_LIBS+="-lqt-openzwavedatabase" message(" ") message("QT-OpenZWave Summary:") diff --git a/ozwadmin-main/ozwadmin-main.pro b/ozwadmin-main/ozwadmin-main.pro index 86d89b2..369ffd4 100644 --- a/ozwadmin-main/ozwadmin-main.pro +++ b/ozwadmin-main/ozwadmin-main.pro @@ -54,9 +54,12 @@ INCLUDEPATH += ../devicedb-lib ../ozwadmin-widgets macx: { LIBS += -framework IOKit -framework CoreFoundation - BUNDLE.files = ../../qt-openzwave/qt-openzwave/libqt-openzwave.1.dylib ../../open-zwave/libopenzwave-1.6.dylib ../../qt-openzwave/qt-openzwavedatabase/libqt-openzwavedatabase.1.dylib + BUNDLE.files = $$OZW_LIB_PATH/libopenzwave-1.6.dylib $$QTOZW_LIB_PATH/libqt-openzwave.1.dylib $$QTOZW_LIB_PATH/../qt-openzwavedatabase/libqt-openzwavedatabase.1.dylib BUNDLE.path = Contents/Frameworks/ QMAKE_BUNDLE_DATA += BUNDLE + MakeBundle.commands = $$[QT_HOST_BINS]/macdeployqt ../ozwadmin.app && $$top_srcdir/scripts/macdeployqtfix.py ../ozwadmin.app/Contents/MacOS/ozwadmin $$[QT_INSTALL_PREFIX] + QMAKE_EXTRA_TARGETS += MakeBundle + QMAKE_POST_LINK += $$MakeBundle.commands ICON = res/ozw_logo.icns } diff --git a/scripts/macdeployqtfix.py b/scripts/macdeployqtfix.py new file mode 100755 index 0000000..1189c43 --- /dev/null +++ b/scripts/macdeployqtfix.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +""" +finish the job started by macdeployqtfix +""" + +from subprocess import Popen, PIPE +from string import Template +import os +import sys +import logging +import argparse +import re +from collections import namedtuple + + +QTLIB_NAME_REGEX = r'^(?:@executable_path)?/.*/(Qt[a-zA-Z]*).framework/(?:Versions/\d/)?\1$' +QTLIB_NORMALIZED = r'$prefix/Frameworks/$qtlib.framework/Versions/$qtversion/$qtlib' + +QTPLUGIN_NAME_REGEX = r'^(?:@executable_path)?/.*/[pP]lug[iI]ns/(.*)/(.*).dylib$' +QTPLUGIN_NORMALIZED = r'$prefix/PlugIns/$plugintype/$pluginname.dylib' + +#BREWLIB_REGEX = r'^//usr/local/.*//(.*)' +BREWLIB_REGEX =r'(libqt-.*)' +BREWLIB_NORMALIZED = r'$prefix/Frameworks/$brewlib' + + +class GlobalConfig(object): + logger = None + qtpath = None + exepath = None + + +def run_and_get_output(popen_args): + """Run process and get all output""" + process_output = namedtuple('ProcessOutput', ['stdout', 'stderr', 'retcode']) + try: + GlobalConfig.logger.debug('run_and_get_output({0})'.format(repr(popen_args))) + + proc = Popen(popen_args, stdin=PIPE, stdout=PIPE, stderr=PIPE) + stdout, stderr = proc.communicate(b'') + proc_out = process_output(stdout, stderr, proc.returncode) + + GlobalConfig.logger.debug('\tprocess_output: {0}'.format(proc_out)) + return proc_out + except Exception as exc: + GlobalConfig.logger.error('\texception: {0}'.format(exc)) + return process_output('', exc.message, -1) + + +def get_dependencies(filename): + """ + input: filename must be an absolute path + Should call `otool` and returns the list of dependencies, unsorted, + unmodified, just the raw list so then we could eventually re-use in other + more specialized functions + """ + GlobalConfig.logger.debug('get_dependencies({0})'.format(filename)) + popen_args = ['otool', '-L', filename] + proc_out = run_and_get_output(popen_args) + deps = [] + if proc_out.retcode == 0: + # some string splitting + deps = [s.strip().split(' ')[0] for s in proc_out.stdout.splitlines()[1:] if s] + # prevent infinite recursion when a binary depends on itself (seen with QtWidgets)... + deps = [s for s in deps if os.path.basename(filename) not in s] + return deps + + +def is_qt_plugin(filename): + """ + Checks if a given file is a qt plugin. + Accepts absolute path as well as path containing @executable_path + """ + qtlib_name_rgx = re.compile(QTPLUGIN_NAME_REGEX) + return qtlib_name_rgx.match(filename) is not None + + +def is_qt_lib(filename): + """ + Checks if a given file is a qt library. + Accepts absolute path as well as path containing @executable_path + """ + qtlib_name_rgx = re.compile(QTLIB_NAME_REGEX) + return qtlib_name_rgx.match(filename) is not None + + +def is_brew_lib(filename): + """ + Checks if a given file is a brew library + Accepts absolute path as well as path containing @executable_path + """ + qtlib_name_rgx = re.compile(BREWLIB_REGEX) + return qtlib_name_rgx.match(filename) is not None + + +def normalize_qtplugin_name(filename): + """ + input: a path to a qt plugin, as returned by otool, that can have this form : + - an absolute path /../plugins/PLUGINTYPE/PLUGINNAME.dylib + - @executable_path/../plugins/PLUGINTYPE/PLUGINNAME.dylib + output: + a tuple (qtlib, abspath, rpath) where: + - qtname is the name of the plugin (libqcocoa.dylib, etc.) + - abspath is the absolute path of the qt lib inside the app bundle of exepath + - relpath is the correct rpath to a qt lib inside the app bundle + """ + + GlobalConfig.logger.debug('normalize_plugin_name({0})'.format(filename)) + + qtplugin_name_rgx = re.compile(QTPLUGIN_NAME_REGEX) + rgxret = qtplugin_name_rgx.match(filename) + if not rgxret: + msg = 'couldn\'t normalize a non-qt plugin filename: {0}'.format(filename) + GlobalConfig.logger.critical(msg) + raise Exception(msg) + + # qtplugin normalization settings + qtplugintype = rgxret.groups()[0] + qtpluginname = rgxret.groups()[1] + + templ = Template(QTPLUGIN_NORMALIZED) + + # from qtlib, forge 2 path : + # - absolute path of qt lib in bundle, + abspath = os.path.normpath(templ.safe_substitute( + prefix=os.path.dirname(GlobalConfig.exepath) + '/..', + plugintype=qtplugintype, + pluginname=qtpluginname)) + + # - and rpath containing @executable_path, relative to exepath + rpath = templ.safe_substitute( + prefix='@executable_path/..', + plugintype=qtplugintype, + pluginname=qtpluginname) + + GlobalConfig.logger.debug('\treturns({0})'.format((qtpluginname, abspath, rpath))) + return qtpluginname, abspath, rpath + + +def normalize_qtlib_name(filename): + """ + input: a path to a qt library, as returned by otool, that can have this form : + - an absolute path /lib/xxx/yyy + - @executable_path/../Frameworks/QtSerialPort.framework/Versions/5/QtSerialPort + output: + a tuple (qtlib, abspath, rpath) where: + - qtlib is the name of the qtlib (QtCore, QtWidgets, etc.) + - abspath is the absolute path of the qt lib inside the app bundle of exepath + - relpath is the correct rpath to a qt lib inside the app bundle + """ + GlobalConfig.logger.debug('normalize_qtlib_name({0})'.format(filename)) + + qtlib_name_rgx = re.compile(QTLIB_NAME_REGEX) + rgxret = qtlib_name_rgx.match(filename) + if not rgxret: + msg = 'couldn\'t normalize a non-qt lib filename: {0}'.format(filename) + GlobalConfig.logger.critical(msg) + raise Exception(msg) + + # qtlib normalization settings + qtlib = rgxret.groups()[0] + qtversion = 5 + + templ = Template(QTLIB_NORMALIZED) + + # from qtlib, forge 2 path : + # - absolute path of qt lib in bundle, + abspath = os.path.normpath(templ.safe_substitute( + prefix=os.path.dirname(GlobalConfig.exepath) + '/..', + qtlib=qtlib, + qtversion=qtversion)) + + # - and rpath containing @executable_path, relative to exepath + rpath = templ.safe_substitute( + prefix='@executable_path/..', + qtlib=qtlib, + qtversion=qtversion) + + GlobalConfig.logger.debug('\treturns({0})'.format((qtlib, abspath, rpath))) + return qtlib, abspath, rpath + + +def normalize_brew_name(filename): + """ + input: a path to a brew library, as returned by otool, that can have this form : + - an absolute path /usr/local/lib/yyy + output: + a tuple (brewlib, abspath, rpath) where: + - brewlib is the name of the brew lib + - abspath is the absolute path of the qt lib inside the app bundle of exepath + - relpath is the correct rpath to a qt lib inside the app bundle + """ + GlobalConfig.logger.debug('normalize_brew_name({0})'.format(filename)) + + brewlib_name_rgx = re.compile(BREWLIB_REGEX) + rgxret = brewlib_name_rgx.match(filename) + if not rgxret: + msg = 'couldn\'t normalize a brew lib filename: {0}'.format(filename) + GlobalConfig.logger.critical(msg) + raise Exception(msg) + + # brewlib normalization settings + brewlib = rgxret.groups()[0] + templ = Template(BREWLIB_NORMALIZED) + + # from brewlib, forge 2 path : + # - absolute path of qt lib in bundle, + abspath = os.path.normpath(templ.safe_substitute( + prefix=os.path.dirname(GlobalConfig.exepath) + '/..', + brewlib=brewlib)) + + # - and rpath containing @executable_path, relative to exepath + rpath = templ.safe_substitute( + prefix='@executable_path/..', + brewlib=brewlib) + + GlobalConfig.logger.debug('\treturns({0})'.format((brewlib, abspath, rpath))) + return brewlib, abspath, rpath + + +def fix_dependency(binary, dep): + """ + fix 'dep' dependency of 'binary'. 'dep' is a qt library + """ + if is_qt_lib(dep): + qtname, dep_abspath, dep_rpath = normalize_qtlib_name(dep) + elif is_qt_plugin(dep): + qtname, dep_abspath, dep_rpath = normalize_qtplugin_name(dep) + elif is_brew_lib(dep): + qtname, dep_abspath, dep_rpath = normalize_brew_name(dep) + else: + return True + + dep_ok = True + # check that rpath of 'dep' inside binary has been correctly set + # (ie: relative to exepath using '@executable_path' syntax) + if dep != dep_rpath: + # dep rpath is not ok + GlobalConfig.logger.info('changing rpath \'{0}\' in binary {1}'.format(dep, binary)) + + # call install_name_tool -change on binary + popen_args = ['install_name_tool', '-change', dep, dep_rpath, binary] + proc_out = run_and_get_output(popen_args) + if proc_out.retcode != 0: + GlobalConfig.logger.error(proc_out.stderr) + dep_ok = False + else: + # call install_name_tool -id on binary + popen_args = ['install_name_tool', '-id', dep_rpath, binary] + proc_out = run_and_get_output(popen_args) + if proc_out.retcode != 0: + GlobalConfig.logger.error(proc_out.stderr) + dep_ok = False + + # now ensure that 'dep' exists at the specified path, relative to bundle + if dep_ok and not os.path.exists(dep_abspath): + + # ensure destination directory exists + GlobalConfig.logger.info('ensuring directory \'{0}\' exists: {0}'. + format(os.path.dirname(dep_abspath))) + popen_args = ['mkdir', '-p', os.path.dirname(dep_abspath)] + proc_out = run_and_get_output(popen_args) + if proc_out.retcode != 0: + GlobalConfig.logger.info(proc_out.stderr) + dep_ok = False + else: + # copy missing dependency into bundle + qtnamesrc = os.path.join(GlobalConfig.qtpath, 'lib', '{0}.framework'. + format(qtname), qtname) + GlobalConfig.logger.info('copying missing dependency in bundle: {0}'. + format(qtname)) + popen_args = ['cp', qtnamesrc, dep_abspath] + proc_out = run_and_get_output(popen_args) + if proc_out.retcode != 0: + GlobalConfig.logger.info(proc_out.stderr) + dep_ok = False + else: + # ensure permissions are correct if we ever have to change its rpath + GlobalConfig.logger.info('ensuring 755 perm to {0}'.format(dep_abspath)) + popen_args = ['chmod', '755', dep_abspath] + proc_out = run_and_get_output(popen_args) + if proc_out.retcode != 0: + GlobalConfig.logger.info(proc_out.stderr) + dep_ok = False + else: + GlobalConfig.logger.debug('{0} is at correct location in bundle'.format(qtname)) + + if dep_ok: + return fix_binary(dep_abspath) + return False + + +def fix_binary(binary): + """ + input: + binary: relative or absolute path (no @executable_path syntax) + process: + - first fix the rpath for the qt libs on which 'binary' depend + - copy into the bundle of exepath the eventual libraries that are missing + - (create the soft links) needed ? + - do the same for all qt dependencies of binary (recursive) + """ + GlobalConfig.logger.debug('fix_binary({0})'.format(binary)) + + # loop on 'binary' dependencies + for dep in get_dependencies(binary): + if not fix_dependency(binary, dep): + GlobalConfig.logger.error('quitting early: couldn\'t fix dependency {0} of {1}'.format(dep, binary)) + return False + return True + + +def fix_main_binaries(): + """ + list the main binaries of the app bundle and fix them + """ + # deduce bundle path + bundlepath = os.path.sep.join(GlobalConfig.exepath.split(os.path.sep)[0:-3]) + + # fix main binary + GlobalConfig.logger.info('fixing executable \'{0}\''.format(GlobalConfig.exepath)) + if fix_binary(GlobalConfig.exepath): + GlobalConfig.logger.info('fixing plugins') + for root, dummy, files in os.walk(bundlepath): + for name in [f for f in files if os.path.splitext(f)[1] == '.dylib']: + GlobalConfig.logger.info('fixing plugin {0}'.format(name)) + if not fix_binary(os.path.join(root, name)): + return False + return True + + +def main(): + descr = """finish the job started by macdeployqt! + - find dependencies/rpaths with otool + - copy missed dependencies with cp and mkdir + - fix missed rpaths with install_name_tool + + exit codes: + - 0 : success + - 1 : error + """ + + parser = argparse.ArgumentParser(description=descr, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('exepath', + help='path to the binary depending on Qt') + parser.add_argument('qtpath', + help='path of Qt libraries used to build the Qt application') + parser.add_argument('-q', '--quiet', action='store_true', default=False, + help='do not create log on standard output') + parser.add_argument('-nl', '--no-log-file', action='store_true', default=False, + help='do not create log file \'./macdeployqtfix.log\'') + parser.add_argument('-v', '--verbose', action='store_true', default=False, + help='produce more log messages(debug log)') + args = parser.parse_args() + + # globals + GlobalConfig.qtpath = os.path.normpath(args.qtpath) + GlobalConfig.exepath = args.exepath + GlobalConfig.logger = logging.getLogger() + + # configure logging + ################### + + # create formatter + formatter = logging.Formatter('%(levelname)s | %(message)s') + # create console GlobalConfig.logger + if not args.quiet: + chdlr = logging.StreamHandler(sys.stdout) + chdlr.setFormatter(formatter) + GlobalConfig.logger.addHandler(chdlr) + + # create file GlobalConfig.logger + if not args.no_log_file: + fhdlr = logging.FileHandler('./macdeployqtfix.log', mode='w') + fhdlr.setFormatter(formatter) + GlobalConfig.logger.addHandler(fhdlr) + + if args.no_log_file and args.quiet: + GlobalConfig.logger.addHandler(logging.NullHandler()) + else: + GlobalConfig.logger.setLevel(logging.DEBUG if args.verbose else logging.INFO) + + if fix_main_binaries(): + GlobalConfig.logger.info('macdeployqtfix terminated with success') + ret = 0 + else: + GlobalConfig.logger.error('macdeployqtfix terminated with error') + ret = 1 + sys.exit(ret) + + +if __name__ == "__main__": + main() From bb2e7b989a6d35def9d5fb01df394ac4ce2d201a Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Mon, 27 Jan 2020 17:44:41 +0800 Subject: [PATCH 2/3] Update Python executable name --- scripts/macdeployqtfix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/macdeployqtfix.py b/scripts/macdeployqtfix.py index 1189c43..a37be84 100755 --- a/scripts/macdeployqtfix.py +++ b/scripts/macdeployqtfix.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python # -*- coding: utf-8 -*- """ finish the job started by macdeployqtfix From 2d9785809e3432d1b53f850e3f5f8d93348da563 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Mon, 27 Jan 2020 19:31:54 +0800 Subject: [PATCH 3/3] Fix Windows Builds --- ozw-admin.pri | 2 ++ scripts/win32build.bat | 1 + 2 files changed, 3 insertions(+) diff --git a/ozw-admin.pri b/ozw-admin.pri index de6b153..3dbc738 100644 --- a/ozw-admin.pri +++ b/ozw-admin.pri @@ -144,6 +144,8 @@ win32 { exists( $$top_srcdir/../open-zwave/cpp/src/) { message("Found OZW in $$absolute_path($$top_srcdir/../open-zwave/cpp/src)") INCLUDEPATH += $$absolute_path($$top_srcdir/../open-zwave/cpp/src/)/ + INCLUDEPATH += $$absolute_path($$top_srcdir/../qt-openzwave/qt-openzwave/include/) + INCLUDEPATH += $$absolute_path($$top_srcdir/../qt-openzwave/qt-openzwavedatabase/include/) equals(BUILDTYPE, "release") { exists( $$absolute_path($$top_srcdir/../open-zwave/cpp/build/windows/vs2010/ReleaseDLL/OpenZWave.dll ) ) { LIBS += -L$$absolute_path($$top_srcdir/../open-zwave/cpp/build/windows/vs2010/ReleaseDLL) -lopenzwave diff --git a/scripts/win32build.bat b/scripts/win32build.bat index 9a9fd89..3049fb1 100644 --- a/scripts/win32build.bat +++ b/scripts/win32build.bat @@ -1,6 +1,7 @@ @echo off cd ..\open-zwave msbuild /p:Configuration=ReleaseDLL /p:Platform=Win32 cpp\build\windows\vs2010\OpenZWave.sln +msbuild /p:Configuration=DebugDLL /p:Platform=Win32 cpp\build\windows\vs2010\OpenZWave.sln cd ..\qt-openzwave qmake -r -tp vc msbuild /p:Configuration=Release /p:Platform=Win32 qt-openzwave.sln