A Gtk4 CMakeLists.txt example that includes gmodule-export-2.0

I have a software project that works with C, Gtk4 and CMake. I am relatively new to working with these pieces and haven’t discovered much documentation on how to properly make use of CMake.

I have somehow pieced together a CMakeLists.txt and have a good start on the software architecture intermingling with each other, but I still have many questions…

The specific focus of this thread is getting my Gtk4 project working with Signal Handlers and to do that I need to include gmodule-export-2.0 according to their documentation.

So far I have included that with find_package pkg_check-modules by following the examples and past included libraries, but I don’t think I added the necessary flags.

I searched the web for clean Gtk4 CMakeLists.txt examples with Signal Handlers implemented, but haven’t found anything that resonated with me.

I also tried doing set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE), but am a bit baffled in the differences of setting that command and or including the C_Flags.

Signal handlers and function pointers

“applications should instead be compiled with the -Wl,–export-dynamic argument inside their compiler flags, and linked against gmodule-export-2.0.”

Below, I have added my current CMakeLists.txt file.

# This is a Drogon Content Management System(CMS) built on GTK4.
# Copyright (C) 2024-PRESENT SHARPETRONICS, LLC
# Copyright (C) 2024-PRESENT ODINZU WENKI(CHARLES)

# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# Under Section 7 of GPL version 3, you are granted additional
# permissions described in the GCC Runtime Library Exception, version
# 3.1, as published by the Free Software Foundation.
#
# You should have received a copy of the GNU General Public License along with
# this program; see the files LICENSE and LICENSE_EXCEPTION respectively.
# If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
# AUTHORED DATE: 01.14.2024

# Almost all CMake files should start with this
# You should always specify a range with the newest
# and oldest tested versions of CMake. This will ensure
# you pick up the best policies.
cmake_minimum_required(VERSION 3.1...3.28)
file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_SOURCE_DIR} "src/*.c")

# If you set any CMAKE_ variables, that can go here.
# (But usually don't do this, except maybe for C++ standard)
set(CMAKE_PROJECT_DESCRIPTION "A GTK Drogon CMS")
set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/odinzu/drogoncms")
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/vendors/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Adds support for working with Gtk4 <signals> and Cambalache
# https://cmake.org/cmake/help/latest/variable/CMAKE_EXECUTABLE_ENABLE_EXPORTS.html
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)

# add CPM Deps. for C
# Note: only packages that have a CMakeLists.txt..
include(vendors/cmake/CPM_0.38.7.cmake)

#CPMAddPackage("gh:postgres/postgres#7.1.3")
#CPMAddPackage("gh:nlohmann/json@3.10.5")

# This is your project statement. You should always list languages;
# Listing the version is nice here since it sets lots of useful variables
# we removed LANGUAGES C from project to allow for automatic compiler/linker checks; we will use C and C++ libraries for this project.
project(
  DrogonCMS
  VERSION 1.0.0
)

# Adding something we can run - Output name matches target name
add_executable(DrogonCMS ${SOURCES})

#===============================================================================
# Setting compilation flags for various compilers and build types:
#===============================================================================

# some test warning levels for GCC
set(WARNING_LEVELS_GCC
    #-Werror
)
set(WARNING_LEVELS_GCC_DEBUG
    #-Wfloat-equal
    #-Wextra
    -Wall
    -Wdeprecated-declarations
    #-Wundef
    #-Wshadow
    #-Wpointer-arith
    #-Wcast-align
    #-Wstrict-overflow=5
    #-Wwrite-strings
    #-Waggregate-return
    #-Wcast-qual
    #-Wswitch-default
    #-Wswitch-enum
    #-Wconversion
)    

# Print system, compiler CMake ID, version and path:
#message(STATUS "System: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CMAKE_SYSTEM_PROCESSOR}")
#message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER_ID} ${CMAKE_Fortran_COMPILER_VERSION} ${CMAKE_Fortran_COMPILER}")
#message(STATUS "Build type is: ${CMAKE_BUILD_TYPE}")

#===============================================================================
# Default build type is release
# Uncomment this to debug or use "cmake -D CMAKE_BUILD_TYPE=debug .."
#===============================================================================
# External projects go here i.e. a project that isn't using cmake build
#include(ExternalProject)
#ExternalProject_Add(PostgreSQL
#  GIT_REPOSITORY    https://github.com/postgres/postgres.git
#  GIT_TAG           REL_16_1
#  SOURCE_DIR        ${CMAKE_SOURCE_DIR}/vendors/database/
#  CONFIGURE_COMMAND ./configure 
#  CMAKE_ARGS        ""
#  INSTALL_COMMAND   "" 
#  BUILD_IN_SOURCE   1
#  BUILD_COMMAND $(MAKE) MAKELEVEL=0
#  LOG_CONFIGURE     1
#  LOG_BUILD         1
#  LOG_INSTALL       1
#)

