From 410e77be6caf3ca81953a0e94b5dd485703ec6d9 Mon Sep 17 00:00:00 2001 From: "L. E. Segovia" Date: Mon, 20 May 2024 11:46:13 -0300 Subject: [PATCH] cmake: Add Find Module to support Android and iOS consumers This commit adds a Find Module implementing the necessary logic to link against GStreamer, while implementing some extra bits to enhance the compatibility. The first addition is the `mobile` target, which implements the monolithic `gstreamer_android` library, and which here gains compatibility with Apple's operating systems. The second addition is the handling of the basic GStreamer libraries as `--whole-archive` when statically linked, which was ported from the ndk-build project in Cerbero. This is not easy to do, as CMake suffers from several issues that impede its proper usage of pkg-config: - It cannot differentiate between system/compiler specific libraries e.g. `-lm`, `-ldl`, but especially `-framework Cocoa`. - It does not support `--whole-archive` natively until 3.27 - It attempts to reorder flags blindly by separating them with spaces, thus requiring the use of `-Wl,` wrapping or (in the case of Apple frameworks) manual framework lookup The third addition is the port of the Fontconfig and ca-certificates bundling logic. Part-of: --- .../gstreamer/cmake/FindGStreamer.cmake | 449 ++++++++++++++++++ subprojects/gstreamer/cmake/meson.build | 9 + subprojects/gstreamer/meson.build | 1 + 3 files changed, 459 insertions(+) create mode 100644 subprojects/gstreamer/cmake/FindGStreamer.cmake create mode 100644 subprojects/gstreamer/cmake/meson.build diff --git a/subprojects/gstreamer/cmake/FindGStreamer.cmake b/subprojects/gstreamer/cmake/FindGStreamer.cmake new file mode 100644 index 0000000000..a7ff453431 --- /dev/null +++ b/subprojects/gstreamer/cmake/FindGStreamer.cmake @@ -0,0 +1,449 @@ +# SPDX-FileCopyrightText: 2024 L. E. Segovia +# SPDX-License-Ref: LGPL-2.1-or-later + +#[=======================================================================[.rst: +FindGStreamer +------- + +Finds the GStreamer library. Requires ``pkg-config`` to be installed. + +Configuration +^^^^^^^^^^^^^ + +This module can be configured with the following variables: + +``GStreamer_STATIC`` + Link against GStreamer statically (see below). + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``GStreamer::GStreamer`` + The GStreamer library. + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``GStreamer_FOUND`` + True if the system has the GStreamer library. +``GStreamer_VERSION`` + The version of the GStreamer library which was found. +``GStreamer_INCLUDE_DIRS`` + Include directories needed to use GStreamer. +``GStreamer_LIBRARIES`` + Libraries needed to link to GStreamer. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``GStreamer_INCLUDE_DIR`` + The directory containing ``gst/gstversion.h``. +``GStreamer_LIBRARY`` + The path to the GStreamer library. + +Configuration Variables +^^^^^^^^^^^^^^^ + +Setting the following variables is required, depending on the operating system: + +``GStreamer_ROOT_DIR`` + Installation prefix of the GStreamer SDK. + +``GStreamer_USE_STATIC_LIBS` + Set to ON to force the use of the static libraries. Default is OFF. + +``GStreamer_EXTRA_DEPS`` + pkg-config names of the extra dependencies that will be included whenever linking against GStreamer. + +#]=======================================================================] + +if (GStreamer_FOUND) + return() +endif() + +##################### +# Setup variables # +##################### + +if (NOT DEFINED GStreamer_ROOT_DIR AND DEFINED GSTREAMER_ROOT) + set(GStreamer_ROOT_DIR ${GSTREAMER_ROOT}) +endif() + +if (NOT GStreamer_ROOT_DIR) + set(GStreamer_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../../") +endif() + +if (NOT EXISTS "${GStreamer_ROOT_DIR}") + message(FATAL_ERROR "The directory GStreamer_ROOT_DIR=${GStreamer_ROOT_DIR} does not exist") +endif() + +if (NOT DEFINED GStreamer_USE_STATIC_LIBS) +set(GStreamer_USE_STATIC_LIBS OFF) +endif() + +# Set the environment for pkg-config +if (WIN32) + set(ENV{PKG_CONFIG_PATH} "${GStreamer_ROOT_DIR}/lib/pkgconfig;${GStreamer_ROOT_DIR}/lib/gstreamer-1.0/pkgconfig;${GStreamer_ROOT_DIR}/lib/gio/modules/pkgconfig") +else() + set(ENV{PKG_CONFIG_PATH} "${GStreamer_ROOT_DIR}/lib/pkgconfig:${GStreamer_ROOT_DIR}/lib/gstreamer-1.0/pkgconfig:${GStreamer_ROOT_DIR}/lib/gio/modules/pkgconfig") +endif() + +# Set the list of extra dependencies +if (NOT DEFINED GStreamer_EXTRA_DEPS) + set(GStreamer_EXTRA_DEPS) + if (DEFINED GSTREAMER_EXTRA_DEPS) + set(GStreamer_EXTRA_DEPS ${GSTREAMER_EXTRA_DEPS}) + endif() +endif() + +# Find libraries. This is meant to be used with static libraries +# (hence the reprioritization) but I've added a fallback to shared libraries +# and stub modules in case any are non-existent. +function(_gst_find_library LOCAL_LIB GST_LOCAL_LIB) + if (DEFINED ${GST_LOCAL_LIB}) + return() + endif() + + set(_gst_suffixes ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(_gst_prefixes ${CMAKE_FIND_LIBRARY_PREFIXES}) + if (APPLE) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".dylib" ".so" ".tbd") + set(CMAKE_FIND_LIBRARY_PREFIXES "" "lib") + elseif (UNIX) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".so") + set(CMAKE_FIND_LIBRARY_PREFIXES "" "lib") + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib") + set(CMAKE_FIND_LIBRARY_PREFIXES "" "lib") + endif() + + if ("${LOCAL_LIB}" IN_LIST _gst_IGNORED_SYSTEM_LIBRARIES) + set(${GST_LOCAL_LIB} ${LOCAL_LIB} PARENT_SCOPE) + else() + find_library(${GST_LOCAL_LIB} + ${LOCAL_LIB} + HINTS ${ARGN} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + REQUIRED + ) + set(${GST_LOCAL_LIB} "${${GST_LOCAL_LIB}}" PARENT_SCOPE) + if (NOT ${GST_LOCAL_LIB}) + message(FATAL_ERROR "${LOCAL_LIB} was unexpectedly not found.") + endif() + endif() + + set(CMAKE_FIND_LIBRARY_SUFFIXES ${_gst_suffixes}) + set(CMAKE_FIND_LIBRARY_PREFIXES ${_gst_prefixes}) +endfunction() + +macro(_gst_apply_link_libraries HIDE PC_LIBRARIES PC_HINTS GST_TARGET) + if (APPLE AND ${HIDE}) + target_link_directories(${GST_TARGET} INTERFACE + ${${PC_HINTS}} + ) + endif() + foreach(LOCAL_LIB IN LISTS ${PC_LIBRARIES}) + if (LOCAL_LIB MATCHES "${_gst_SRT_REGEX_PATCH}") + string(REGEX REPLACE "${_gst_SRT_REGEX_PATCH}" "\\1" LOCAL_LIB "${LOCAL_LIB}") + endif() + string(MAKE_C_IDENTIFIER "_gst_${LOCAL_LIB}" GST_LOCAL_LIB) + if (NOT ${GST_LOCAL_LIB}) + _gst_find_library(${LOCAL_LIB} ${GST_LOCAL_LIB} ${${PC_HINTS}}) + endif() + if ("${${GST_LOCAL_LIB}}" IN_LIST _gst_IGNORED_SYSTEM_LIBRARIES) + target_link_libraries(${GST_TARGET} INTERFACE + ${${GST_LOCAL_LIB}}) + elseif (APPLE AND ${HIDE}) + set(LOCAL_FILE) + get_filename_component(LOCAL_FILE ${${GST_LOCAL_LIB}} NAME) + target_link_libraries(${GST_TARGET} INTERFACE + "-hidden-l${LOCAL_FILE}") + elseif((UNIX OR ANDROID) AND ${HIDE}) + target_link_libraries(${GST_TARGET} INTERFACE + -Wl,--exclude-libs,${${GST_LOCAL_LIB}}) + else() + target_link_libraries(${GST_TARGET} INTERFACE + ${${GST_LOCAL_LIB}}) + endif() + endforeach() +endmacro() + +macro(_gst_filter_missing_directories GST_INCLUDE_DIRS) + set(_gst_include_dirs) + foreach(DIR IN LISTS ${GST_INCLUDE_DIRS}) + if ((IS_DIRECTORY "${DIR}") AND (EXISTS "${DIR}")) + list(APPEND _gst_include_dirs "${DIR}") + else() + message(WARNING "Skipping missing include folder ${DIR}.") + endif() + endforeach() + set(${GST_INCLUDE_DIRS} "${_gst_include_dirs}") +endmacro() + +macro(_gst_apply_frameworks PC_STATIC_LDFLAGS_OTHER GST_TARGET) + if (APPLE) + # LDFLAGS_OTHER may include framework linkage. Because CMake + # iterates over arguments separated by spaces, it doesn't realise + # that those arguments must not be split. + set(new_ldflags) + set(assemble_framework FALSE) + foreach(_arg IN LISTS ${PC_STATIC_LDFLAGS_OTHER}) + if (assemble_framework) + set(assemble_framework FALSE) + find_library(GST_${_arg}_LIB ${_arg} REQUIRED) + target_link_libraries(${GST_TARGET} + INTERFACE + "${GST_${_arg}_LIB}" + ) + elseif (_arg STREQUAL "-framework") + set(assemble_framework TRUE) + else() + set(assemble_framework FALSE) + list(APPEND new_ldflags "${_arg}") + endif() + endforeach() + set_target_properties(${GST_TARGET} PROPERTIES + INTERFACE_LINK_OPTIONS "${new_ldflags}" + ) + else() + set_target_properties(${TARGET} PROPERTIES + INTERFACE_LINK_OPTIONS "${${PC_STATIC_LDFLAGS_OTHER}}" + ) + endif() +endmacro() + +################################ +# Set up the targets # +################################ + +find_package(PkgConfig REQUIRED) + +# GStreamer's pkg-config modules are a MUST -- but we'll test them below +pkg_check_modules(PC_GStreamer gstreamer-1.0 ${GStreamer_EXTRA_DEPS}) +# Simulate the list that'll be wholearchive'd. +# Unfortunately, this uses an option only available with pkgconf. +# set(_old_pkg_config_executable "${PKG_CONFIG_EXECUTABLE}") +# set(PKG_CONFIG_EXECUTABLE ${PKG_CONFIG_EXECUTABLE} --maximum-traverse-depth=1) +# pkg_check_modules(PC_GStreamer_NoDeps QUIET REQUIRED gstreamer-1.0 ${GStreamer_EXTRA_DEPS}) +# set(PKG_CONFIG_EXECUTABLE "${_old_pkg_config_executable}") + +set(GStreamer_VERSION "${PC_GStreamer_VERSION}") + +# Test validity of the paths +# NOTE: only paths that must be considered are those provided by pkg-config +# NOTE 2: also exclude sysroots +find_path(GStreamer_INCLUDE_DIR + NAMES gst/gstversion.h + PATHS ${PC_GStreamer_INCLUDE_DIRS} + PATH_SUFFIXES gstreamer-1.0 + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + REQUIRED +) + +find_library(GStreamer_LIBRARY + NAMES gstreamer-1.0 + PATHS ${PC_GStreamer_LIBRARY_DIRS} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + REQUIRED +) + +# Android: Ignore these libraries when constructing the IMPORTED_LOCATION +set(_gst_IGNORED_SYSTEM_LIBRARIES c c++ unwind m dl atomic) +if (ANDROID) + list(APPEND _gst_IGNORED_SYSTEM_LIBRARIES log GLESv2 EGL OpenSLES android) +elseif(APPLE) + list(APPEND _gst_IGNORED_SYSTEM_LIBRARIES iconv resolv System) +endif() + +# Normalize library flags coming from srt/haisrt +# https://github.com/Haivision/srt/commit/b90b64d26f850fb0efcc4cdd8b31cbf74bd4db0c +set(_gst_SRT_REGEX_PATCH "^:lib(.+)\\.(a|so|lib|dylib)$") + +if(PC_GStreamer_FOUND AND (NOT TARGET GStreamer::GStreamer)) + # This is not UNKNOWN but INTERFACE, as we only intend to + # make a target suitable for downstream consumption. + # FindPkgConfig already takes care of things, however it is totally unable + # to discern between shared and static libraries when populating + # xxx_STATIC_LINK_LIBRARIES, so we need to populate them manually. + add_library(GStreamer::GStreamer INTERFACE IMPORTED) + + if (GStreamer_USE_STATIC_LIBS) + _gst_filter_missing_directories(PC_GStreamer_STATIC_INCLUDE_DIRS) + set_target_properties(GStreamer::GStreamer PROPERTIES + INTERFACE_COMPILE_OPTIONS "${PC_GStreamer_STATIC_CFLAGS_OTHER}" + ) + if (PC_GStreamer_STATIC_INCLUDE_DIRS) + set_target_properties(GStreamer::GStreamer PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PC_GStreamer_STATIC_INCLUDE_DIRS}" + ) + endif() + _gst_apply_frameworks(PC_GStreamer_STATIC_LDFLAGS_OTHER GStreamer::GStreamer) + else() + set_target_properties(GStreamer::GStreamer PROPERTIES + INTERFACE_COMPILE_OPTIONS "${PC_GStreamer_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${PC_GStreamer_INCLUDE_DIRS}" + INTERFACE_LINK_OPTIONS "${PC_GStreamer_LDFLAGS_OTHER}" + ) + endif() + + add_library(GStreamer::deps INTERFACE IMPORTED) + + if (NOT GStreamer_USE_STATIC_LIBS) + set_target_properties(GStreamer::deps PROPERTIES + INTERFACE_LINK_LIBRARIES "${PC_GStreamer_LINK_LIBRARIES}" + ) + # We're done + else() + # Handle all libraries, even those specified with -l:libfoo.a (srt) + # Due to the unavailability of pkgconf's `--maximum-traverse-depth` + # on stock pkg-config, I attempt to simulate it through the shared + # libraries listing. + # If pkgconf is available, replace all PC_GStreamer_ entries with + # PC_GStreamer_NoDeps and uncomment the code block above. + foreach(LOCAL_LIB IN LISTS PC_GStreamer_LIBRARIES) + # list(TRANSFORM REPLACE) is of no use here + # https://gitlab.kitware.com/cmake/cmake/-/issues/16899 + if (LOCAL_LIB MATCHES "${_gst_SRT_REGEX_PATCH}") + string(REGEX REPLACE "${_gst_SRT_REGEX_PATCH}" "\\1" LOCAL_LIB "${LOCAL_LIB}") + endif() + string(MAKE_C_IDENTIFIER "_gst_${LOCAL_LIB}" GST_LOCAL_LIB) + if (NOT ${GST_LOCAL_LIB}) + _gst_find_library(${LOCAL_LIB} ${GST_LOCAL_LIB} ${PC_GStreamer_STATIC_LIBRARY_DIRS}) + endif() + target_link_libraries(GStreamer::GStreamer INTERFACE + "${${GST_LOCAL_LIB}}" + ) + endforeach() + + _gst_apply_link_libraries(ON PC_GStreamer_STATIC_LIBRARIES PC_GStreamer_STATIC_LIBRARY_DIRS GStreamer::deps) + endif() + + target_link_libraries(GStreamer::GStreamer + INTERFACE + GStreamer::deps + ) +endif() + +foreach(_gst_PLUGIN IN LISTS GSTREAMER_PLUGINS) + # Safety valve for the custom targets above + if ("${_gst_plugin}" IN_LIST _gst_CUSTOM_TARGETS) + continue() + endif() + + if (TARGET GStreamer::${_gst_PLUGIN}) + continue() + endif() + + if (GStreamer_FIND_REQUIRED_${_gst_PLUGIN}) + set(_gst_PLUGIN_REQUIRED REQUIRED) + else() + set(_gst_PLUGIN_REQUIRED) + endif() + + pkg_check_modules(PC_GStreamer_${_gst_PLUGIN} "gst${_gst_PLUGIN}") + + set(GStreamer_${_gst_PLUGIN}_FOUND "${PC_GStreamer_${_gst_PLUGIN}_FOUND}") + if (NOT GStreamer_${_gst_PLUGIN}_FOUND) + continue() + endif() + + add_library(GStreamer::${_gst_PLUGIN} INTERFACE IMPORTED) + _gst_filter_missing_directories(PC_GStreamer_${_gst_PLUGIN}_INCLUDE_DIRS) + set_target_properties(GStreamer::${_gst_PLUGIN} PROPERTIES + INTERFACE_COMPILE_OPTIONS "${PC_GStreamer_${_gst_PLUGIN}_CFLAGS_OTHER}" + ) + if (PC_GStreamer_${_gst_PLUGIN}_INCLUDE_DIRS) + set_target_properties(GStreamer::${_gst_PLUGIN} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PC_GStreamer_${_gst_PLUGIN}_INCLUDE_DIRS}" + ) + endif() + if (GStreamer_USE_STATIC_LIBS) + _gst_apply_frameworks(PC_GStreamer_${_gst_PLUGIN}_STATIC_LDFLAGS_OTHER GStreamer::${_gst_PLUGIN}) + else() + set_target_properties(GStreamer::${_gst_PLUGIN} PROPERTIES + INTERFACE_LINK_OPTIONS "${PC_GStreamer_${_gst_PLUGIN}_LDFLAGS_OTHER}" + INTERFACE_LINK_LIBRARIES "${PC_GStreamer_${_gst_PLUGIN}_LINK_LIBRARIES}" + ) + # We're done + continue() + endif() + + # Handle all libraries, even those specified with -l:libfoo.a (srt) + _gst_apply_link_libraries(OFF PC_GStreamer_${_gst_PLUGIN}_STATIC_LIBRARIES PC_GStreamer_${_gst_PLUGIN}_STATIC_LIBRARY_DIRS GStreamer::${_gst_PLUGIN}) +endforeach() + +foreach(_gst_PLUGIN IN LISTS GSTREAMER_APIS) + # Safety valve for the custom targets above + if ("${_gst_plugin}" IN_LIST _gst_CUSTOM_TARGETS) + continue() + endif() + + if (TARGET GStreamer::${_gst_PLUGIN}) + continue() + endif() + + if (GStreamer_FIND_REQUIRED_${_gst_PLUGIN}) + set(_gst_PLUGIN_REQUIRED REQUIRED) + else() + set(_gst_PLUGIN_REQUIRED) + endif() + + string(REGEX REPLACE "^api_(.+)" "\\1" _gst_PLUGIN_PC "${_gst_PLUGIN}") + string(REPLACE "_" "-" _gst_PLUGIN_PC "${_gst_PLUGIN_PC}") + + pkg_check_modules(PC_GStreamer_${_gst_PLUGIN} "gstreamer-${_gst_PLUGIN_PC}-1.0") + + set(GStreamer_${_gst_PLUGIN}_FOUND "${PC_GStreamer_${_gst_PLUGIN}_FOUND}") + if (NOT GStreamer_${_gst_PLUGIN}_FOUND) + continue() + endif() + + add_library(GStreamer::${_gst_PLUGIN} INTERFACE IMPORTED) + _gst_filter_missing_directories(PC_GStreamer_${_gst_PLUGIN}_INCLUDE_DIRS) + set_target_properties(GStreamer::${_gst_PLUGIN} PROPERTIES + INTERFACE_COMPILE_OPTIONS "${PC_GStreamer_${_gst_PLUGIN}_CFLAGS_OTHER}" + INTERFACE_LINK_OPTIONS "${PC_GStreamer_${_gst_PLUGIN}_LDFLAGS_OTHER}" + ) + if (PC_GStreamer_${_gst_PLUGIN}_INCLUDE_DIRS) + set_target_properties(GStreamer::${_gst_PLUGIN} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PC_GStreamer_${_gst_PLUGIN}_INCLUDE_DIRS}" + ) + endif() + if (GStreamer_USE_STATIC_LIBS) + _gst_apply_frameworks(PC_GStreamer_${_gst_PLUGIN}_STATIC_LDFLAGS_OTHER GStreamer::${_gst_PLUGIN}) + else() + set_target_properties(GStreamer::${_gst_PLUGIN} PROPERTIES + INTERFACE_LINK_OPTIONS "${PC_GStreamer_${_gst_PLUGIN}_LDFLAGS_OTHER}" + INTERFACE_LINK_LIBRARIES "${PC_GStreamer_${_gst_PLUGIN}_LINK_LIBRARIES}" + ) + # We're done + continue() + endif() + + # Handle all libraries, even those specified with -l:libfoo.a (srt) + _gst_apply_link_libraries(OFF PC_GStreamer_${_gst_PLUGIN}_STATIC_LIBRARIES PC_GStreamer_${_gst_PLUGIN}_STATIC_LIBRARY_DIRS GStreamer::${_gst_PLUGIN}) +endforeach() + +# Perform final validation +include(FindPackageHandleStandardArgs) +set(_gst_handle_version_range) +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.19.0") + set(_gst_handle_version_range "HANDLE_VERSION_RANGE") +endif() +find_package_handle_standard_args(GStreamer + REQUIRED_VARS + GStreamer_LIBRARY + GStreamer_INCLUDE_DIR + VERSION_VAR GStreamer_VERSION + ${_gst_handle_version_range} + HANDLE_COMPONENTS +) diff --git a/subprojects/gstreamer/cmake/meson.build b/subprojects/gstreamer/cmake/meson.build new file mode 100644 index 0000000000..7b63d5aee4 --- /dev/null +++ b/subprojects/gstreamer/cmake/meson.build @@ -0,0 +1,9 @@ +find_module = files( + 'FindGStreamer.cmake', +) + +install_data( + find_module, + install_dir: get_option('datadir') / 'cmake' +) + diff --git a/subprojects/gstreamer/meson.build b/subprojects/gstreamer/meson.build index bfd6c412d9..4560218a5f 100644 --- a/subprojects/gstreamer/meson.build +++ b/subprojects/gstreamer/meson.build @@ -679,6 +679,7 @@ endif gst_libraries = [] +subdir('cmake') subdir('gst') subdir('libs') subdir('plugins')