Skip to content

Instantly share code, notes, and snippets.

@agirault
Last active May 29, 2020 13:18
Show Gist options
  • Save agirault/3244bf956c2cad7217b148291135f85e to your computer and use it in GitHub Desktop.
Save agirault/3244bf956c2cad7217b148291135f85e to your computer and use it in GitHub Desktop.
CMake configuration for a shared ios framework linking against static Qt5 libs to be embedded in an iOS app
# cmake /path/to/src \
# -GXcode \ # or Ninja
# -DQt5_DIR=/path/to/Qt/5.13.0/ios/lib/cmake/Qt5 \
# -DCMAKE_SYSTEM_NAME=iOS \
# -DCMAKE_OSX_DEPLOYMENT_TARGET=11 \
# -DCMAKE_INSTALL_PREFIX=/usr/local/frameworks \
# -DCMAKE_OSX_ARCHITECTURES="arm64" \ # arm64 for device, x86_64 for simulator (but x86_64 not in installed Qt5 ios static libs?)
cmake_minimum_required (VERSION 3.14 FATAL_ERROR)
project (Foo VERSION 1.0 LANGUAGES C CXX)
# Find QT
set(QT5_MODULES
Core
Widgets
Gui
Quick
Qml
# other modules needed...
)
find_package (Qt5
COMPONENTS ${QT5_MODULES}
REQUIRED
)
# Options for QT
set (CMAKE_AUTOMOC ON)
set (CMAKE_AUTORCC ON)
set (CMAKE_AUTOUIC ON)
# Set files
set (PRIVATE_HEADER_DIRS
# private include directories
)
set (PUBLIC_HEADER_DIRS
# public include directories
)
set (PRIVATE_HEADERS
# private include files
)
set (PUBLIC_HEADERS
# public include files
)
set (SOURCES
# source files (c, cpp, mm)
)
set (RESOURCES
# qrc files
)
set (UIS
# ui files
)
# Create library
add_library (${PROJECT_NAME} SHARED
${PUBLIC_HEADERS} # needed for set_target_properties to work for framework
${PRIVATE_HEADERS} # needed for set_target_properties to work for framework
${SOURCES}
${RESOURCES}
${UIS}
)
# Find Qt targets for modules and plugins
# Note: Missing more things?
# Should be fixed in 5.14: https://bugreports.qt.io/browse/QTBUG-38913
set (QT5_TARGETS "")
foreach (QT5_MODULE ${QT5_MODULES})
set (QT5_TARGET Qt5::${QT5_MODULE})
list (APPEND QT5_TARGETS ${QT5_TARGET})
set (QT5_MODULE_PLUGINS ${Qt5${QT5_MODULE}_PLUGINS})
if (QT5_MODULE_PLUGINS)
list(APPEND QT5_TARGETS ${QT5_MODULE_PLUGINS})
endif()
endforeach()
# Link
target_link_libraries (${PROJECT_NAME}
PUBLIC
${QT5_TARGETS}
# other dependency targets
)
# Include directories
# Note: not needed if not exporting since we are already creating a framework?
target_include_directories (${PROJECT_NAME}
PRIVATE ${PRIVATE_HEADER_DIRS}
PUBLIC ${PUBLIC_HEADER_DIRS}
)
# Add BITCODE for other generators than Xcode
# Note: Does not seem to last in install tree
target_compile_options(${PROJECT_NAME}
PUBLIC
"-fembed-bitcode"
)
# Framework
set_target_properties (${PROJECT_NAME} PROPERTIES
FRAMEWORK TRUE
INSTALL_NAME_DIR "@rpath" # Note: did not seem to work with MACOSX_RPATH TRUE instead. Needed to embed the framework in an iOS app
# BUILD_WITH_INSTALL_NAME_DIR TRUE # Needed to set install_name to @rpath in build tree also
PRIVATE_HEADER "${PRIVATE_HEADERS}" # Needed for set_target_properties to work for framework
PUBLIC_HEADER "${PUBLIC_HEADERS}" # Needed for set_target_properties to work for framework
MACOSX_FRAMEWORK_IDENTIFIER "com.your-organization.${PROJECT_NAME}" #CFBundleIdentifier
MACOSX_FRAMEWORK_SHORT_VERSION_STRING "${PROJECT_VERSION}" #CFBundleShortVersionString
#MACOSX_FRAMEWORK_BUNDLE_VERSION B001 #CFBundleVersion
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer"
)
# Install
install (TARGETS ${PROJECT_NAME}
FRAMEWORK DESTINATION . # relative to CMAKE_INSTALL_PREFIX
PRIVATE_HEADER
PUBLIC_HEADER
)
@agirault
Copy link
Author