# set(CMAKE_BUILD_TYPE debug)
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Debug CACHE STRING
    "Choose build type: None Debug Release"
    FORCE)
endif()

# Find PkgConfig.
find_package(PkgConfig REQUIRED)

pkg_check_modules(GTK4 REQUIRED gtk4>=4.16.5)
message("GTK4 include dir: ${GTK4_INCLUDE_DIRS}")
message("GTK4 libraries: ${GTK4_LIBRARY_DIRS}")
message("GTK4 other Cflags: ${GTK4_CFLAGS_OTHER}")

pkg_check_modules(ADW REQUIRED libadwaita-1>=1.6.1)
message("ADW include dir: ${ADW_INCLUDE_DIRS}")
message("ADW libraries: ${ADW_LIBRARY_DIRS}")
message("ADW other Cflags: ${ADW_CFLAGS_OTHER}")

pkg_check_modules(GMODULE-EXPORT-2.0 REQUIRED gmodule-export-2.0>=2.82.2)
message("GMODULE-EXPORT-2.0 include dir: ${GMODULE-EXPORT-2.0_INCLUDE_DIRS}")
message("GMODULE-EXPORT-2.0 libraries: ${GMODULE-EXPORT-2.0_LIBRARY_DIRS}")
message("GMODULE-EXPORT-2.0 other Cflags: ${GMODULE-EXPORT-2.0_CFLAGS_OTHER}")

message(STATUS "Flags: ${CMAKE_C_FLAGS}")

#pkg_check_modules(SSL REQUIRED openssl>=3.1.2)
#message("SSL libraries: ${SSL_LIBRARY_DIRS}")
#pkg_check_modules(SSL REQUIRED zlib>=1.3)

# Make use of PostgreSQL if FOUND, else use SQLite3
find_package (PostgreSQL)
if (PostgreSQL_FOUND)
  message("POSTGRESQL include dir: ${PostgreSQL_INCLUDE_DIRS}")
  message("POSTGRESQL libraries: ${PostgreSQL_LIBRARIES}")
  include_directories(${PostgreSQL_INCLUDE_DIRS} ${PostgreSQL_SERVER_INCLUDE_DIRS})
  target_link_libraries (${PROJECT_NAME} PUBLIC ${PostgreSQL_LIBRARIES})

#pkg_check_modules(SQLite3 REQUIRED sqlite3>=3.43.1)
elseif (SQLITE3_FOUND)
  find_package (SQLite3)
  message("SQLITE3 include dir: ${SQLITE3_INCLUDE_DIRS}")
  message("SQLITE3 libraries: ${SQLITE3_LIBRARIES}")
  include_directories(${SQLITE3_INCLUDE_DIRS})
  target_link_libraries (${PROJECT_NAME} PUBLIC ${SQLITE3_LIBRARIES})
endif (PostgreSQL_FOUND)

# Setup CMake to use GTK+, tell the compiler where to look for headers
# and to the linker where to look for libraries
include_directories(${GTK4_INCLUDE_DIRS} ${ADW_INCLUDE_DIRS} ${GMODULE-EXPORT-2.0_INCLUDE_DIRS})
link_directories(${GTK4_LIBRARY_DIRS} ${ADW_LIBRARY_DIRS} ${GMODULE-EXPORT-2.0_LIBRARY_DIRS})
add_definitions(${GTK4_CFLAGS_OTHER} ${ADW_CFLAGS_OTHER} ${GMODULE-EXPORT-2.0_CFLAGS_OTHER})

# Include the local drogon server source code for testing and development
add_subdirectory(vendors/api-server/)
add_subdirectory(vendors/simple-reverse-proxy/)

# This is a "default" library, and will match the *** variable setting.
# Other common choices are STATIC, SHARED, and MODULE
# Including header files here helps IDEs but is not required.
# Output libname matches target name, with the usual extensions on your system
#add_library(MyLibExample simple_lib.cpp simple_lib.hpp)

# Link each target with other targets or add options, etc.
# Make sure you link your targets with this command. It can also link libraries and
# even flags, so linking a target that does not exist will not give a configure-time error.
target_include_directories(${PROJECT_NAME} PUBLIC ${GTK4_LIBRARIES} ${ADW_LIBRARIES} ${GMODULE-EXPORT-2.0_LIBRARIES})
target_link_libraries(${PROJECT_NAME} PRIVATE ${GTK4_LIBRARIES} ${ADW_LIBRARIES} ${GMODULE-EXPORT-2.0_LIBRARIES})

