Skip to content

Latest commit

 

History

History
372 lines (282 loc) · 12.9 KB

README.cmake.md

File metadata and controls

372 lines (282 loc) · 12.9 KB

For users - how to compile FluidSynth

The latest information on how to compile FluidSynth using the cmake build system can be found in our wiki:

https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake

For developers - how to add a new feature to the CMake build system

Let's explain this issue with an example. We are adding InstPatch support to FluidSynth as an optional feature, conditionally adding source files that require this feature. The first step is to add a macro option() to the main CMakeLists.txt file, the one that is located at the fluidsynth root directory.

file CMakeLists.txt, line 79:

option ( enable-libinstpatch "use libinstpatch (if available) to load DLS and GIG files" on )

If you require a minimum version of the library, set it as such:

file CMakeLists.txt, line 505:

set ( LIBINSTPATCH_MINIMUM_VERSION 1.1.0 )

Now, let's check if the InstPatch library and headers are installed, using find_package:

file CMakeLists.txt, lines 634-641:

unset ( LIBINSTPATCH_SUPPORT CACHE )
if ( enable-libinstpatch )
    find_package ( InstPatch ${LIBINSTPATCH_MINIMUM_VERSION} )
    set ( LIBINSTPATCH_SUPPORT ${InstPatch_FOUND} )
    if ( LIBINSTPATCH_SUPPORT )
        list( APPEND PC_REQUIRES_PRIV "libinstpatch-1.0")
    endif (LIBINSTPATCH_SUPPORT )
endif ( enable-libinstpatch )

The first line clears the value of the CMake variable LIBINSTPATCH_SUPPORT. If the value of the option enable-libinstpatch is true, then the function find_package() is used to test a package named "InstPatch" with version 1.1.0 or later. Unfortunately, libinstpatch does not provide an official CMake configuration, so we will need to write it a Find module. Let's start by creating a new file in the cmake_admin directory called FindInstPatch.cmake.

The first thing we should do is to document what this module provides:

file cmake_admin/FindInstPatch.cmake, lines 1-25:

#[=======================================================================[.rst:
FindInstPatch
-------

Finds the InstPatch library.

Imported Targets
^^^^^^^^^^^^^^^^

This module provides the following imported targets, if found:

``InstPatch::libinstpatch``
  The InstPatch library

Result Variables
^^^^^^^^^^^^^^^^

This will define the following variables:

``InstPatch_FOUND``
  True if the system has the InstPatch library.
``InstPatch_VERSION``
  The version of the InstPatch library which was found.

#]=======================================================================]

In this case, we provide a CMake target InstPatch::libinstpatch, and the two variables InstPatch_FOUND and InstPatch_VERSION. If upstream provides an official CMake config, the find module should provide the exact same variables and targets, even if they are not used. Moreover, the namespace of the target (InstPatch::) should match the file name (FindInstPatch.cmake).

If the library provides a pkg-config file, we should use it to get information about location, version, and usage requirements. We can achieve this with the pkg_check_modules macro:

file cmake_admin/FindInstPatch.cmake, lines 27-29:

# Use pkg-config if available
find_package(PkgConfig QUIET)
pkg_check_modules(PC_INSTPATCH QUIET libinstpatch-1.0)

We specifically want both calls to have the QUIET specifier as the system may not have pkg-config or the pc file for the library, but this should not stop searching for the library.

