From 1a2fff8ed8a2fb510d371c8c882be2e003e93a76 Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Tue, 30 Sep 2014 17:08:29 +0200 Subject: [PATCH] OS X: Use a python script to fix for bundle fixup... ...rather than relying on cmake magic --- admin/osx/macdeployqt.py | 322 +++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 22 ++- 2 files changed, 332 insertions(+), 12 deletions(-) create mode 100755 admin/osx/macdeployqt.py diff --git a/admin/osx/macdeployqt.py b/admin/osx/macdeployqt.py new file mode 100755 index 0000000000..5935a2fd81 --- /dev/null +++ b/admin/osx/macdeployqt.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# This file is part of ownCloud. +# It was inspired in large part by the macdeploy script in Clementine +# and Tomahawk +# +# ownCloud is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# ownCLoud is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ownCloud. If not, see . + +import os +import re +import subprocess +import commands +import sys +from glob import glob + +def QueryQMake(attrib): + return subprocess.check_output(['qmake', '-query', attrib]).rstrip('\n') + +FRAMEWORK_SEARCH_PATH=[ + '/Library/Frameworks', + os.path.join(os.environ['HOME'], 'Library/Frameworks') +] + +LIBRARY_SEARCH_PATH=['/usr/local/lib', '.'] + +QT_PLUGINS = [ + 'accessible/libqtaccessiblewidgets.dylib', + 'sqldrivers/libqsqlite.dylib', + 'platforms/libqcocoa.dylib', + 'imageformats/libqgif.dylib', + 'imageformats/libqico.dylib', + 'imageformats/libqjpeg.dylib', + 'imageformats/libqsvg.dylib', + 'imageformats/libqmng.dylib', +] + +QT_PLUGINS_SEARCH_PATH=[ +# os.path.join(os.environ['QTDIR'], 'plugins'), + '/usr/local/Cellar/qt/5.2.1/plugins', +] + + +class Error(Exception): + pass + + +class CouldNotFindQtPluginErrorFindFrameworkError(Error): + pass + + +class InstallNameToolError(Error): + pass + + +class CouldNotFindQtPluginError(Error): + pass + + +class CouldNotFindScriptPluginError(Error): + pass + +if len(sys.argv) < 2: + print 'Usage: %s ' % sys.argv[0] + exit() + +def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +bundle_dir = sys.argv[1] + +bundle_name = os.path.basename(bundle_dir).split('.')[0] + +commands = [] + +binary_dir = os.path.join(bundle_dir, 'Contents', 'MacOS') +frameworks_dir = os.path.join(bundle_dir, 'Contents', 'Frameworks') +commands.append(['mkdir', '-p', frameworks_dir]) +resources_dir = os.path.join(bundle_dir, 'Contents', 'Resources') +commands.append(['mkdir', '-p', resources_dir]) +plugins_dir = os.path.join(bundle_dir, 'Contents', 'PlugIns') +binaries = [i for i in glob(os.path.join(bundle_dir, 'Contents', 'MacOS', "*")) if is_exe(i)]; + + +fixed_libraries = [] +fixed_frameworks = [] + +def GetBrokenLibraries(binary): + #print "Checking libs for binary: %s" % binary + output = subprocess.Popen(['otool', '-L', binary], stdout=subprocess.PIPE).communicate()[0] + broken_libs = { + 'frameworks': [], + 'libs': []} + for line in [x.split(' ')[0].lstrip() for x in output.split('\n')[1:]]: + #print "Checking line: %s" % line + if not line: # skip empty lines + continue + if os.path.basename(binary) == os.path.basename(line): + #print "mnope %s-%s" % (os.path.basename(binary), os.path.basename(line)) + continue + if re.match(r'^\s*/System/', line): + continue # System framework + elif re.match(r'^\s*/usr/lib/', line): + #print "unix style system lib" + continue # unix style system library + elif re.match(r'Breakpad', line): + continue # Manually added by cmake. + elif re.match(r'^\s*@executable_path', line) or re.match(r'^\s*@loader_path', line): + # Potentially already fixed library + if '.framework' in line: + relative_path = os.path.join(*line.split('/')[3:]) + if not os.path.exists(os.path.join(frameworks_dir, relative_path)): + broken_libs['frameworks'].append(relative_path) + else: + relative_path = os.path.join(*line.split('/')[1:]) + #print "RELPATH %s %s" % (relative_path, os.path.join(binary_dir, relative_path)) + if not os.path.exists(os.path.join(binary_dir, relative_path)): + broken_libs['libs'].append(relative_path) + elif re.search(r'\w+\.framework', line): + broken_libs['frameworks'].append(line) + else: + broken_libs['libs'].append(line) + + return broken_libs + +def FindFramework(path): + search_pathes = FRAMEWORK_SEARCH_PATH + search_pathes.insert(0, QueryQMake('QT_INSTALL_LIBS')) + for search_path in search_pathes: + abs_path = os.path.join(search_path, path) + if os.path.exists(abs_path): + return abs_path + + raise CouldNotFindFrameworkError(path) + +def FindLibrary(path): + if os.path.exists(path): + return path + search_pathes = LIBRARY_SEARCH_PATH + search_pathes.insert(0, QueryQMake('QT_INSTALL_LIBS')) + for search_path in search_pathes: + abs_path = os.path.join(search_path, path) + if os.path.exists(abs_path): + return abs_path + else: # try harder---look for lib name in library folders + newpath = os.path.join(search_path,os.path.basename(path)) + if os.path.exists(newpath): + return newpath + + return "" + #raise CouldNotFindFrameworkError(path) + +def FixAllLibraries(broken_libs): + for framework in broken_libs['frameworks']: + FixFramework(framework) + for lib in broken_libs['libs']: + FixLibrary(lib) + +def FixFramework(path): + if path in fixed_libraries: + return + else: + fixed_libraries.append(path) + abs_path = FindFramework(path) + broken_libs = GetBrokenLibraries(abs_path) + FixAllLibraries(broken_libs) + + new_path = CopyFramework(abs_path) + id = os.sep.join(new_path.split(os.sep)[3:]) + FixFrameworkId(new_path, id) + for framework in broken_libs['frameworks']: + FixFrameworkInstallPath(framework, new_path) + for library in broken_libs['libs']: + FixLibraryInstallPath(library, new_path) + +def FixLibrary(path): + if path in fixed_libraries or FindSystemLibrary(os.path.basename(path)) is not None: + return + else: + fixed_libraries.append(path) + abs_path = FindLibrary(path) + if abs_path == "": + print "Could not resolve %s, not fixing!" % path + return + broken_libs = GetBrokenLibraries(abs_path) + FixAllLibraries(broken_libs) + + new_path = CopyLibrary(abs_path) + FixLibraryId(new_path) + for framework in broken_libs['frameworks']: + FixFrameworkInstallPath(framework, new_path) + for library in broken_libs['libs']: + FixLibraryInstallPath(library, new_path) + +def FixPlugin(abs_path, subdir): + broken_libs = GetBrokenLibraries(abs_path) + FixAllLibraries(broken_libs) + + new_path = CopyPlugin(abs_path, subdir) + for framework in broken_libs['frameworks']: + FixFrameworkInstallPath(framework, new_path) + for library in broken_libs['libs']: + FixLibraryInstallPath(library, new_path) + +def FixBinary(path): + broken_libs = GetBrokenLibraries(path) + FixAllLibraries(broken_libs) + for framework in broken_libs['frameworks']: + FixFrameworkInstallPath(framework, path) + for library in broken_libs['libs']: + FixLibraryInstallPath(library, path) + +def CopyLibrary(path): + new_path = os.path.join(frameworks_dir, os.path.basename(path)) + args = ['ditto', '--arch=i386', '--arch=x86_64', path, new_path] + commands.append(args) + args = ['chmod', 'u+w', new_path] + commands.append(args) + return new_path + +def CopyPlugin(path, subdir): + new_path = os.path.join(plugins_dir, subdir, os.path.basename(path)) + args = ['mkdir', '-p', os.path.dirname(new_path)] + commands.append(args) + args = ['ditto', '--arch=i386', '--arch=x86_64', path, new_path] + commands.append(args) + args = ['chmod', 'u+w', new_path] + commands.append(args) + return new_path + +def CopyFramework(path): + parts = path.split(os.sep) + print "CopyFramework:", path + for i, part in enumerate(parts): + if re.match(r'\w+\.framework', part): + full_path = os.path.join(frameworks_dir, *parts[i:-1]) + break + args = ['mkdir', '-p', full_path] + commands.append(args) + args = ['ditto', '--arch=i386', '--arch=x86_64', path, full_path] + commands.append(args) + args = ['chmod', 'u+w', os.path.join(full_path, parts[-1])] + commands.append(args) + + menu_nib = os.path.join(os.path.split(path)[0], 'Resources', 'qt_menu.nib') + if os.path.exists(menu_nib): + args = ['cp', '-r', menu_nib, resources_dir] + commands.append(args) + + return os.path.join(full_path, parts[-1]) + +def FixId(path, library_name): + id = '@executable_path/../Frameworks/%s' % library_name + args = ['install_name_tool', '-id', id, path] + commands.append(args) + +def FixLibraryId(path): + library_name = os.path.basename(path) + FixId(path, library_name) + +def FixFrameworkId(path, id): + FixId(path, id) + +def FixInstallPath(library_path, library, new_path): + args = ['install_name_tool', '-change', library_path, new_path, library] + commands.append(args) + +def FindSystemLibrary(library_name): + for path in ['/lib', '/usr/lib']: + full_path = os.path.join(path, library_name) + if os.path.exists(full_path): + return full_path + return None + +def FixLibraryInstallPath(library_path, library): + system_library = FindSystemLibrary(os.path.basename(library_path)) + if system_library is None: + new_path = '@executable_path/../Frameworks/%s' % os.path.basename(library_path) + FixInstallPath(library_path, library, new_path) + else: + FixInstallPath(library_path, library, system_library) + +def FixFrameworkInstallPath(library_path, library): + parts = library_path.split(os.sep) + for i, part in enumerate(parts): + if re.match(r'\w+\.framework', part): + full_path = os.path.join(*parts[i:]) + break + new_path = '@executable_path/../Frameworks/%s' % full_path + FixInstallPath(library_path, library, new_path) + +def FindQtPlugin(name): + search_path = QT_PLUGINS_SEARCH_PATH + search_path.insert(0, QueryQMake('QT_INSTALL_PLUGINS')) + for path in search_path: + if os.path.exists(path): + if os.path.exists(os.path.join(path, name)): + return os.path.join(path, name) + raise CouldNotFindQtPluginError(name) + +for binary in binaries: + FixBinary(binary) + +for plugin in QT_PLUGINS: + FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin)) + +if len(sys.argv) <= 2: + print 'Will run %d commands:' % len(commands) + for command in commands: + print ' '.join(command) + +for command in commands: + p = subprocess.Popen(command) + os.waitpid(p.pid, 0) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b7f014a12..4a00d9f21c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -404,18 +404,6 @@ if(NOT BUILD_LIBRARIES_ONLY) ) endif() - -#FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary -# currently it needs to be done because the code right above needs to be executed no matter -# if building a bundle or not and the install_qt4_executable needs to be called afterwards -if(BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY) - if(Qt5Core_FOUND) - install_qt5_executable(${OWNCLOUD_OSX_BUNDLE} "qtaccessiblewidgets;qsqlite;qcocoa") - else(Qt5Core_FOUND) - install_qt4_executable(${OWNCLOUD_OSX_BUNDLE} "qtaccessiblewidgets;qsqlite") - endif(Qt5Core_FOUND) -endif() - find_program(KRAZY2_EXECUTABLE krazy2) if(KRAZY2_EXECUTABLE) # s/y k/y ALL k/ for building this target always @@ -455,3 +443,13 @@ elseif(NOT BUILD_LIBRARIES_ONLY) LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() + +#FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary +# currently it needs to be done because the code right above needs to be executed no matter +# if building a bundle or not and the install_qt4_executable needs to be called afterwards +if(BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY) + install(CODE " + message(STATUS \"Deploying (Qt) dependencies and fixing library pathes...\") + execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/admin/osx/macdeployqt.py\" ${OWNCLOUD_OSX_BUNDLE}) + " COMPONENT RUNTIME) +endif()