blob: af039cbbaba94bf9ea125566c59b420890512365 [file] [log] [blame]
# Copyright (C) 2022 Igalia S.L.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#[=======================================================================[.rst:
FindGI
------
Finds the GObject-Introspection tools and adds the :command:`GI_INTROSPECT`
command. The following variables will also be set:
``GI_FOUND``
True if the GObject-Introspection tools are available.
``GI_VERSION``
Version of the GObject-Introspection tools.
``GI_SCANNER_EXE``
Path to the ``g-ir-scanner`` program.
``GI_COMPILER_EXE``
Path to the ``g-ir-compiler`` program.
``GI_GIRDIR``
Path where to install ``.gir`` files in the target system.
``GI_TYPELIBDIR``
Path where to install ``.typelib`` files in the target system.
``GI_HAVE_SOURCES_TOP_DIRS``
Whether the introspection scannner supports the ``--sources-top-dirs=``
command line flag.
#]=======================================================================]
# Add a dummy command in case introspection is disabled. This allows
# always use it and automatically have it be a noop in that case, instead
# of having a check next to each invocation.
if (NOT ENABLE_INTROSPECTION)
function(GI_INTROSPECT)
endfunction()
return()
endif ()
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
if (PACKAGE_FIND_VERSION_COUNT GREATER 0)
set(_gi_version_cmp ">=${PACKAGE_FIND_VERSION}")
endif ()
pkg_check_modules(PC_GI gobject-introspection-1.0${_gi_version_cmp})
if (PC_GI_FOUND)
pkg_get_variable(_GI_SCANNER_EXE gobject-introspection-1.0 g_ir_scanner)
pkg_get_variable(_GI_COMPILER_EXE gobject-introspection-1.0 g_ir_compiler)
pkg_get_variable(_GI_GIRDIR gobject-introspection-1.0 girdir)
pkg_get_variable(_GI_TYPELIBDIR gobject-introspection-1.0 typelibdir)
pkg_get_variable(_GI_PREFIX gobject-introspection-1.0 prefix)
set(GI_VERSION ${PC_GI_VERSION})
endif ()
endif ()
find_program(GI_SCANNER_EXE NAMES ${_GI_SCANNER_EXE} g-ir-scanner)
find_program(GI_COMPILER_EXE NAMES ${_GI_COMPILER_EXE} g-ir-compiler)
include(GNUInstallDirs)
if (_GI_GIRDIR AND _GI_PREFIX)
string(FIND "${_GI_GIRDIR}" "${_GI_PREFIX}" _idx)
if (_idx EQUAL 0)
string(LENGTH "${_GI_PREFIX}" _idx)
string(SUBSTRING "${_GI_GIRDIR}" ${_idx} -1 _GI_GIRDIR)
set(_GI_GIRDIR "${CMAKE_INSTALL_PREFIX}/${_GI_GIRDIR}")
else ()
unset(_GI_GIRDIR)
endif ()
endif ()
if (NOT _GI_GIRDIR)
set(_GI_GIRDIR "${CMAKE_INSTALL_DATADIR}/gir-1.0")
endif ()
if (_GI_TYPELIBDIR AND _GI_PREFIX)
string(FIND "${_GI_TYPELIBDIR}" "${_GI_PREFIX}" _idx)
if (_idx EQUAL 0)
string(LENGTH "${_GI_PREFIX}" _idx)
string(SUBSTRING "${_GI_TYPELIBDIR}" ${_idx} -1 _GI_TYPELIBDIR)
set(_GI_TYPELIBDIR "${CMAKE_INSTALL_PREFIX}/${_GI_TYPELIBDIR}")
else ()
unset(_GI_TYPELIBDIR)
endif ()
endif ()
if (NOT _GI_TYPELIBDIR)
set(_GI_TYPELIBDIR "${CMAKE_INSTALL_LIBDIR}/girepository-1.0")
endif ()
set(GI_GIRDIR "${_GI_GIRDIR}" CACHE PATH "Path to installed .gir files")
set(GI_TYPELIBDIR "${_GI_TYPELIBDIR}" CACHE PATH "Path to installed .typelib files")
if (NOT GI_VERSION AND GI_SCANNER_EXE)
execute_process(
COMMAND "${GI_SCANNER_EXE}" --version
OUTPUT_VARIABLE GI_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
ERROR_QUIET
)
if (GI_VERSION MATCHES "^g-ir-scanner[[:space:]]+([0-9.]+)")
set(GI_VERSION ${CMAKE_MATCH_1})
else ()
unset(GI_VERSION)
endif ()
endif ()
if (GI_VERSION VERSION_GREATER_EQUAL 1.59.1)
set(GI_HAVE_SOURCES_TOP_DIRS TRUE)
else ()
set(GI_HAVE_SOURCES_TOP_DIRS FALSE)
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GI
REQUIRED_VARS GI_SCANNER_EXE GI_COMPILER_EXE GI_GIRDIR GI_TYPELIBDIR
VERSION_VAR GI_VERSION
)
if (NOT GI_FOUND)
return()
endif ()
define_property(TARGET
PROPERTY GI_GIR_PATH
BRIEF_DOCS "Path to .gir file"
FULL_DOCS "Path to .gir file generated by the target"
)
define_property(TARGET
PROPERTY GI_PACKAGE
BRIEF_DOCS "Exported package"
FULL_DOCS "Name of the pkg-config package for the target"
)
#[=======================================================================[.rst:
.. command:: GI_INTROSPECT
.. code-block:: cmake
GI_INTROSPECT(<namespace> <nsversion> <header>
[TARGET <target>]
[SYMBOL_PREFIX <string>]
[IDENTIFIER_PREFIX <string>]
[PACKAGE <pkgname>]
[DEPENDENCIES <dependency>...]
[SOURCES <file>...
[NO_IMPLICIT_SOURCES])
Enables generating introspection data for a library ``<target>``, which
will make the introspected API available in the ``<namespace>-<nsversion>``
module. Both ``.gir`` and ``.typelib`` will be built and configured for
installation.
The ``<header>`` argument indicates how to include the top-level *public*
API header for the library, for example ``gtk/gtk.h``.
``TARGET`` specifies the name of the CMake used to build the library to scan
for introspection data. If not specified, the default value is the same as
the ``<namespace>``.
``SYMBOL_PREFIX`` specifies the prefix of symbols (functions) to scan for.
If not specified, the default value is the ``<namespace>`` converted to
lowercase.
``IDENTIFIER_PREFIX`` specifies the prefix of identifiers (types) to scan
for. If not specified, the default value is the ``<namespace>`` converted
to uppercase.
``PACKAGE`` indicates the ``pkg-config`` package exported by the generated
``.gir``. If not specified, the default value is the ``<namespace>``
converted to lowercase.
``DEPENDENCIES`` specifies an optional list of dependencies of the library
being scanned for introspection data. Each dependecy can be one of:
* A GObject-Introspection module name, e.g. ``GObject-2.0``.
* A GObject-Introspection module name, a colon, and the name of its
``pkg-config`` package, e.g. ``Gtk-4.0:gtk4``. This is useful for
those modules where both names don't match.
* Another ``<namespace>`` from a previous usage of the command. This
will add its ``.gir`` as an uninstalled dependency and ensure that
dependencies are built beforehand.
By default the sources scanned for introspection documentation comments
are those used to build the ``<target>`` library, plus those specified
with ``SOURCES``. Adding the ``NO_IMPLICIT_SOURCES`` flag uses only the
latter.
The command creates a target named ``gir-<namespace>`` to build the
``.gir`` file, with two properties: ``GI_GIR_PATH`` contains the path
to the generated (uninstalled) file, and ``GI_PACKAGE`` containing the
string ``<pkgname>-<nsversion>``.
A target named ``typelib-<namespace>`` is created as well to build the
``.typelib`` file.
Targets ``gir-all`` and ``typelib-all`` can be used to build all the
``.gir`` and ``.typelib`` files for a project.
#]=======================================================================]
function(GI_INTROSPECT namespace nsversion header)
cmake_parse_arguments(PARSE_ARGV 2 opt
"NO_IMPLICIT_SOURCES"
"IDENTIFIER_PREFIX;PACKAGE;SYMBOL_PREFIX;TARGET"
"DEPENDENCIES;SOURCES;OPTIONS"
)
if (NOT opt_PACKAGE)
string(TOLOWER "${namespace}" opt_PACKAGE)
endif ()
if (NOT opt_SYMBOL_PREFIX)
string(TOLOWER "${namespace}" opt_SYMBOL_PREFIX)
endif ()
if (NOT opt_IDENTIFIER_PREFIX)
string(TOUPPER "${opt_SYMBOL_PREFIX}" opt_IDENTIFIER_PREFIX)
endif ()
if (NOT opt_TARGET)
set(opt_TARGET "${namespace}")
endif ()
if (NOT TARGET ${opt_TARGET})
message(FATAL_ERROR "Target '${opt_TARGET}' was not defined")
endif ()
set(gir_deps)
set(gir_name "${namespace}-${nsversion}")
set(gir_path "${CMAKE_BINARY_DIR}/${gir_name}.gir")
set(typ_path "${CMAKE_BINARY_DIR}/${gir_name}.typelib")
set(scanner_flags)
if (GI_HAVE_SOURCES_TOP_DIRS)
list(APPEND scanner_flags "--sources-top-dirs=${CMAKE_SOURCE_DIR}")
endif ()
# Each dependency can be:
# * GI include, i.e. "GObject-2.0", implies --include=GObject-2.0, --pkg=gobject-2.0
# * GI include ":" pkgconfig module, i.e. "Gtk-4.0:gtk4", implies --include=Gtk-4.0, --pkg=gtk4
# * CMake target, i.e. "JavaScriptCore", implies --include-uninstalled=<girfile>. The target
# must have been previously used with GI_INTROSPECT(), and for each use on the target the
# corresponding <girfile> will be picked automatically.
foreach (dep IN LISTS opt_DEPENDENCIES)
if (TARGET "gir-${dep}")
get_property(dep_gir_path TARGET "gir-${dep}" PROPERTY GI_GIR_PATH)
if (dep_gir_path)
list(APPEND scanner_flags "--include-uninstalled=${dep_gir_path}")
list(APPEND gir_deps "${dep_gir_path}")
else ()
message(AUTHOR_WARNING
"Target '${dep}' listed as a dependency but it has not "
"been previously configured with GI_INTROSPECT()"
)
endif ()
elseif (dep MATCHES "^([a-zA-Z0-9._-]+):([a-z0-9._\\+-]+)$")
list(APPEND scanner_flags
"--include=${CMAKE_MATCH_1}"
"--pkg=${CMAKE_MATCH_2}"
)
else ()
string(TOLOWER "${dep}" dep_pkg)
list(APPEND scanner_flags
"--include=${dep}"
"--pkg=${dep_pkg}"
)
endif ()
endforeach ()
get_property(target_srcdir TARGET ${opt_TARGET} PROPERTY SOURCE_DIR)
foreach (incdir IN LISTS ${opt_TARGET}_INTERFACE_INCLUDE_DIRECTORIES)
if (NOT IS_ABSOLUTE "${incdir}")
get_filename_component(incdir "${incdir}" REALPATH BASE_DIR "${target_srcdir}")
endif ()
list(APPEND scanner_flags "-I${incdir}")
endforeach ()
set(gir_srcs "")
foreach (src IN LISTS opt_SOURCES)
if (NOT IS_ABSOLUTE "${src}")
get_filename_component(src "${src}" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
endif ()
if (IS_DIRECTORY "${src}")
if (EXISTS "${src}")
file(GLOB src_files LIST_DIRECTORIES FALSE CONFIGURE_DEPENDS "${src}/*.c" "${src}/*.cpp")
if (src_files)
list(APPEND gir_srcs ${src_files})
else ()
message(AUTHOR_WARNING "Directory '${src}' specified as source, but contains no source files")
endif ()
else ()
message(AUTHOR_WARNING "Directory '${src}' specified as source, but it does not exist")
endif ()
else ()
list(APPEND gir_srcs "${src}")
endif ()
endforeach ()
if (NOT opt_NO_IMPLICIT_SOURCES)
foreach (src IN LISTS ${opt_TARGET}_INSTALLED_HEADERS ${opt_TARGET}_SOURCES)
if (NOT IS_ABSOLUTE "${src}")
get_filename_component(src "${src}" REALPATH BASE_DIR "${target_srcdir}")
endif ()
list(APPEND gir_srcs "${src}")
endforeach ()
endif ()
if (NOT gir_srcs)
message(FATAL_ERROR "No sources to scan specified")
endif ()
# Generate .gir
set(target_def "$<TARGET_PROPERTY:${opt_TARGET},COMPILE_DEFINITIONS>")
set(target_inc "$<TARGET_PROPERTY:${opt_TARGET},INTERFACE_INCLUDE_DIRECTORIES>")
add_custom_command(
OUTPUT "${gir_path}"
COMMENT "Generating ${gir_name}.gir"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS ${gir_deps} ${gir_srcs}
VERBATIM
COMMAND_EXPAND_LISTS
COMMAND ${CMAKE_COMMAND} -E env "CC=${CMAKE_C_COMPILER}"
"${GI_SCANNER_EXE}" --quiet --warn-all --warn-error --no-libtool
"--output=${gir_path}"
"--library=$<TARGET_FILE_BASE_NAME:${opt_TARGET}>"
"--library-path=$<TARGET_FILE_DIR:${opt_TARGET}>"
"--namespace=${namespace}"
"--nsversion=${nsversion}"
"--c-include=${header}"
"--identifier-prefix=${opt_IDENTIFIER_PREFIX}"
"--symbol-prefix=${opt_SYMBOL_PREFIX}"
"--pkg-export=${opt_PACKAGE}-${nsversion}"
"$<$<BOOL:${target_def}>:-D$<JOIN:${target_def},;-D>>"
"$<$<BOOL:${target_inc}>:-I$<JOIN:${target_inc},;-I>>"
${scanner_flags}
${opt_OPTIONS}
${gir_srcs}
)
add_custom_target("gir-${namespace}" DEPENDS "${gir_path}")
if (NOT TARGET gir-all)
add_custom_target(gir-all COMMENT "All GI .gir targets")
endif ()
add_dependencies(gir-all "gir-${namespace}")
install(
FILES "${gir_path}"
DESTINATION "${GI_GIRDIR}"
COMPONENT runtime
)
# Generate .typelib
add_custom_command(
OUTPUT "${typ_path}"
COMMENT "Generating ${gir_name}.typelib"
DEPENDS "${gir_path}"
VERBATIM
COMMAND "${GI_COMPILER_EXE}"
"--includedir=${CMAKE_BINARY_DIR}"
"--output=${typ_path}"
"${gir_path}"
)
add_custom_target("typelib-${namespace}" DEPENDS "${typ_path}")
if (NOT TARGET typelib-all)
add_custom_target(typelib-all ALL COMMENT "All GI .typelib targets")
endif ()
add_dependencies(typelib-all "typelib-${namespace}")
install(
FILES "${typ_path}"
DESTINATION "${GI_TYPELIBDIR}"
COMPONENT runtime
)
# Record in targets to use later on e.g. with gi-docgen.
set_property(TARGET "gir-${namespace}" PROPERTY GI_GIR_PATH "${gir_path}")
set_property(TARGET "gir-${namespace}" PROPERTY GI_PACKAGE "${opt_PACKAGE}-${nsversion}")
endfunction()