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:
- GL::
AbstractShaderProgram:: validate() expects that the shader has a properly configured framebuffer bound along with proper GL:: Renderer setup. That is often hard to achieve, so the function cannot be portably used for shader validity testing. GL_TIMESTAMP
used by GL::TimeQuery:: timestamp() is not implemented on macOS and gives zero results.
Using ANGLE to translate OpenGL to Metal
See also ANGLE OpenGL ES translation layer for further information.