agirault commented Aug 8, 2019

Using:

  • CMake 3.14.3
  • Qt 5.13.0

This CMake config file manages to create a framework with headers, and a shared library (foo) with a proper @rpath in its install name. That framework can then be embedded in an iOS app directly in XCode successfully.

However, there are a couple of issues left with using Qt5 ios static libraries:

Remaining issues

1. Missing Qt platform plugin "ios" (and others plugins?)

Even though the foo library and the iOS App build fine, the current configuration without any additional code will trigger the following error message at runtime when the app loads the foo library:

[qt.qpa.plugin] Could not find the Qt platform plugin "ios" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

This is because the ios static plugin is not imported, which can be done by adding Q_IMPORT_PLUGIN(QIOSIntegrationPlugin) in code within the foo library.

Doing this triggers the next issue, so it's uncertain if this is enough to fix it. Also, there are probably more plugins that need to be loaded after that.

2. Linking issues against static Qt5 libs

Might be related to:

2.1. Missing plugins

Adding Q_IMPORT_PLUGIN for the ios plugin caused the foo library build to fail at link time when only linking against the qt5 module targets and not linking against the qt5 plugin targets:

Undefined symbols for architecture arm64:
  "qt_static_plugin_QIOSIntegrationPlugin()", referenced from:
      FooFunction()::StaticQIOSIntegrationPluginPluginInstance::StaticQIOSIntegrationPluginPluginInstance() in foo.o

Analyzing the linker showed that the foo library is not linking against libqios.a, which is located in /ios/plugins/platforms/ of the Qt5 install directory. A look at the content of /ios/lib/cmake/Qt5Gui shows that Qt5Gui_QIOSIntegrationPlugin.cmake defines a CMake target Qt5::QIOSIntegrationPlugin, that is stored in a CMake variable Qt5Gui_PLUGINS. This is the same template for all plugins of every module, so we should be able to retrieve all the needed Qt5 targets (modules and plugins) with this:

set (QT5_TARGETS "")
foreach (QT5_MODULE ${QT5_MODULES})
  set (QT5_TARGET Qt5::${QT5_MODULE})
  list (APPEND QT5_TARGETS ${QT5_TARGET})
  set (QT5_MODULE_PLUGINS ${Qt5${QT5_MODULE}_PLUGINS})
  if (QT5_MODULE_PLUGINS)
    list(APPEND QT5_TARGETS ${QT5_MODULE_PLUGINS})
  endif()
endforeach()

This properly add the ios plugin static library to the linker command, as well as all the other plugins (too many?) from the linked qt5 modules. However, this leads to the following linker errors:

2.2. Missing frameworks and libs

Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_CAEAGLLayer", referenced from:
      objc-class-ref in libqios.a(quiview.o)
  "_OBJC_METACLASS_$_CALayer", referenced from:
      _OBJC_METACLASS_$_QIOSLoupeLayer in libqios.a(qiostextinputoverlay.o)
      _OBJC_METACLASS_$_QIOSHandleLayer in libqios.a(qiostextinputoverlay.o)
  "_OBJC_CLASS_$_CALayer", referenced from:
      _OBJC_CLASS_$_QIOSLoupeLayer in libqios.a(qiostextinputoverlay.o)
      _OBJC_CLASS_$_QIOSHandleLayer in libqios.a(qiostextinputoverlay.o)
      objc-class-ref in libqios.a(qiostextinputoverlay.o)
  "_kCATransactionDisableActions", referenced from:
      -[QIOSLoupeLayer display] in libqios.a(qiostextinputoverlay.o)
      -[QIOSHandleLayer setCursorRectangle:] in libqios.a(qiostextinputoverlay.o)
      -[QIOSSelectionRecognizer updateSelection] in libqios.a(qiostextinputoverlay.o)
  "_OBJC_CLASS_$_CABasicAnimation", referenced from:
      objc-class-ref in libqios.a(qiosinputcontext.o)
      objc-class-ref in libqios.a(qiostextinputoverlay.o)
  "_CATransform3DMakeTranslation", referenced from:
      QIOSInputContext::scroll(int) in libqios.a(qiosinputcontext.o)
  "_CATransform3DMakeScale", referenced from:
      -[QIOSLoupeLayer initWithSize:cornerRadius:bottomOffset:] in libqios.a(qiostextinputoverlay.o)
      ___29-[QIOSLoupeLayer setVisible:]_block_invoke in libqios.a(qiostextinputoverlay.o)
      -[QIOSHandleLayer display] in libqios.a(qiostextinputoverlay.o)
  "typeinfo for QRasterBackingStore", referenced from:
      typeinfo for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "QRasterBackingStore::toImage() const", referenced from:
      vtable for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "QRasterBackingStore::resize(QSize const&, QRegion const&)", referenced from:
      vtable for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "QRasterBackingStore::scroll(QRegion const&, int, int)", referenced from:
      vtable for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "_OBJC_CLASS_$_CATransaction", referenced from:
      objc-class-ref in libqios.a(qiostextinputoverlay.o)
  "QRasterBackingStore::format() const", referenced from:
      vtable for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "QRasterBackingStore::beginPaint(QRegion const&)", referenced from:
      vtable for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "_AudioServicesPlayAlertSound", referenced from:
      QIOSIntegration::beep() const in libqios.a(qiosintegration.o)
      non-virtual thunk to QIOSIntegration::beep() const in libqios.a(qiosintegration.o)
  "QRasterBackingStore::QRasterBackingStore(QWindow*)", referenced from:
      QIOSBackingStore::QIOSBackingStore(QWindow*) in libqios.a(qiosbackingstore.o)
  "QMacInternalPasteboardMime::destroyMimeTypes()", referenced from:
      QIOSIntegration::~QIOSIntegration() in libqios.a(qiosintegration.o)
  "QMacInternalPasteboardMime::all(unsigned char)", referenced from:
      QIOSMimeData::retrieveData(QString const&, QVariant::Type) const in libqios.a(qiosclipboard.o)
      QIOSClipboard::setMimeData(QMimeData*, QClipboard::Mode) in libqios.a(qiosclipboard.o)
  "QCoreTextFontDatabase::addApplicationFont(QByteArray const&, QString const&)", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QMacInternalPasteboardMime::flavorToMime(unsigned char, QString)", referenced from:
      QIOSMimeData::formats() const in libqios.a(qiosclipboard.o)
  "typeinfo for QCoreTextFontDatabase", referenced from:
      typeinfo for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QRasterBackingStore::~QRasterBackingStore()", referenced from:
      QIOSBackingStore::QIOSBackingStore(QWindow*) in libqios.a(qiosbackingstore.o)
      QIOSBackingStore::~QIOSBackingStore() in libqios.a(qiosbackingstore.o)
      QIOSBackingStore::~QIOSBackingStore() in libqios.a(qiosbackingstore.o)
  "_OBJC_CLASS_$_CAKeyframeAnimation", referenced from:
      objc-class-ref in libqios.a(qiostextinputoverlay.o)
  "QCoreTextFontDatabase::~QCoreTextFontDatabase()", referenced from:
      QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::~QCoreTextFontDatabaseEngineFactory() in libqios.a(qiosintegration.o)
      QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::~QCoreTextFontDatabaseEngineFactory() in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::populateFamily(QString const&)", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::invalidate()", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::releaseHandle(void*)", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QRasterBackingStore::paintDevice()", referenced from:
      vtable for QIOSBackingStore in libqios.a(qiosbackingstore.o)
  "QCoreTextFontDatabase::fontsAlwaysScalable() const", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::populateFamilyAliases()", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "_main", referenced from:
      user_main_trampoline() in libqios.a(qioseventdispatcher.o)
     (maybe you meant: _qt_main_wrapper)
  "_CATransform3DEqualToTransform", referenced from:
      QIOSInputContext::scroll(int) in libqios.a(qiosinputcontext.o)
  "QCoreTextFontDatabase::defaultFont() const", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::isPrivateFontFamily(QString const&) const", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::fallbacksForFamily(QString const&, QFont::Style, QFont::StyleHint, QChar::Script) const", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::standardSizes() const", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(QByteArray const&, double, QFont::HintingPreference)", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::themeFonts() const", referenced from:
      QIOSTheme::font(QPlatformTheme::Font) const in libqios.a(qiostheme.o)
  "_OBJC_CLASS_$_CAShapeLayer", referenced from:
      objc-class-ref in libqios.a(qiostextinputoverlay.o)
  "QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(QFontDef const&, void*)", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::QCoreTextFontDatabase()", referenced from:
      QIOSIntegration::QIOSIntegration() in libqios.a(qiosintegration.o)
  "QCoreTextFontDatabase::populateFontDatabase()", referenced from:
      vtable for QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine> in libqios.a(qiosintegration.o)
  "QMacInternalPasteboardMime::initializeMimeTypes()", referenced from:
      QIOSIntegration::initialize() in libqios.a(qiosintegration.o)

