Adding CMake support to existing library

I try to keep this question and its context as generic as possible, to help others who may be facing a similar challenge. Also, @craig.scott s excellent Professional CMake barely touches this part and I hope this question could spark some ideas for additional content.


My goal is to provide direct CMake support for an existing library which is both forward and backward-compatible.

This means that I would like to contribute CMake support directly upstream into the library starting at some version X, and provide a FindModule to allow forward-compatibility for versions before X.
Both the CMake config and the FindModule should provide the same variables and the same target to link against.

Further context

The library is complex and has a custom build configuration, which generates a pkg-config file using a python script.
pkg-config support is available for all relevant versions of the library.

It also allows downloading, building and installing dependencies during the configuration step. This can make the pkg-config file rather complex. Flags are not straight forward.

Finally, the primary library maintainers are not daily users of CMake, which makes code simplicity even more significant.

Support for versions prior to X

To provide forward-compatibility, I could write a FindModule which can then be copied into other projects to detect older versions of the library. This poses the question on how to design it.

Rely on pkg-config

As the library provides a pkg-config file, I could rely fully on the pkg-config and use FindPkgConfig to locate the library.
In the FindModule, I would locate the library, set up variables as well as an imported target with an alias.

Pro: everything complex is handled by the FindPkgConfig CMake FindModule. The Module is clean, simple, and understandable for non CMake experts.
Contra: pkg-config is required on the system and the pc files have to be detectable.

Use pkg-config as a hint

I copy the behaviour of many CMake-provided FindModules and use pkg-config as a hint to finding the library using find_library etc.
In the Find module, I would locate the library using pkg-config if it is available on the system, locate the library using the pkg-config info as hints, attempt handle additional flags, set up variables as well as an imported target.

Pro: does not require a pkg-config installation
Contra: correctly handling all flags the find module is very complex and poses a severe maintenance burden

Support starting at version X

There are multiple options to upstream direct CMake support, including not adding any direct support at all, solely relying on the above FindModule.

Generate CMake config files

I could reuse the existing code used to generate the pkg-config files to additionally generate CMake config and version files.

Pro: direct CMake support without additional dependencies
Contra: Adds maintainability overhead, as multiple places need to be changed to keep these config files consistent.

CMake Config as a pkg-config wrapper

I could reuse the existing code to generate a CMake version file and a CMake Config file which internally used pkg-config to request the exact version.

Pro: less code to maintain as the complicated parts are handled when creating the pkg-config file
Contra: may detect incorrect versions via pkg-config (same version different build) and requires pkg-config to be installed on the system.

FindModule only

I don’t provide CMake config and version files, but solely rely on the provided FindModule.

Pro: Even less code to maintain. No need to keep functionality aligned between the CMake config and the FindModule.
Contra: Limits functionality to the information provided by the FindModule. If it requires pkg-config to work, then CMake support as a whole requires a pkg-config installation.

These are the options I have considered until now.

Questions to the CMake community and developers:

  • Can you think of further options, pros, or cons?
  • Has experience shown that some methods are to be avoided/preferred?
  • Which method would you accept as a contribution to your code base?

I highly recommend making the -config.cmake variant first and then making that interface through the Find module directly. Avoid variables where possible and instead only provide IMPORTED targets. You can then stick this at the top of the Find module:

find_package(Name CONFIG) # may want to forward QUIET and COMPONENTS
if (Name_FOUND)
  # Canonical interface, just return.
  return ()
  # Found the package, but it said "no"; let it stand.
  return ()
endif ()

The config.cmake file can then fill out Name_NOT_FOUND_MESSAGE for any reason why a request wasn’t reasonable and avoid the Find module’s logic.

If that’s the case, providing a proper -config.cmake file is not really in the “trivial” category IME (especially for a project as complicated as “downloads its own dependencies”). I suspect you might be best by generating a -config.cmake from the .pc file information at build time rather than trying to make CMake targets directly (unless the project plans on moving the CMake exclusively).

The main power of -config.cmake files is that CMake provides a lot of the information in a reliable way. Nothing stops a Find module from providing the same quality except how much work it is.

To me, the biggest driver to using -config.cmake files instead of Find modules is not the format of the file, but the fact that -config.cmake files come with the package they are providing, whereas a Find module is typically provided by something outside that package. This means Find modules are almost always less reliable than -config.cmake files because a Find module has to deal with an unbounded range of package versions, but a -config.cmake file only has to deal with one package and it knows exactly what should be in that package.

1 Like