Platform-specific guides » macOS

Tips and tricks for macOS.

See also iOS.

Bundle creation

While graphical applications can run "as is", directly from the compiled executable, it's not possible to set various crucial properties of the app such as HiDPI support — for that you need to create a bundle, specifying its options through a *.plist file. If you use CMake, it provides a builtin file with a few options and you can use it like this:

add_executable(my-application main.cpp)
# ...
if(CORRADE_TARGET_APPLE)
    set_target_properties(my-application PROPERTIES
        MACOSX_BUNDLE ON
        MACOSX_BUNDLE_BUNDLE_NAME "My Application"
        MACOSX_BUNDLE_BUNDLE_IDENTIFIER "cz.mosra.magnum.my-application")
endif()

The builtin file doesn't include all possible properties, however it's possible to supply your own. A minimal file can look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key>
  <string>en-US</string>
  <key>CFBundleExecutable</key>
  <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
  <key>CFBundleIdentifier</key>
  <string>{{ package }}</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>{{ app_name }}</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
</dict>
</plist>

Replace {{ package }} with Java-like package name for your app (in this case it could be e.g. "cz.mosra.magnum.my_application", for example), {{ app_name }} with human-readable app name that's displayed in the system (so e.g. "My Application"). If you name it MacOSXBundleInfo.plist.in, it can be supplied to the bundle like below. The ${MACOSX_BUNDLE_EXECUTABLE_NAME} will get automatically replaced with the target executable name.

if(CORRADE_TARGET_APPLE)
    set_target_properties(my-application PROPERTIES
        MACOSX_BUNDLE ON
        MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist.in)
endif()

See the official Apple Property List file documentation for information about all options.

If you don't use CMake, these options can be set directly through Xcode UI, for example.

Bundling dylibs

If your application relies on dylibs (such as libGLESv2.dylib / libEGL.dylib for ANGLE), there's a few extra steps compared to static libraries to get them working. First, these have to be put into the Frameworks/ directory inside the bundle — for example like below if you have them referenced with a CMake project. Additionally, the files may need to get signed when copied:

set_source_files_properties(libGLESv2.dylib libEGL.dylib PROPERTIES
    MACOSX_PACKAGE_LOCATION Frameworks
    XCODE_FILE_ATTRIBUTES CodeSignOnCopy)

Then, the *.dylib files need to have their install name changed to contan a RPATH entry, so the executable linking to them will know it has to look for them in the RPATH. For 3rd party *.dylib files it can be done with the following command, dynamic libraries built directly by CMake should have this already set.

install_name_tool -id "@rpath/libGLESv2.dylib" libGLESv2.dylib
install_name_tool -id "@rpath/libEGL.dylib" libEGL.dylib

Finally, the application executable needs to have the RPATH pointed to the framework location:

set_target_properties(my-application PROPERTIES
    INSTALL_RPATH "@executable_path/../Frameworks")

HiDPI (Retina) support

macOS and iOS is the only platform where HiDPI support of an app can't be advertised programmatically. According to various sources, this capability is enabled by default on macOS 10.15+ and iOS 13+, for older versions you need to supply a custom *.plist file with NSHighResolutionCapable enabled:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  ...
  <key>NSHighResolutionCapable</key>
  <true/>
</dict>
</plist>

If you are using CMake, you can use the method described above to create a bundle with given file. Also note that this property is already included in CMake's builtin Info.plist on version 3.9 and newer.

Clang version mapping

Apple Clang has a different versioning scheme from upstream Clang, making it hard to know which Clang version corresponds to which Apple Clang version. Wikipedia has a handy version mapping table.

CMake exposes Apple Clang as AppleClang, so a complete check for e.g. upstream version 5 needs to look like this:

if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT
    CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0") OR
   (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT
    CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.3"))
    # Code requiring Clang 5 and newer
endif()

Best practices

Official Apple documentation:

Setting up macOS build on Travis CI

A lot of Travis features is shared between Linux and macOS, see Setting up Linux build on Travis CI for more information.

In general, a macOS build is done by adding the following to your .travis.yml matrix build. See the official documentation for more information.

matrix:
  include:
  - language: cpp
    os: osx
    compiler: clang

Most of the build setup can be shared with Linux, as both systems have roughly the same set of packages. For installing dependencies there's no builtin way, but you can use Homebrew. Be aware that calling for example brew install ninja by default causes Homebrew to update itself first. That currently (March 2018) takes almost two minutes. It's possible to skip the update by setting an environment variable as shown below, however this might fail in case you need a very recent version of a package.

HOMEBREW_NO_AUTO_UPDATE=1 brew install ninja

Troubleshooting

Build mysteriously fails due to some <cmath> errors

Happening usually after a system or Xcode upgrade, builds can start failing with errors like below. The exact reasons are unclear, can vary and happen only in certain cases (for example a manual build works but Homebrew package install doesn't).

In file included from ../src/MagnumPlugins/PngImageConverter/PngImageConverter.cpp:42:
In file included from /usr/local/include/Magnum/ImageView.h:35:
In file included from /usr/local/include/Magnum/PixelStorage.h:36:
In file included from /usr/local/include/Magnum/Math/Vector3.h:32:
In file included from /usr/local/include/Magnum/Math/Vector2.h:32:
In file included from /usr/local/include/Magnum/Math/Vector.h:37:
In file included from /usr/local/include/Corrade/Utility/StlMath.h:75:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/cmath:317:9: error: no member named 'signbit' in the global namespace
using ::signbit;
      ~~^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/cmath:318:9: error: no member named 'fpclassify' in the global namespace
using ::fpclassify;
      ~~^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/cmath:319:9: error: no member named 'isfinite' in the global namespace; did you mean 'finite'?
using ::isfinite;
      ~~^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/usr/include/math.h:752:12: note: 'finite' declared here
extern int finite(double)
           ^

In the above case it's because PngImageConverter (or PngImporter) uses libPNG, which depends on zlib. Zlib, along with curl and few others are the only libraries shipped directly with macOS, and that's the core of the problem. Here zlib got errorneously found in a different SDK than the remaining dependencies, causing two mutually incompatible include directories from a 11.0 and 10.15 SDK used together, as can be seen in the clang invocation (which gets displayed by ninja on failure or can be retreived from Homebrew logs):

clang++ … -I/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/usr/include … -o CMakeFiles/PngImageConverter.dir/PngImageConverter.cpp.o -c ../src/MagnumPlugins/PngImageConverter/PngImageConverter.cpp -isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk -isystem/usr/local/include

A magic incantation that should remove the curse, often suggested by Homebrew itself, is the following:

sudo rm -rf /Library/Developer/CommandLineTools
sudo xcode-select --install

Vulkan on macOS

Vulkan on macOS is implemented on top of Metal, and there are two possible ways:

  • Using MoltenVK directly. The library can supplement a Vulkan loader as well, which means you don't need two libraries, however this way you lose the possibility to have multiple devices (for example a SwiftShader software rendering). MoltenVK is directly available in Homebrew as molten-vk.
  • Using Vulkan Loader together with MoltenVK and possibly other device implementations. That way you'll get the benefits of a standard loader and device selection. The loader isn't available in Homebrew so you need to either install the whole several-hunred-megabyte SDK or build it from source.

Magnum's own FindVulkan.cmake will look for the Vulkan Loader first and if not found, falls back to searching for MoltenVK.

OpenGL on macOS

With Apple decision to focus on Metal, macOS OpenGL support is stuck on version 4.1 (i.e., two versions before compute shaders are available). Moreover, OpenGL is deprecated since macOS 10.14. If you don't want to rely on the deprecated OpenGL driver, you have an option to use Vulkan as shown above, go with ANGLE to translate OpenGL to Metal (details below), or go with Mesa Zink to translate OpenGL to Vulkan.

With the deprecated macOS drivers, these are the known issues:

Using ANGLE to translate OpenGL to Metal

See also ANGLE OpenGL ES translation layer for further information.