Next, we need to search for the headers and the library. For the headers, libinstpatch installs them in libinstpatch-X/libinstpatch/*.h, with X being 1 for versions 1.1.0 and below, and 2 for 1.1.1 and above. In our code, we use the headers as such: #include <libinstpatch/libinstpatch.h>. With this in mind, we use find_path to find the include directory:

file cmake_admin/FindInstPatch.cmake, lines 32-36:

find_path(
  InstPatch_INCLUDE_DIR
  NAMES "libinstpatch/libinstpatch.h"
  PATHS "${PC_INSTPATCH_INCLUDEDIR}"
  PATH_SUFFIXES "libinstpatch-1" "libinstpatch-2")

Here is a breakdown of the arguments:

  • InstPatch_INCLUDE_DIR is the variable where the result will be stored;
  • NAMES should match what is used in code to include the header;
  • PATHS should be set to what pkg-config provided us, if pkg-config is unavailable it will be ignored;
  • PATH_SUFFIXES should have the potential intermediate directories between CMake's search paths and what was specified in NAMES, it may omitted when not applicable.

To search for the library, we call find_library:

file cmake_admin/FindInstPatch.cmake, lines 38-41:

find_library(
  InstPatch_LIBRARY
  NAMES "instpatch-1.0"
  PATHS "${PC_INSTPATCH_LIBDIR}")

Here is a breakdown of the arguments:

  • InstPatch_LIBRARY is the variable where the result will be stored;
  • NAMES should list every name the library may have, some libraries may have a different prefix or suffix on different configurations (-static suffix, d suffix for debug builds);
  • PATHS should be set to what pkg-config provided us, if pkg-config is unavailable it will be ignored.

Getting the version of the library is trivial with pkg-config, but may not be possible to get without. Fortunately, the file libinstpatch/version.h provides the version:

#define IPATCH_VERSION       "1.2.0"

We can extract the version using regex:

file cmake_admin/FindInstPatch.cmake, lines 44-52:

if(PC_INSTPATCH_VERSION)
  set(InstPatch_VERSION "${PC_INSTPATCH_VERSION}")
elseif(InstPatch_INCLUDE_DIR)
  file(READ "${InstPatch_INCLUDE_DIR}/libinstpatch/version.h" _version_h)
  string(REGEX MATCH
               "#define[ \t]+IPATCH_VERSION[ \t]+\"([0-9]+.[0-9]+.[0-9]+)\""
               _instpatch_version_re "${_version_h}")
  set(InstPatch_VERSION "${CMAKE_MATCH_1}")
endif()

The last thing we need to search for is the usage requirements, providing them makes static linking much easier. If pkg-config is available, we use a helper function to get the correct set of properties, otherwise you need to refer to the upstream documentation. In the case of libinstpatch, we need glib-2 and libsndfile, fortunately, we already have Find modules for both libraries:

file cmake_admin/FindInstPatch.cmake, lines 55-69:

if(PC_INSTPATCH_FOUND)
  get_target_properties_from_pkg_config("${InstPatch_LIBRARY}" "PC_INSTPATCH"
                                        "_instpatch")
else()
  if(NOT TARGET GLib2::gobject-2
     OR NOT TARGET GLib2::gthread-2
     OR NOT TARGET GLib2::glib-2)
    find_package(GLib2 QUIET)
  endif()
  if(NOT TARGET SndFile::sndfile)
    find_package(SndFile QUIET)
  endif()
  set(_instpatch_link_libraries "GLib2::gobject-2" "GLib2::gthread-2"
                                "GLib2::glib-2" "SndFile::sndfile")
endif()

We then use find_package_handle_standard_args to check that all the variables are set and the version matches what was requested:

file cmake_admin/FindInstPatch.cmake, lines 72-76:

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
  InstPatch
  REQUIRED_VARS "InstPatch_LIBRARY" "InstPatch_INCLUDE_DIR"
  VERSION_VAR "InstPatch_VERSION")

This will set InstPatch_FOUND to true if the checks succeed and false otherwise.

If the library is found, we then create the target and set its properties:

file cmake_admin/FindInstPatch.cmake, lines 78-87:

if(InstPatch_FOUND AND NOT TARGET InstPatch::libinstpatch)
  add_library(InstPatch::libinstpatch UNKNOWN IMPORTED)
  set_target_properties(
    InstPatch::libinstpatch
    PROPERTIES IMPORTED_LOCATION "${InstPatch_LIBRARY}"
               INTERFACE_COMPILE_OPTIONS "${_instpatch_compile_options}"
               INTERFACE_INCLUDE_DIRECTORIES "${InstPatch_INCLUDE_DIR}"
               INTERFACE_LINK_LIBRARIES "${_instpatch_link_libraries}"
               INTERFACE_LINK_DIRECTORIES "${_instpatch_link_directories}")
endif()

Here is a breakdown of the properties:

  • IMPORTED_LOCATION should be the result of find_library;
  • INTERFACE_INCLUDE_DIRECTORES should be the result of find_path;
  • INTERFACE_LINK_LIBRARIES should be the transitive usage requirements, it may be omitted if there are none;
  • INTERFACE_COMPILE_OPTIONS and INTERFACE_LINK_DIRECTORIES should be set to what our get_flags_from_pkg_config helper provided us, if pkg-config is unavailable they will be ignored.

Lastly, we call mark a few cache variables as advanced:

file cmake_admin/FindInstPatch.cmake, line 88:

mark_as_advanced(InstPatch_INCLUDE_DIR InstPatch_LIBRARY)

Next, we need to inform downstream projects of this so if fluidsynth was built as a static library, all the transitive dependencies are properly forwarded. To achieve this, we need to edit FluidSynthConfig.cmake.in:

file FluidSynthConfig.cmake, line 22:

set(FLUIDSYNTH_SUPPORT_LIBINSTPATCH @LIBINSTPATCH_SUPPORT@)

file FluidSynthConfig.cmake, lines 91-93:

  if(FLUIDSYNTH_SUPPORT_LIBINSTPATCH AND NOT TARGET InstPatch::libinstpatch)
    find_dependency(InstPatch @LIBINSTPATCH_MINIMUM_VERSION@)
  endif()

There is a report to summarize the performed checks and the enabled features after the configuration steps, so let's add a line in this report regarding the InstPatch support.

file cmake_admin/report.cmake, lines 119-124:

set ( INPUTS_REPORT "${INPUTS_REPORT}Support for DLS files:   " )
if ( LIBINSTPATCH_SUPPORT )
    set ( INPUTS_REPORT "${INPUTS_REPORT}yes\n" )
else ( LIBINSTPATCH_SUPPORT )
    set ( INPUTS_REPORT "${INPUTS_REPORT}no (libinstpatch not found)\n" )
endif ( LIBINSTPATCH_SUPPORT )

The variable LIBINSTPATCH_SUPPORT is available for the CMake files, but we want to make it available to the compilers as well, to conditionally build code using #ifdef LIBINSTPATCH_SUPPORT. This can be done adding a line to the config.cmake file:

file src/config.cmake, lines 148-149:

/* libinstpatch for DLS and GIG */
#cmakedefine LIBINSTPATCH_SUPPORT @LIBINSTPATCH_SUPPORT@

The file config.cmake will be processed at configure time, producing a header file "config.h" in the build directory with this content, if the InstPatch support has been enabled and found:

/* libinstpatch for DLS and GIG */
#define LIBINSTPATCH_SUPPORT 1

Finally, we add the new sources files to the library during the add_library call, and link the library using the target_link_libraries function. Note that linking to a target will automatically set the necessary include directories.

file src/CMakeLists.txt, lines 96-98:

if ( LIBINSTPATCH_SUPPORT )
  set ( fluid_libinstpatch_SOURCES sfloader/fluid_instpatch.c sfloader/fluid_instpatch.h )
endif ( LIBINSTPATCH_SUPPORT )

file src/CMakeLists.txt, lines 230-257:

add_library ( libfluidsynth-OBJ OBJECT
    ...
    ${fluid_libinstpatch_SOURCES}
    ...
)

file src/CMakeLists.txt, lines 386-388:

if ( TARGET InstPatch::libinstpatch AND LIBINSTPATCH_SUPPORT )
    target_link_libraries ( libfluidsynth-OBJ PUBLIC InstPatch::libinstpatch )
endif()

If the library is used in the fluidsynth executable, you also need to link it to its target:

file src/CMakeLists.txt, lines 484-486:

if ( TARGET InstPatch::libinstpatch AND LIBINSTPATCH_SUPPORT )
    target_link_libraries ( fluidsynth PRIVATE InstPatch::libinstpatch )
endif()

Find modules guideline

It is not always necessary to provide find modules if:

  • The upstream library provides its own config
  • Most distribution ship this config

For example:

  • libsndfile provides a CMake config file only when built with CMake but not with autotools. In that case, it may be preferable to provide a Find module that matches what the config would provide. See cmake_admin/FindSndFile.
  • SDL2 provides a config file that defines targets starting 2.0.12. Since even Debian stable provides this config, it is safe to assume that a find module will not be needed.

If upstream has only begun shipping CMake config recently, it is preferable to add a Find module.

If upstream does not provide a CMake config altogether, you will need to make your own from scratch. There is no defined rule for naming, but the common practice is to have the namespace use PascalCase.

You can have CMake prefer the upstream config file over the find modules by setting CMAKE_FIND_PACKAGE_PREFER_CONFIG to ON.