# Target Compile Options
target_compile_options(${PROJECT_NAME} PRIVATE ${WARNING_LEVELS_GCC} $<$<CONFIG:DEBUG>:${WARNING_LEVELS_GCC_DEBUG}>)

#===============================================================================
# Package generation:
#===============================================================================
#set(CPACK_PACKAGE_CHECKSUM SHA256)
#set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
#set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.md")
#set(CPACK_GENERATOR "TGZ")
#set(CPACK_SOURCE_GENERATOR "TGZ")
#include(CPack)

#===============================================================================
# Add subdirectories to build:
#===============================================================================
#add_subdirectory(src)

# include directory for third party libraries
include_directories(vendors/)

###############################################################################
## PACKAGING ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
#install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell CMake that we want it
# in the package
#install(DIRECTORY resources DESTINATION example_destination)

#set(CPACK_PACKAGE_CHECKSUM SHA256)
#set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
#set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.md")
#set(CPACK_GENERATOR "TGZ")
#set(CPACK_SOURCE_GENERATOR "TGZ")

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
#set(CPACK_PACKAGE_NAME "MyExample")
#set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
#set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
#include(CPack)

I don’t believe that’s related to the sort of exports that gmodule is looking for, and regardless it’s unnecessary because…

And because that’s a requirement, it’s encoded into the pkg-config data for the library:

$ pkg-config --cflags --libs gmodule-export-2.0 |fmt
-I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include
-I/usr/include/sysprof-6 -pthread -Wl,--export-dynamic -lgmodule-2.0
-pthread -lglib-2.0

So if you get the pkg_check_modules stuff right, it’ll be taken care of automatically.

A couple of observations:

  1. While it’s not wrong to set up each pkg-config dependency separately, if they’re all targeted to the same executable/library, you can also do them all at once for convenience — it’s called pkg_check_moduleS(), plural.
  2. I’d suggest raising your cmake_minimum_required() version to the still-ancient 3.7, and telling FindPkgConfig to create a target with the pkg-config dependencies, so that you can consume all of its results with a simple target_link_libraries() call.

So, something like this should work:

cmake_minimum_required(3.7 .. 3.30)

find_package(PkgConfig REQUIRED)

pkg_check_modules(
  Dependencies REQUIRED
  IMPORTED_TARGET 
  gtk4>=4.16.5 
  libadwaita-1>=1.6.1 
  gmodule-export-2.0>=2.82.2
)

FindPkgConfig will create a target named PkgConfig::Dependencies with all of the CFLAGS, linker args, and etc. set up on it, which you can use on your executable:

add_executable(DrogonCMS ${SOURCES})

# ...after all of the dependency-finding...

target_link_libraries(
  DrogonCMS PRIVATE
  PkgConfig::Dependencies
)

…that replaces all of the ${[GTK4|ADW|GMODULE-EXPORTS-2.0]_INCLUDE_DIRS}, ${..._LIBRARY_DIRS}, ${... CFLAGS_OTHER}, etc. you were consuming.

Note that you’ll still have to set up the other dependencies on your executable target, though I’d suggest doing that with the targeted (no pun) commands instead:

target_include_directories(
  DrogonCMS PRIVATE
    ${PostgreSQL_INCLUDE_DIRS}
    # etc...
)

…Or, better yet, if you up your cmake_minimum_required() version a bit farther to CMake 3.14 (which is still pretty old), FindPostgreSQL will create an IMPORTED target just like pkg-config. So then you just do,

find_package (PostgreSQL)
if (TARGET PostgreSQL::PostgreSQL)
  target_link_libraries(DrogonCMS PRIVATE
    PostgreSQL::PostgreSQL
  )
else()
  pkg_check_modules(
    Sqlite3 REQUIRED
    IMPORTED_TARGET
    sqlite3>=3.43.1
  )
  # Not technically necessary because the
  # REQUIRED would cause pkg_check_modules
  # to fail if it couldn't create it, but...
  if(TARGET PkgConfig::Sqlite3)
    target_link_libraries(DrogonCMS PRIVATE
      PkgConfig::Sqlite3
    )
  endif()
endif()

…Oh, and as a final style note (you’re clearly working from some very old CMakeLists.txt files as examples)… the endif (<repeat_the_if_conditions>) style is long deprecated. Just use endif (). Same thing for endforeach(), endfunction(), etc.

I’d recommend giving An Introduction to Modern CMake a read, and unlearning all of the bad old habits demonstrated by those ancient, ancient CMakeLists.txt files.

1 Like

Thank you for your refreshing and quality response.

I will update the CMakeLists.txt to your recommendation. Again, thank you very much! :raised_hands: