Homepage

A workaround for "framework 'AGL' not found" for Qt on macOS

Non-commercial prebuilt Qt versions below 6.9 currently fail to link on macOS Tahoe (macOS 26). Two quick specific examples are Qt 6.5.3 and 6.8.3, both offered in the maintenance tool at the time of writing.

Applications compile fine, but the linker dies:

ld: framework 'AGL' not found

AGL is Apple's old OpenGL-on-Carbon shim. It has been deprecated for years and was finally removed from the macOS SDK in Tahoe. Nothing in user code references it, so where is the AGL reference coming from?

The failing link command has -framework AGL in it explicitly:

/usr/bin/c++ ... -o bin/MyApp \
    ...
    -framework AGL
    ...

This comes in as a public link dependency of Qt6::Gui, and the source is Qt's bundled FindWrapOpenGL.cmake, which ships inside every non-commercial Qt release until 6.9:

if(APPLE)
    # ...resolve OpenGL framework path into __opengl_fw_path...

    find_library(WrapOpenGL_AGL NAMES AGL)
    if(WrapOpenGL_AGL)
        set(__opengl_agl_fw_path "${WrapOpenGL_AGL}")
    endif()
    if(NOT __opengl_agl_fw_path)
        set(__opengl_agl_fw_path "-framework AGL")
    endif()

    target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_fw_path})
    target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_agl_fw_path})
endif()

This explains why it silently worked for years: on a pre-Tahoe SDK, find_library(WrapOpenGL_AGL NAMES AGL) succeeded and set an absolute path to the AGL framework, which the linker happily resolved. On Tahoe, find_library returns nothing, the code falls back to the literal "-framework AGL", and the linker errors out.

Qt fixed this in 6.9 by deleting the entire AGL block (the find_library, the two ifs, and the trailing target_link_libraries). If you can move to 6.9, it's advisable to do so.

Otherwise, you need to either patch Qt in place (not pretty!) or preempt Qt's wrapper.

The trick

CMake's find_package(Foo ...) walks CMAKE_MODULE_PATH looking for a file named FindFoo.cmake before it falls back to Qt's bundled copy. So if we drop our own FindWrapOpenGL.cmake somewhere on the project, point CMAKE_MODULE_PATH at that directory, and make sure the directory comes first in the search order, ours wins.

The override file is a verbatim copy of Qt 6.7's module with the AGL block removed. The deletion matches the upstream 6.9 patch line for line. Drop it at ./cmake/FindWrapOpenGL.cmake:

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#
# Modified from qtbase/cmake/FindWrapOpenGL.cmake: AGL block removed
# to support macOS Tahoe. Matches the upstream Qt 6.9 fix.

if(TARGET WrapOpenGL::WrapOpenGL)
    set(WrapOpenGL_FOUND ON)
    return()
endif()

set(WrapOpenGL_FOUND OFF)

find_package(OpenGL ${WrapOpenGL_FIND_VERSION})

if (OpenGL_FOUND)
    set(WrapOpenGL_FOUND ON)
    add_library(WrapOpenGL::WrapOpenGL INTERFACE IMPORTED)
    if(APPLE)
        get_target_property(__opengl_fw_lib_path OpenGL::GL IMPORTED_LOCATION)
        if(__opengl_fw_lib_path AND NOT __opengl_fw_lib_path MATCHES "/([^/]+)\\.framework$")
            get_filename_component(__opengl_fw_path "${__opengl_fw_lib_path}" DIRECTORY)
        endif()
        if(NOT __opengl_fw_path)
            set(__opengl_fw_path "-framework OpenGL")
        endif()
        target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_fw_path})
    else()
        target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE OpenGL::GL)
    endif()
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(WrapOpenGL DEFAULT_MSG WrapOpenGL_FOUND)

This is Qt's own module with only the AGL-resolving block removed (marginally less ugly than patching Qt in-place). The early-return guard, the find_package(OpenGL) call, the OpenGL framework path resolution, and the find_package_handle_standard_args at the end are all unchanged.

Since this file is a derivative of Qt's, the you should preserve Qt's BSD-3-Clause copyright notice.

Wiring it in

Two ways to make CMake pick up the override. Either edit your top-level CMakeLists.txt:

list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

find_package(Qt6 REQUIRED COMPONENTS Gui)

Or pass it on the cmake command line:

cmake -S . -B build -DCMAKE_MODULE_PATH="$(pwd)/cmake"

Either works. PREPEND matters if CMAKE_MODULE_PATH already has entries, since the override needs to be checked before Qt's path.

If the build has more than one cmake invocation (a separate plugin build, a vendored dependency that also calls find_package(Qt6 ... Gui ...)), each one needs the override. Each cmake project independently resolves WrapOpenGL and independently links the broken framework. Patch one and miss the other, and the link error just moves.

Verifying the override is picked up

When CMAKE_MODULE_PATH is wired up correctly but the file at cmake/FindWrapOpenGL.cmake is missing or misnamed, CMake silently falls back to Qt's bundled module and the link keeps failing with the same AGL error. The cmake output does not flag the fallback.

To confirm the override is winning, put a message(WARNING "using local FindWrapOpenGL") at the top of the copy and re-run cmake. If the warning does not appear, the module path is wrong or the filename is wrong.

See also