diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d22793042e..ea226286b4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -397,7 +397,7 @@ elseif(MACOS) set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/deploy/mac/MacOSXBundleInfo.plist.in" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/deploy/macos/MacOSXBundleInfo.plist.in" MACOSX_BUNDLE_BUNDLE_NAME "${CMAKE_PROJECT_NAME}" MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}" MACOSX_BUNDLE_COPYRIGHT "${QGC_APP_COPYRIGHT}" @@ -541,8 +541,8 @@ install( ) set(deploy_tool_options_arg "") -if(APPLE) - set(deploy_tool_options_arg -qmldir=${CMAKE_SOURCE_DIR}/src -hardened-runtime -timestamp -appstore-compliant) +if(MACOS) + set(deploy_tool_options_arg -qmldir=${CMAKE_SOURCE_DIR}/src -hardened-runtime -timestamp -appstore-compliant) # -dmg endif() qt_generate_deploy_qml_app_script( @@ -595,6 +595,8 @@ if(LINUX) elseif(WIN32) install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CreateWinInstaller.cmake") elseif(MACOS) + install(CODE "set(OSXRELOCATOR ${CMAKE_SOURCE_DIR}/deploy/macos/osxrelocator.py)") + install(CODE "set(TARGET_NAME ${QGC_APP_NAME})") install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CreateMacDMG.cmake") endif() diff --git a/cmake/CreateCPackBundle.cmake b/cmake/CreateCPackBundle.cmake index 1c91552ee35..5c2c7a01e2f 100644 --- a/cmake/CreateCPackBundle.cmake +++ b/cmake/CreateCPackBundle.cmake @@ -2,7 +2,7 @@ include(CreateCPackCommon) set(CPACK_GENERATOR "BUNDLE") -set(QGC_INSTALLER_SOURCE "${CMAKE_SOURCE_DIR}/deploy/mac") +set(QGC_INSTALLER_SOURCE "${CMAKE_SOURCE_DIR}/deploy/macos") set(CPACK_BUNDLE_NAME ${CMAKE_PROJECT_NAME}) set(CPACK_BUNDLE_PLIST "${QGC_INSTALLER_SOURCE}/MacOSXBundleInfo.plist.in") diff --git a/cmake/CreateCPackDMG.cmake b/cmake/CreateCPackDMG.cmake index 0c8089db9ad..23271d82b2a 100644 --- a/cmake/CreateCPackDMG.cmake +++ b/cmake/CreateCPackDMG.cmake @@ -2,7 +2,7 @@ include(CreateCPackCommon) set(CPACK_GENERATOR "DragNDrop") -set(QGC_INSTALLER_SOURCE "${CMAKE_SOURCE_DIR}/deploy/mac") +set(QGC_INSTALLER_SOURCE "${CMAKE_SOURCE_DIR}/deploy/macos") set(CPACK_DMG_VOLUME_NAME ${CPACK_PACKAGE_FILE_NAME}) set(CPACK_DMG_FORMAT UDBZ) diff --git a/cmake/CreateMacDMG.cmake b/cmake/CreateMacDMG.cmake index 672f3c3b9f5..56508c24738 100644 --- a/cmake/CreateMacDMG.cmake +++ b/cmake/CreateMacDMG.cmake @@ -1,48 +1,122 @@ +include(CMakePrintHelpers) + message(STATUS "Creating Mac Bundle") -set(TARGET_NAME QGroundControl) +# set(TARGET_NAME QGroundControl) +set(BUNDLE_PATH ${CMAKE_BINARY_DIR}/staging/${TARGET_NAME}.app) set(SYSTEM_FRAMEWORK_PATH /Library/Frameworks) -set(BUNDLE_FRAMEWORK_PATH staging/${TARGET_NAME}.app/Contents/Frameworks) - -message(STATUS "Copy GStreamer framework into bundle") -file(COPY ${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework DESTINATION ${BUNDLE_FRAMEWORK_PATH}) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/bin) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/etc) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/share) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/Headers) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/include) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/Commands) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/pkgconfig) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/glib-2.0) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/graphene-1.0) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gst-validate-launcher) -file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/*.a) -file(REMOVE ${REMOVE_LIB_FILES}) -file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/*.la) -file(REMOVE ${REMOVE_LIB_FILES}) -file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/*.a) -file(REMOVE ${REMOVE_LIB_FILES}) -file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/*.la) -file(REMOVE ${REMOVE_LIB_FILES}) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/include) -file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/pkgconfig) +set(BUNDLE_FRAMEWORK_PATH ${BUNDLE_PATH}/Contents/Frameworks) + +# execute_process( +# COMMAND ${CMAKE_SOURCE_DIR}/deploy/macos/prepare_gstreamer_framework.sh ${CMAKE_BINARY_DIR}/gstwork/ staging/${TARGET_NAME}.app ${TARGET_NAME} +# RESULT_VARIABLE GSTREAMER_FRAMEWORK_RESULT +# OUTPUT_VARIABLE GSTREAMER_FRAMEWORK_OUTPUT +# ERROR_VARIABLE GSTREAMER_FRAMEWORK_ERROR +# ) + +# cmake_print_variables(GSTREAMER_FRAMEWORK_RESULT GSTREAMER_FRAMEWORK_OUTPUT GSTREAMER_FRAMEWORK_ERROR) + +# message(STATUS "Copy GStreamer framework into bundle") +# file( +# INSTALL "${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework" +# DESTINATION "${BUNDLE_FRAMEWORK_PATH}" +# PATTERN "*.la" EXCLUDE +# PATTERN "*.a" EXCLUDE +# PATTERN "*/include" EXCLUDE +# PATTERN "*/Headers" EXCLUDE +# ) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/bin) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/etc) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/share) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/Headers) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/include) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/Commands) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/pkgconfig) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/glib-2.0) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/graphene-1.0) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gst-validate-launcher) +# file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/*.a) +# file(REMOVE ${REMOVE_LIB_FILES}) +# file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/*.la) +# file(REMOVE ${REMOVE_LIB_FILES}) +# file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/*.a) +# file(REMOVE ${REMOVE_LIB_FILES}) +# file(GLOB REMOVE_LIB_FILES ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/*.la) +# file(REMOVE ${REMOVE_LIB_FILES}) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/include) +# file(REMOVE_RECURSE ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/pkgconfig) + +# execute_process(COMMAND otool -L ${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework/GStreamer) # Fix up library paths to point into bundle -execute_process(COMMAND ln -sf ${BUNDLE_FRAMEWORK_PATH} ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/libexec/Frameworks) -execute_process(COMMAND install_name_tool -change ${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/GStreamer @executable_path/../Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer staging/${TARGET_NAME}.app/Contents/MacOS/${TARGET_NAME}) +# execute_process( +# COMMAND ln -sf ${BUNDLE_FRAMEWORK_PATH} ${BUNDLE_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/libexec +# ) +# execute_process( +# COMMAND install_name_tool -id @executable_path/../Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer ${BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/1.0/GStreamer +# RESULT_VARIABLE GSTREAMER_FRAMEWORK_RESULT +# OUTPUT_VARIABLE GSTREAMER_FRAMEWORK_OUTPUT +# ERROR_VARIABLE GSTREAMER_FRAMEWORK_ERROR +# ) +# execute_process( +# COMMAND install_name_tool -change ${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/GStreamer @executable_path/../Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer ${BUNDLE_PATH}/Contents/MacOS/${TARGET_NAME} +# RESULT_VARIABLE GSTREAMER_FRAMEWORK_RESULT +# OUTPUT_VARIABLE GSTREAMER_FRAMEWORK_OUTPUT +# ERROR_VARIABLE GSTREAMER_FRAMEWORK_ERROR +# ) + +find_package(Python3 REQUIRED) +# set(OSXRELOCATOR ${CMAKE_SOURCE_DIR}/deploy/macos/osxrelocator.py) + +execute_process( + COMMAND ${Python3_EXECUTABLE} ${OSXRELOCATOR} ${BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/Current/lib /Library/Frameworks/GStreamer.framework/ @executable_path/../Frameworks/GStreamer.framework/ -r + RESULT_VARIABLE PYTHON_RESULT + OUTPUT_VARIABLE PYTHON_OUTPUT + ERROR_VARIABLE PYTHON_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +cmake_print_variables(PYTHON_RESULT PYTHON_OUTPUT PYTHON_ERROR) + +execute_process( + COMMAND ${Python3_EXECUTABLE} ${OSXRELOCATOR} ${BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/Current/libexec /Library/Frameworks/GStreamer.framework/ @executable_path/../../../../../GStreamer.framework/ -r + RESULT_VARIABLE PYTHON_RESULT + OUTPUT_VARIABLE PYTHON_OUTPUT + ERROR_VARIABLE PYTHON_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +cmake_print_variables(PYTHON_RESULT PYTHON_OUTPUT PYTHON_ERROR) + +execute_process( + COMMAND ${Python3_EXECUTABLE} ${OSXRELOCATOR} ${BUNDLE_PATH}/Contents/Frameworks/GStreamer.framework/Versions/Current/bin /Library/Frameworks/GStreamer.framework/ @executable_path/../../../../GStreamer.framework/ -r + RESULT_VARIABLE PYTHON_RESULT + OUTPUT_VARIABLE PYTHON_OUTPUT + ERROR_VARIABLE PYTHON_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +cmake_print_variables(PYTHON_RESULT PYTHON_OUTPUT PYTHON_ERROR) + +execute_process( + COMMAND ${Python3_EXECUTABLE} ${OSXRELOCATOR} ${BUNDLE_PATH}/Contents/MacOS /Library/Frameworks/GStreamer.framework/ @executable_path/../Frameworks/GStreamer.framework/ -r + RESULT_VARIABLE PYTHON_RESULT + OUTPUT_VARIABLE PYTHON_OUTPUT + ERROR_VARIABLE PYTHON_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +cmake_print_variables(PYTHON_RESULT PYTHON_OUTPUT PYTHON_ERROR) + +execute_process(COMMAND otool -L ${BUNDLE_PATH}/Contents/MacOS/${TARGET_NAME}) # include(BundleUtilities) -# include(CMakePrintHelpers) -# fixup_bundle("${CMAKE_BINARY_DIR}/staging/${TARGET_NAME}.app" "" "${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/GStreamer") -# verify_app("${CMAKE_BINARY_DIR}/staging/${TARGET_NAME}.app") -# verify_bundle_prerequisites("${CMAKE_BINARY_DIR}/staging/${TARGET_NAME}.app" VERIFY_BUNDLE_PREREQS_RESULT VERIFY_BUNDLE_PREREQS_INFO) +# fixup_bundle("${BUNDLE_PATH}" "" "${SYSTEM_FRAMEWORK_PATH}/GStreamer.framework/Versions/1.0/lib/GStreamer") +# verify_app("${BUNDLE_PATH}") +# verify_bundle_prerequisites("${BUNDLE_PATH}" VERIFY_BUNDLE_PREREQS_RESULT VERIFY_BUNDLE_PREREQS_INFO) # cmake_print_variables(VERIFY_BUNDLE_PREREQS_RESULT VERIFY_BUNDLE_PREREQS_INFO) -# verify_bundle_symlinks("${CMAKE_BINARY_DIR}/staging/${TARGET_NAME}.app" VERIFY_BUNDLE_SYMLINKS_RESULT VERIFY_BUNDLE_SYMLINKS_INFO) +# verify_bundle_symlinks("${BUNDLE_PATH}" VERIFY_BUNDLE_SYMLINKS_RESULT VERIFY_BUNDLE_SYMLINKS_INFO) # cmake_print_variables(VERIFY_BUNDLE_SYMLINKS_RESULT VERIFY_BUNDLE_SYMLINKS_INFO) message(STATUS "Creating Mac DMG") -file(REMOVE_RECURSE package) -file(MAKE_DIRECTORY package) -file(COPY staging/${TARGET_NAME}.app DESTINATION package) -execute_process(COMMAND create-dmg --volname "${TARGET_NAME} Installer" "${TARGET_NAME}.dmg" "package/") +file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/package) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/package) +file(COPY ${BUNDLE_PATH} DESTINATION ${CMAKE_BINARY_DIR}/package) +execute_process(COMMAND create-dmg --volname "${TARGET_NAME}" "${TARGET_NAME}.dmg" "${CMAKE_BINARY_DIR}/package/") diff --git a/cmake/CustomOptions.cmake b/cmake/CustomOptions.cmake index f4408efbdd9..5431584e128 100644 --- a/cmake/CustomOptions.cmake +++ b/cmake/CustomOptions.cmake @@ -20,12 +20,7 @@ option(QGC_UTM_ADAPTER "Enable UTM Adapter" OFF) option(QGC_VIEWER3D "Enable Viewer3D" ON) # Qt6Quick3D_FOUND option(QGC_ENABLE_UVC "Enable UVC Devices" ON) # Qt6Multimedia_FOUND -if(APPLE AND NOT IOS) - # Still haven't figured out how to package GStreamer with the app. - option(QGC_ENABLE_GST_VIDEOSTREAMING "Enable GStreamer Video Backend" OFF) -else() - option(QGC_ENABLE_GST_VIDEOSTREAMING "Enable GStreamer Video Backend" ON) -endif() +option(QGC_ENABLE_GST_VIDEOSTREAMING "Enable GStreamer Video Backend" ON) option(QGC_ENABLE_QT_VIDEOSTREAMING "Enable QtMultimedia Video Backend" OFF) # Qt6Multimedia_FOUND set(QGC_MAVLINK_GIT_REPO "https://github.com/mavlink/c_library_v2.git" CACHE STRING "URL to MAVLink Git Repo") @@ -38,7 +33,7 @@ set(QGC_ANDROID_PACKAGE_NAME "org.mavlink.qgroundcontrol" CACHE STRING "Android # MacOS set(QGC_BUNDLE_ID "org.qgroundcontrol.QGroundControl" CACHE STRING "MacOS Bundle ID") # MACOS -set(QGC_MACOS_ICON_PATH "${CMAKE_SOURCE_DIR}/deploy/mac" CACHE PATH "MacOS Icon Path") # MACOS +set(QGC_MACOS_ICON_PATH "${CMAKE_SOURCE_DIR}/deploy/macos" CACHE PATH "MacOS Icon Path") # MACOS # APM option(QGC_DISABLE_APM_MAVLINK "Disable APM Dialect" OFF) diff --git a/cmake/find-modules/FindGStreamer.cmake b/cmake/find-modules/FindGStreamer.cmake index 3f000ac404e..b221a76b8e0 100644 --- a/cmake/find-modules/FindGStreamer.cmake +++ b/cmake/find-modules/FindGStreamer.cmake @@ -44,7 +44,8 @@ if(WIN32) ) elseif(MACOS) list(APPEND CMAKE_FRAMEWORK_PATH "/Library/Frameworks") - set(GSTREAMER_PREFIX "/Library/Frameworks/GStreamer.framework/Versions/1.0") + set(GSTREAMER_FRAMEWORK_PATH "/Library/Frameworks/GStreamer.framework" CACHE PATH "GStreamer Framework Path") + set(GSTREAMER_PREFIX "${GSTREAMER_FRAMEWORK_PATH}/Versions/1.0") find_program(PKG_CONFIG_PROGRAM pkg-config PATHS ${GSTREAMER_PREFIX}/bin NO_DEFAULT_PATH) if(PKG_CONFIG_PROGRAM) set(PKG_CONFIG_EXECUTABLE ${PKG_CONFIG_PROGRAM}) @@ -668,6 +669,10 @@ target_include_directories(GStreamer::GStreamer target_link_directories(GStreamer::GStreamer INTERFACE ${GSTREAMER_LIB_PATH}) +if(MACOS AND EXISTS ${GSTREAMER_FRAMEWORK_PATH}) + target_link_libraries(GStreamer::GStreamer INTERFACE "-F /Library/Frameworks -framework GStreamer") +endif() + ################################################################################ # TODO: https://gstreamer.freedesktop.org/documentation/qt6d3d11/index.html#qml6d3d11sink-page diff --git a/deploy/mac/MacOSXBundleInfo.plist.in b/deploy/macos/MacOSXBundleInfo.plist.in similarity index 100% rename from deploy/mac/MacOSXBundleInfo.plist.in rename to deploy/macos/MacOSXBundleInfo.plist.in diff --git a/deploy/mac/macx.icns b/deploy/macos/macx.icns similarity index 100% rename from deploy/mac/macx.icns rename to deploy/macos/macx.icns diff --git a/deploy/macos/osxrelocator.py b/deploy/macos/osxrelocator.py new file mode 100755 index 00000000000..9a6999b5dfb --- /dev/null +++ b/deploy/macos/osxrelocator.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# cerbero - a multi-platform build system for Open Source software +# Copyright (C) 2012 Andoni Morales Alastruey +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import os +import subprocess + + +INT_CMD = 'install_name_tool' +OTOOL_CMD = 'otool' + + +def shell_call(cmd, cmd_dir='.', fail=True): + #print("call", cmd) + try: + ret = subprocess.check_call( + cmd, cwd=cmd_dir, + env=os.environ.copy()) + except subprocess.CalledProcessError: + if fail: + raise SystemError("Error running command: {}".format(cmd)) + else: + ret = 0 + return ret + + +def shell_check_call(cmd): + #print("ccall", cmd) + try: + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE) + output, _ = process.communicate() + except Exception: + raise SystemError("Error running command: {}".format(cmd)) + return output + + +class OSXRelocator(object): + ''' + Wrapper for OS X's install_name_tool and otool commands to help + relocating shared libraries. + + It parses lib/ /libexec and bin/ directories, changes the prefix path of + the shared libraries that an object file uses and changes it's library + ID if the file is a shared library. + ''' + + def __init__(self, root, lib_prefix, new_lib_prefix, recursive): + self.root = root + self.lib_prefix = self._fix_path(lib_prefix).encode('utf-8') + self.new_lib_prefix = self._fix_path(new_lib_prefix).encode('utf-8') + self.recursive = recursive + + def relocate(self): + self.parse_dir(self.root, filters=['', '.dylib', '.so']) + + def relocate_file(self, object_file, id=None): + self.change_libs_path(object_file) + self.change_id(object_file, id) + + def change_id(self, object_file, id=None): + id = id or object_file.replace(self.lib_prefix.decode('utf-8'), self.new_lib_prefix.decode('utf-8')) + filename = os.path.basename(object_file) + if not (filename.endswith('so') or filename.endswith('dylib')): + return + cmd = [INT_CMD, "-id", id, object_file] + shell_call(cmd, fail=False) + + def change_libs_path(self, object_file): + for lib in self.list_shared_libraries(object_file): + if self.lib_prefix in lib: + new_lib = lib.replace(self.lib_prefix, self.new_lib_prefix) + cmd = [INT_CMD, "-change", lib, new_lib, object_file] + shell_call(cmd) + + def parse_dir(self, dir_path, filters=None): + for dirpath, dirnames, filenames in os.walk(dir_path): + for f in filenames: + if filters is not None and \ + os.path.splitext(f)[1] not in filters: + continue + fn = os.path.join(dirpath, f) + if os.path.islink(fn): + continue + if not os.path.isfile(fn): + continue + self.relocate_file(fn) + if not self.recursive: + break + + @staticmethod + def list_shared_libraries(object_file): + cmd = [OTOOL_CMD, "-L", object_file] + res = shell_check_call(cmd).split(b'\n') + # We don't use the first line + libs = res[1:] + # Remove the first character tabulation + libs = [x[1:] for x in libs] + # Remove the version info + libs = [x.split(b' ', 1)[0] for x in libs] + return libs + + @staticmethod + def library_id_name(object_file): + cmd = [OTOOL_CMD, "-D", object_file] + res = shell_check_call(cmd).split('\n')[0] + # the library name ends with ':' + lib_name = res[:-1] + return lib_name + + def _fix_path(self, path): + if path.endswith('/'): + return path[:-1] + return path + + +class Main(object): + + def run(self): + # We use OptionParser instead of ArgumentsParse because this script + # might be run in OS X 10.6 or older, which do not provide the argparse + # module + import optparse + usage = "usage: %prog [options] directory old_prefix new_prefix" + description = 'Rellocates object files changing the dependent '\ + ' dynamic libraries location path with a new one' + parser = optparse.OptionParser(usage=usage, description=description) + parser.add_option('-r', '--recursive', action='store_true', + default=False, dest='recursive', + help='Scan directories recursively') + + options, args = parser.parse_args() + if len(args) != 3: + parser.print_usage() + exit(1) + relocator = OSXRelocator(args[0], args[1], args[2], options.recursive) + relocator.relocate() + exit(0) + +def main(): + main = Main() + main.run() + +if __name__ == "__main__": + main() diff --git a/deploy/macos/osxrelocator2.py b/deploy/macos/osxrelocator2.py new file mode 100755 index 00000000000..276a5a5fbaf --- /dev/null +++ b/deploy/macos/osxrelocator2.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +# cerbero - a multi-platform build system for Open Source software +# Copyright (C) 2012 Andoni Morales Alastruey +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import os +import subprocess + +INT_CMD = 'install_name_tool' +OTOOL_CMD = 'otool' + + +class CerberoException(Exception): + header = '' + msg = '' + + def __init__(self, msg=''): + self.msg = msg + Exception.__init__(self, self.header + msg) + + +class FatalError(CerberoException): + header = 'Fatal Error: ' + + def __init__(self, msg='', arch=''): + self.arch = arch + CerberoException.__init__(self, msg) + + +class CommandError(FatalError): + header = 'Command Error: ' + + def __init__(self, msg, cmd, returncode): + msg = 'Running {!r} returned {}\n{}'.format(cmd, returncode, msg or '') + FatalError.__init__(self, msg) + + +def _resolve_cmd(cmd, env): + """ + On Windows, we can't pass the PATH variable through the env= kwarg to + subprocess.* and expect it to use that value to search for the command, + because Python uses CreateProcess directly. Unlike execvpe, CreateProcess + does not use the PATH env var in the env supplied to search for the + executable. Hence, we need to search for it manually. + """ + if PLATFORM != Platform.WINDOWS or env is None or 'PATH' not in env: + return cmd + if not os.path.isabs(cmd[0]): + resolved_cmd = shutil.which(cmd[0], path=env['PATH']) + if not resolved_cmd: + raise FatalError('Could not find {!r} in PATH {!r}'.format(cmd[0], env['PATH'])) + cmd[0] = resolved_cmd + return cmd + + +def _cmd_string_to_array(cmd, env): + if isinstance(cmd, list): + return _resolve_cmd(cmd, env) + assert isinstance(cmd, str) + # If we've been given a string, run it through sh to get scripts working on + # Windows and shell syntax such as && and env var setting working on all + # platforms. + return ['sh', '-c', cmd] + + +def shell_check_output(cmd, cmd_dir=None, fail=True, logfile=None, env=None, quiet=False): + cmd = _cmd_string_to_array(cmd, env) + stderr = logfile + if quiet and not logfile: + stderr = subprocess.DEVNULL + if logfile: + logfile.write(f'Running command {cmd!r} in {cmd_dir}\n') + logfile.flush() + + try: + o = subprocess.check_output(cmd, cwd=cmd_dir, env=env, stderr=stderr) + except SUBPROCESS_EXCEPTIONS as e: + msg = getattr(e, 'output', '') + if isinstance(msg, bytes): + msg = msg.decode(sys.stdout.encoding, errors='replace') + if not fail: + return msg + if logfile: + msg += '\nstderr in logfile {}'.format(logfile.name) + raise CommandError(msg, cmd, getattr(e, 'returncode', -1)) + + if sys.stdout.encoding: + o = o.decode(sys.stdout.encoding, errors='replace') + return o + + +def shell_new_call( + cmd, cmd_dir=None, fail=True, logfile=None, env=None, verbose=False, interactive=False, shell=False, input=None +): + cmd = _cmd_string_to_array(cmd, env) + if logfile: + if input: + logfile.write(f'Running command {cmd!r} with stdin {input} in {cmd_dir}\n') + else: + logfile.write(f'Running command {cmd!r} in {cmd_dir}\n') + logfile.flush() + if verbose: + m.message('Running {!r}\n'.format(cmd)) + if input: + stdin = None + elif not interactive: + stdin = subprocess.DEVNULL + else: + stdin = None + try: + subprocess.run( + cmd, + cwd=cmd_dir, + env=env, + stdout=logfile, + stderr=subprocess.STDOUT, + stdin=stdin, + input=input, + shell=shell, + check=True, + ) + except SUBPROCESS_EXCEPTIONS as e: + returncode = getattr(e, 'returncode', -1) + if not fail: + stream = logfile or sys.stderr + if isinstance(e, FileNotFoundError): + stream.write('{}: file not found\n'.format(cmd[0])) + if isinstance(e, PermissionError): + stream.write('{!r}: permission error\n'.format(cmd)) + return returncode + msg = '' + if logfile: + msg = 'Output in logfile {}'.format(logfile.name) + raise CommandError(msg, cmd, returncode) + return 0 + + +class OSXRelocator(object): + """ + Wrapper for OS X's install_name_tool and otool commands to help + relocating shared libraries. + + It parses lib/ /libexec and bin/ directories, changes the prefix path of + the shared libraries that an object file uses and changes it's library + ID if the file is a shared library. + """ + + def __init__(self, root, install_prefix, recursive, logfile=None): + self.root = root + self.install_prefix = self._fix_path(install_prefix) + self.recursive = recursive + self.use_relative_paths = True + self.logfile = None + + def relocate(self): + self.parse_dir(self.root) + + def relocate_dir(self, dirname): + self.parse_dir(os.path.join(self.root, dirname)) + + def relocate_file(self, object_file, original_file=None): + self.change_libs_path(object_file, original_file) + + def change_id(self, object_file, id=None): + """ + Changes the `LC_ID_DYLIB` of the given object file. + @object_file: Path to the object file + @id: New ID; if None, it'll be `@rpath/` + """ + id = id or object_file.replace(self.install_prefix, '@rpath') + if not self._is_mach_o_file(object_file): + return + cmd = [INT_CMD, '-id', id, object_file] + shell_new_call(cmd, fail=False, logfile=self.logfile) + + def change_libs_path(self, object_file, original_file=None): + """ + Sanitizes the `LC_LOAD_DYLIB` and `LC_RPATH` load commands, + setting the former to be of the form `@rpath/libyadda.dylib`, + and the latter to point to the /lib folder within the GStreamer prefix. + @object_file: the actual file location + @original_file: where the file will end up in the output directory + structure and the basis of how to calculate rpath entries. This may + be different from where the file is currently located e.g. when + creating a fat binary from copy of the original file in a temporary + location. + """ + if not self._is_mach_o_file(object_file): + return + if original_file is None: + original_file = object_file + # First things first: ensure the load command of future consumers + # points to the real ID of this library + # This used to be done only at Universal lipo time, but by then + # it's too late -- unless one wants to run through all load commands + # If the library isn't a dylib, it's a framework, in which case + # assert that it's already rpath'd + dylib_id = self.get_dylib_id(object_file) + is_dylib = dylib_id is not None + is_framework = is_dylib and not object_file.endswith('.dylib') + if not is_framework: + self.change_id(object_file, id='@rpath/{}'.format(os.path.basename(original_file))) + elif '@rpath' not in dylib_id: + raise FatalError(f'Cannot relocate a fixed location framework: {dylib_id}') + # With that out of the way, we need to sort out how many parents + # need to be navigated to reach the root of the GStreamer prefix + depth = len(os.path.dirname(original_file).split('/')) - len(self.install_prefix.split('/')) + p_depth = '/..' * depth + # These paths assume that the file being relocated resides within + # /lib + rpaths = [ + # From a deeply nested library + f'@loader_path{p_depth}/lib', + # From a deeply nested framework or binary + f'@executable_path{p_depth}/lib', + # From a library within the prefix + '@loader_path/../lib', + # From a binary within the prefix + '@executable_path/../lib', + ] + if depth > 1: + rpaths += [ + # Allow loading from the parent (e.g. GIO plugin) + '@loader_path/..', + '@executable_path/..', + ] + if is_framework: + # Start with framework's libraries + rpaths = [ + '@loader_path/lib', + ] + rpaths + # Make them unique + rpaths = list(set(rpaths)) + # Remove absolute RPATHs, we don't want or need these + existing_rpaths = list(set(self.list_rpaths(object_file))) + for p in filter(lambda p: p.startswith('/'), self.list_rpaths(object_file)): + cmd = [INT_CMD, '-delete_rpath', p, object_file] + shell_new_call(cmd, fail=False) + # Add relative RPATHs + for p in filter(lambda p: p not in existing_rpaths, rpaths): + cmd = [INT_CMD, '-add_rpath', p, object_file] + shell_new_call(cmd, fail=False) + # Change dependencies' paths from absolute to @rpath/ + for lib in self.list_shared_libraries(object_file): + new_lib = lib.replace(self.install_prefix, '@rpath').replace('@rpath/lib/', '@rpath/') + # These are leftovers from meson thinking RPATH == prefix + if new_lib == lib: + continue + cmd = [INT_CMD, '-change', lib, new_lib, object_file] + shell_new_call(cmd, fail=False, logfile=self.logfile) + + def change_lib_path(self, object_file, old_path, new_path): + for lib in self.list_shared_libraries(object_file): + if old_path in lib: + new_path = lib.replace(old_path, new_path) + cmd = [INT_CMD, '-change', lib, new_path, object_file] + shell_new_call(cmd, fail=True, logfile=self.logfile) + + def parse_dir(self, dir_path, filters=None): + for dirpath, dirnames, filenames in os.walk(dir_path): + for f in filenames: + if filters is not None and os.path.splitext(f)[1] not in filters: + continue + self.change_libs_path(os.path.join(dirpath, f)) + if not self.recursive: + break + + @staticmethod + def get_dylib_id(object_file): + res = shell_check_output([OTOOL_CMD, '-D', object_file]).splitlines() + return res[-1] if len(res) > 1 else None + + @staticmethod + def list_shared_libraries(object_file): + res = shell_check_output([OTOOL_CMD, '-L', object_file]).splitlines() + # We don't use the first line + libs = res[1:] + # Remove the first character tabulation + libs = [x[1:] for x in libs] + # Remove the version info + libs = [x.split(' ', 1)[0] for x in libs] + return libs + + @staticmethod + def list_rpaths(object_file): + res = shell_check_output([OTOOL_CMD, '-l', object_file]).splitlines() + i = iter(res) + paths = [] + for line in i: + if 'LC_RPATH' not in line: + continue + next(i) + path_line = next(i) + # Extract the path from a line that looks like this: + # path @loader_path/.. (offset 12) + path = path_line.split('path ', 1)[1].split(' (offset', 1)[0] + paths.append(path) + return paths + + def _fix_path(self, path): + if path.endswith('/'): + return path[:-1] + return path + + def _is_mach_o_file(self, filename): + fileext = os.path.splitext(filename)[1] + + if '.dylib' in fileext: + return True + + filedesc = shell_check_output(['file', '-bh', filename]) + + if fileext == '.a' and 'ar archive' in filedesc: + return False + + return filedesc.startswith('Mach-O') + + +class Main(object): + def run(self): + # We use OptionParser instead of ArgumentsParse because this script + # might be run in OS X 10.6 or older, which do not provide the argparse + # module + import optparse + + usage = 'usage: %prog [options] library_path old_prefix new_prefix' + description = ( + 'Rellocates object files changing the dependant ' ' dynamic libraries location path with a new one' + ) + parser = optparse.OptionParser(usage=usage, description=description) + parser.add_option( + '-r', + '--recursive', + action='store_true', + default=False, + dest='recursive', + help='Scan directories recursively', + ) + + options, args = parser.parse_args() + if len(args) != 3: + parser.print_usage() + exit(1) + relocator = OSXRelocator(args[1], args[2], options.recursive) + relocator.relocate_file(args[0]) + exit(0) + + +if __name__ == '__main__': + main = Main() + main.run() diff --git a/deploy/macos/prepare_gstreamer_framework.sh b/deploy/macos/prepare_gstreamer_framework.sh new file mode 100755 index 00000000000..f59f4c93bbd --- /dev/null +++ b/deploy/macos/prepare_gstreamer_framework.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# +# Author: Gus Grubba +# +# Copies the regular distribution of the gstreamer framework +# into the libs directory (to be used for creating the Mac OS +# bundle). It also prunes unnecessary files and relocates the +# dynamic libraries. +# +# This script is run by the build process and should not be +# executed manually. +# +# Usage: $script [framework destination path] [app bundle path] [qgc executable name] +# +# destination is usually: ../libs/lib/Frameworks/ +# app bundle is something like: /path/qgroundcontrol.app +# executable name is usually: qgroundcontrol + +die () { + echo "$@" 1>&2 + exit 1 +} + +GST_VER=1.0 +GST_ROOT=/Library/Frameworks/GStreamer.framework +GST_BASE=$GST_ROOT/Versions/$GST_VER +RELOC=$(dirname $0)/osxrelocator.py + +OLDDLPATH=/Library/Frameworks/GStreamer.framework/ +NEWDLPATH=@executable_path/../Frameworks/GStreamer.framework/ + +echo "GST Installer" +[ "$#" -eq 3 ] || die "3 arguments required, $# provided" +[ -d "$2" ] || die "Could not find $2" + +FMWORK_TARGET=$1 +BUNDLE_TARGET=$2 +GST_SOURCE=${1%/}/GStreamer.framework +GST_TARGET=$GST_SOURCE/Versions/$GST_VER +QGC_BINARY=$BUNDLE_TARGET/Contents/MacOS/$3 +[ -e "$QGC_BINARY" ] || die "Could not find $QGC_BINARY" + +process_framework() { + #-- Start looking for the source framework + [ -d "$GST_ROOT" ] || die "Could not find gstreamer framework in $GST_ROOT" + [ -d "$GST_BASE" ] || die "Wrong version of gstreamer framework found in $GST_ROOT" + [ -e $RELOC ] || die "Cannot find $RELOC" + echo "GST Installer: Copying $GST_ROOT to $FMWORK_TARGET" + rsync -a --delete "$GST_ROOT" "$FMWORK_TARGET" || die "Error copying $GST_ROOT to $FMWORK_TARGET" + #-- Prune unused stuff + rm -rf $GST_TARGET/bin + rm -rf $GST_TARGET/etc + rm -rf $GST_TARGET/share + rm -rf $GST_TARGET/Headers + rm -rf $GST_TARGET/include + rm -rf $GST_TARGET/lib/*.a + rm -rf $GST_TARGET/lib/*.la + rm -rf $GST_TARGET/lib/gio/modules/static + rm -rf $GST_TARGET/lib/glib-2.0 + rm -rf $GST_TARGET/lib/gst-validate-launcher + rm -rf $GST_TARGET/lib/gstreamer-1.0/static + rm -rf $GST_TARGET/lib/libffi-3.0.13 + rm -rf $GST_TARGET/lib/pkgconfig + rm $GST_TARGET/Commands + #-- Now relocate the embedded paths + echo "GST Installer: Relocating" + python $RELOC -r "$GST_TARGET" "$OLDDLPATH" "$NEWDLPATH" > /dev/null || die "Error relocating binaries in $GST_TARGET/lib" +} + +#-- Check and see if we've already processed the framework +echo "GST Installer: Checking $GST_TARGET" +[ -d $GST_TARGET ] || process_framework +#-- Now copy the framework to the app bundle +echo "GST Installer: Copying $GST_SOURCE to $BUNDLE_TARGET/Contents/Frameworks/" +rsync -a --delete $GST_SOURCE $BUNDLE_TARGET/Contents/Frameworks/ || die "Error copying framework into app bundle" +#-- The plugin scanner needs to find the GStreamer libraries +GSTINBUNDLE=$BUNDLE_TARGET/Contents/Frameworks/GStreamer.framework/Versions/$GST_VER +pushd $GSTINBUNDLE/libexec && ln -sf ../../../../../Frameworks . && popd || die "Error creating Frameworks symlink in $GST_TARGET/libexec" +#-- Fix main binary +install_name_tool -change /Library/Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer @executable_path/../Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer "$QGC_BINARY" > /dev/null || die "Error relocating $QGC_BINARY" +pushd $GSTINBUNDLE && install_name_tool -id @executable_path/../Frameworks/GStreamer.framework/Versions/1.0/lib/GStreamer GStreamer && popd || die "Error relocating GStreamer" + + + diff --git a/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc index a57471689ee..788895130d3 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc +++ b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc @@ -107,8 +107,9 @@ static void _qgcputenv(const QString &key, const QString &root, const QString &p static void _setGstEnvVars() { const QString currentDir = QCoreApplication::applicationDirPath(); + qCDebug(GStreamerLog) << "App Directory:" << currentDir; -#if defined(Q_OS_MAC) +#if defined(Q_OS_MAC) && defined(QGC_GST_MACOS_FRAMEWORK) _qgcputenv("GST_REGISTRY_REUSE_PLUGIN_SCANNER", "no"); _qgcputenv("GST_PLUGIN_SCANNER", currentDir, "/../Frameworks/GStreamer.framework/Versions/1.0/libexec/gstreamer-1.0/gst-plugin-scanner"); _qgcputenv("GST_PTP_HELPER_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/1.0/libexec/gstreamer-1.0/gst-ptp-helper"); diff --git a/src/VideoManager/VideoReceiver/GStreamer/gstqml6gl/CMakeLists.txt b/src/VideoManager/VideoReceiver/GStreamer/gstqml6gl/CMakeLists.txt index a0560146264..cc850c3f0c7 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/gstqml6gl/CMakeLists.txt +++ b/src/VideoManager/VideoReceiver/GStreamer/gstqml6gl/CMakeLists.txt @@ -50,21 +50,21 @@ elseif(WIN32) file(GLOB GST_HELPER_BINS ${GST_HELPER_BINS_PATH}) install(FILES ${GST_HELPER_BINS} DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gstreamer-1.0) elseif(MACOS) - # cmake_path(CONVERT "${GSTREAMER_PREFIX}/bin/*.dylib" TO_CMAKE_PATH_LIST GST_MACOS_BINS_PATH) - # file(GLOB GST_MACOS_BINS ${GST_MACOS_BINS_PATH}) - # install(FILES ${GST_MACOS_BINS} DESTINATION ${CMAKE_INSTALL_BINDIR}) - - # cmake_path(CONVERT "${GSTREAMER_LIB_PATH}/gio/modules/*.dylib" TO_CMAKE_PATH_LIST GST_GIO_MODULES_PATH) - # file(GLOB GST_GIO_MODULES ${GST_GIO_MODULES_PATH}) - # install(FILES ${GST_GIO_MODULES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/gio/modules) - - # cmake_path(CONVERT "${GSTREAMER_PLUGIN_PATH}/*.dylib" TO_CMAKE_PATH_LIST GST_MACOS_PLUGINS_PATH) - # file(GLOB GST_MACOS_PLUGINS ${GST_MACOS_PLUGINS_PATH}) - # install(FILES ${GST_MACOS_PLUGINS} DESTINATION ${CMAKE_INSTALL_LIBDIR}/gstreamer-1.0) - - # cmake_path(CONVERT "${GSTREAMER_PREFIX}/libexec/gstreamer-1.0/*" TO_CMAKE_PATH_LIST GST_HELPER_BINS_PATH) - # file(GLOB GST_HELPER_BINS ${GST_HELPER_BINS_PATH}) - # install(FILES ${GST_HELPER_BINS} DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gstreamer-1.0) + if(EXISTS "${GSTREAMER_FRAMEWORK_PATH}") + # install( + # IMPORTED_RUNTIME_ARTIFACTS GStreamer::GStreamer + # FRAMEWORK DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}.app/Contents/Frameworks/GStreamer.framework" + # ) + install( + DIRECTORY "${GSTREAMER_FRAMEWORK_PATH}" + DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}.app/Contents/Frameworks" + PATTERN "*.la" EXCLUDE + PATTERN "*.a" EXCLUDE + PATTERN "*/include" EXCLUDE + PATTERN "*/Headers" EXCLUDE + ) + target_compile_definitions(gstqml6gl PUBLIC QGC_GST_MACOS_FRAMEWORK) + endif() elseif(ANDROID) if(CMAKE_HOST_WIN32) cmake_path(CONVERT "${GSTREAMER_PREFIX}/share/gst-android/ndk-build/tools/windows/*.dll" TO_CMAKE_PATH_LIST GST_WIN_TOOLS_PATH)