Those symbols are from:

  • the QuartzCore framework for the _OBJC_CLASS_ symbols
  • the Qt5FontDatabaseSupport library for the QCoreTextFontDatabase symbols
  • the Qt5GraphicsSupport library for the QRasterBackingStore symbols
  • the Qt5ClipboardSupport library for the QMacInternalPasteboardMime symbols
  • the ImageIO framework for the _CGImage symbols (after adding Qt5ClipboardSupport)
  • the AudioToolbox framework for the _AudioServicesPlayAlertSound symbol

Adding -framework <FrameworkName> as well as the qt5 libraries (manually) to foo's target_link_libraries resolved most of them, but then leads to the latest error:

2.3. Missing frameworks and libs

"Undefined symbols for architecture arm64:
  "_main", referenced from:
      user_main_trampoline() in libqios.a(qioseventdispatcher.o)
     (maybe you meant: _qt_main_wrapper)

The "_main" symbol is required for this library. This was brought up here also and saying it just was not supported.

Alexandru Croitor (Qt) provided this explanation:

For the undefined symbol main bit, funnily enough the linker actually mentioned part of the solution.
You can see what qmake does at https://code.qt.io/cgit/qt/qtbase.git/tree/mkspecs/features/uikit/qt.prf?h=5.12.4#n29 Essentially adding a target_link_options(target PRIVATE "-e _qt_main_wrapper") should hopefully fix the issue.

However, this would work maybe when linking against an executable (with a loader), but it would not work for the shared library, to what Alexandru answered:

I see now that you want to link all the qt static libs into a dynamic framework. I don't think that's gonna work out of the box, because when you link all the static libs into the dynamic framework, the linker obviously expects to resolve all the symbols, and main won't be found anywhere, and that's an assumption that we currently enforce for the static Qt iOS build. Internally, we talked just a little bit that for Qt 6 we might want to split the _qt_main_wrapper symbol into a separate static library, so we could support a dynamic library Qt build for iOS, and I think this would have helped you in the particular case you have. But I don't think there are any plans for supporting it in Qt 5.

Aka: creating a shared library that links against qt5 static libraries is simply not supported at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment