Build Qt as Universal Binary on macOS

Introduction

We will build a Universal Binary of the last available public Qt5 library 5.15.19 with Qt WebEngine using Xcode on macOS.

To address a common suggestion upfront, using Homebrew or macports to install Qt isn’t helpful in this case, as it doesn’t provide a universal build of Qt. For those aiming to work with an open-source version, you have to go the path of pain. Let’s try to make it less hard. And guess what’s new? Homebrew does not deliver Qt Webengine with Qt5.15.19. If you want garlic-player for serious digital signage you have to build Qt on your own.

Before

In the older version of this tutorial we used Xcode 14.0.1 on a macOS 13 (Ventura). The reason was that this is the last Xcode version which includes macOS 12 SDK. With a newer version of Xcode, you have to deal with SDK 13. You will be able after applying some patches to build the Qt lib, but Qt Webengine build will fail and fail. Apple, Qt, and Chromium like to fuck-up things and complicate them.

But the old tutorial worked only until Qt5.15.11. Starting from Qt5.15.12 Chromium throws again a tantrum and we get errors like:

./gen/third_party/blink/renderer/core/css/parser/at_rule_descriptors.cc:237:7: error: fallthrough annotation does not directly precede switch label
[[fallthrough]];

Apply Patches

What is going on?

Chromium’s gperf.py script post-processes gperf’s output and inserts an extra FALLTHROUGH; statement after each switch-case fallthrough. This made sense for old gperf versions (≤3.1), which only emitted a plain comment.

Since gperf 3.2, gperf emits [[fallthrough]]; on its own. Chromium’s script never learned about this, so it still inserts the redundant statement resulting in two fallthrough annotations in a row, with the extra one sitting directly before the case label. C++17 requires [[fallthrough]]; to be the very last thing before a case, nothing in between. The redundant statement breaks that rule, causing the mentioned error.

Since MacPorts (and most current systems) ship gperf 3.2+ while Qt 5.15.19 is frozen and will never get an upstream fix, this hits almost anyone building it today. Fix: patch gperf.py to stop inserting the redundant statement.

Finally: Even minor bugfix releases are not safe and any helper program can break things. That is why many tutorials fail after some time.

First I thought it is a clang 14 checking problem and I installed macOS 12 and Xcode 13.2, but the error occurred. So I wrote a patch for gperf.py and it worked.

Strongly general recommendation! Don’t fool around and try any newer Xcode or newer macOS than I recommend here. You will waste your time like me. With macOS 26 Tahoe and Xcode 26 you will only be able to compile qtbase after patching AGL from mac.conf. Webengine will show errors and errors because they use outdated libs from zlib, png etc. and you will need a patch orgy. If you find a way, I am open for suggestions.

Patch gperf

You can use the following bash script.

#!/bin/bash
set -e

QT_SRC=/Applications/Qt/5.15.19/Src # Change this to your path or put Qt in Applications like recommended

GPERF_PY="$QT_SRC/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/build/scripts/gperf.py"

if grep -q "'  FALLTHROUGH;')" "$GPERF_PY"; then
    echo "Patching gperf.py (FALLTHROUGH fix)..."
    cp "$GPERF_PY" "$GPERF_PY.orig"
    python3 << EOF
with open("$GPERF_PY", 'r') as f:
    content = f.read()

old = """gperf_output = gperf_output.replace('/*FALLTHROUGH*/',
                                            '  FALLTHROUGH;')"""
new = """gperf_output = gperf_output.replace('/*FALLTHROUGH*/', '')"""

assert old in content, "Pattern not found, check file manually"
content = content.replace(old, new)

with open("$GPERF_PY", 'w') as f:
    f.write(content)
EOF
    echo "gperf.py patched."
else
    echo "gperf.py already patched."
fi

Patch AGL out of mac.conf

If you want to use the Qt5 lib on macOS >= 26 you need another patch, too.

Starting with the macOS 26 (Tahoe) SDK, Apple removed the AGL.framework entirely. AGL was a relic of the old Carbon API from the early Mac OS X days. It was used to bring OpenGL rendering into Carbon applications. Like an appendix, it yu can remove this silent without anyone really noticing. Real OpenGL support never depended on it. Since mac.conf of Qt5.15.19 still references it for OpenGL support, configure or the later Xcode link step will fail with ld: framework 'AGL' not found. This patch removes AGL from mac.conf and links against OpenGL.framework directly instead, which has been available since the very first macOS X release.

You can use the following bash scripts.

#!/bin/bash
set -e

QT_SRC=/Applications/Qt/5.15.19/Src # Change this to your path or put Qt in Applications like recommended

cd "$QT_SRC/qtbase/mkspecs/common"
cp mac.conf mac.conf.orig
sed -i '' '/\/System\/Library\/Frameworks\/AGL\.framework\/Headers\/$/d' mac.conf
sed -i '' 's/QMAKE_INCDIR_OPENGL     = \\/QMAKE_INCDIR_OPENGL     = \/System\/Library\/Frameworks\/OpenGL.framework\/Headers/' mac.conf
sed -i '' '/\/System\/Library\/Frameworks\/OpenGL\.framework\/Headers \\\\$/d' mac.conf
sed -i '' 's/QMAKE_LIBS_OPENGL       = -framework OpenGL -framework AGL/QMAKE_LIBS_OPENGL       = -framework OpenGL/' mac.conf

Build Qt Lib

This process will be carried out in one step. In the other tutorials like Windows, we’ll construct the Qt Library while bypassing the Qt Webengine in the first step. Assembling the primary library is straightforward, especially when contrasted with the complexities of QWebengine. Additionally, this approach allows for the utilization of all available CPU cores without any worries.

But for some mysterious reasons this is not possible with macOS. The second step will throw errors mostly after 90%. Let’s do it in one step.

Requirements for Qt-Lib

Alternatively you can also use

Qt itself builds on macOS 12 with Xcode 13.2.

Note: If you have multiple Xcode versions installed on the same system (not needed if you follow the VM approach above), make sure the correct one is active:

xcode-select -p

If it doesn’t point to the right version, set DEVELOPER_DIR explicitly. e.g.:

export DEVELOPER_DIR=/Applications/Xcode_13_2_1.app/Contents/Developer
tar xvf qt-everywhere-opensource-src-5.15.19.tar.xz

I suggest creating a Qt directory and moving the extracted folder to Src

mkdir -p Qt/5.15.19/
mv qt-everywhere-opensource-src-5.15.19 Qt/5.15.19/Src

Requirements and Preparation for the Mistress of Annoyance: QWebengine

You need to install some software. I prefer macports because it installs in /opt and is more independent of the macOS libs. Which makes it slower to install, but more stable. You can also use Homebrew if you prefer.

Set path to ports for making things easier.

export PATH="/opt/local/bin:/opt/local/sbin:$PATH"

Install:

sudo port install gperf bison flex nodejs20 pkg-config

and for brew:

brew install gperf bison flex nodejs20 pkg-config

Python3 is included and Chromium in Qt5.15.19 will use it, but it needs html5lib.

/usr/bin/python3 -m ensurepip
/usr/bin/python3 -m pip install --user html5lib beautifulsoup4

otherwise Chromium compile will throw this error:

bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: html5lib

Build Steps for Qt-Lib

Open a Terminal, create a shadow directory, and enter it.

Utilizing a shadow build directory is highly beneficial when constructing Qt software or C++ applications. This method, which maintains a separation between source code and build artifacts, facilitates multiple build configurations, eases the cleanup process, and offers several other advantages.

mkdir build_qt
cd build_qt

Now call the configure script in the directory of the source.

$QT_SRC/configure QMAKE_APPLE_DEVICE_ARCHS="x86_64 arm64" \
    -release -optimize-size \
	-opensource -confirm-license -nomake tests -nomake examples \
    -no-openssl -securetransport \
	-webengine-proprietary-codecs \
    -prefix path/to/qt/5.15.19/clang_64 \
    -skip qttranslations \
    -skip qtgamepad \
    -skip qtdatavis3d \
    -skip qtcharts \
    -skip qtlottie \
	-skip qtserialport \
    -skip qt3d \
    -skip qtscript \
    -skip qtlocation \
	-skip qtwayland \
	-skip qtdoc \
	-skip qtpurchasing \
	-skip qtvirtualkeyboard \
	-skip qtspeech

Configure Explanations

This will build qmake and some other stuff.

In case of errors or something is missing, you will get a more or less detailed notice. If everything is fine, continue to the next step.

Start make

Call make with multicore support for M chips. Use more if you have more cores and space. With 16 GB RAM, it appears to be safe to use 8 cores.

make -j8

Have some patience. The build needs more than 2.5 hours on a M1 Pro MacBook. On an M4 with 8 cores in the Monterey VM it took about one hour.

Btw.: That is a good chance to hear the fan.

If everything goes well, you need to install the compiled lib to the -prefix directory.

make install

This will also take some minutes.

Share Your Thoughts

If you have any ideas, suggestions for improvement or you found a way to include QWebengine with newer Xcode, contact me. You can share your feedback by emailing me or give my project a star on GitHub.