diff options
| author | Alex Merry <alex.merry@kde.org> | 2014-03-05 14:26:27 +0000 | 
|---|---|---|
| committer | Alex Merry <alex.merry@kde.org> | 2014-03-11 11:41:25 +0000 | 
| commit | bebacbcf8580dd983027bdaceb53cbfcb805bc27 (patch) | |
| tree | 2c406f330e3c14d25505b5a48b9b849f3d3e3183 | |
| parent | 72783b5acac8756c90221318e7b24bebf4e95521 (diff) | |
| download | extra-cmake-modules-bebacbcf8580dd983027bdaceb53cbfcb805bc27.tar.gz extra-cmake-modules-bebacbcf8580dd983027bdaceb53cbfcb805bc27.tar.bz2 | |
Add documentation about writing find modules
REVIEW: 116025
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | docs/writing-find-modules.md | 378 | 
2 files changed, 390 insertions, 0 deletions
| @@ -65,6 +65,18 @@ This module is organised as follows:    modules. +Creating Modules +---------------- + +Proposed new modules should be submitted using the +[KDE Review Board instance][RB], and be assigned to the `buildsystem`, +`extracmakemodules` and `kdeframeworks` groups.  You should be able to point to +two separate projects that will make use of the module.  See the `docs/` +directory for more information on writing the modules themselves. + +[RB]: https://git.reviewboard.kde.org/ + +  License  ------- diff --git a/docs/writing-find-modules.md b/docs/writing-find-modules.md new file mode 100644 index 00000000..4f6a9cd6 --- /dev/null +++ b/docs/writing-find-modules.md @@ -0,0 +1,378 @@ +Writing Find Modules           {#writing-find-modules} +==================== + +The `find_package` macro from CMake has two ways of finding packages.  If the +package Foo provides a `FooConfig.cmake` file in an appropriate place, CMake +will use that to determine all the necessary information to build against the +package.  Otherwise, if there is a `FindFoo.cmake` file somewhere in +`CMAKE_MODULE_PATH`, that will be used to find whether the package exists and +determine the appropriate information about it if it does.  See +[the CMake documentation][cmake:packages] for more information. + +The primary task of a `FindFoo.cmake` file is to determine whether the requested +package exists on the system, and set the `Foo_FOUND` variable to reflect this. +If Foo is a library, it typically sets variables such as `Foo_LIBRARIES`, +`Foo_INCLUDE_DIRS` and `Foo_DEFINITIONS` to provide the necessary information to +build and link against that library.  The `FindFoo.cmake` files in +extra-cmake-modules usually provide imported targets to make using the libraries +even simpler.  The files may also provide additional variables and useful CMake +macros. + +We will describe a typical CMake module for finding a library. + +[cmake:packages]: http://www.cmake.org/cmake/help/git-master/manual/cmake-packages.7.html + + +Documentation +------------- + +The first thing that is needed is documentation.  Start the file with a simple +statement of what the module does.  In the simplest case, this is just + +    # Find the Foo library + +but more description may be required for some packages.  If there are caveats or +other details users of the module should be aware of, you can add further +paragraphs below this.  Then you need to document what variables and imported +targets are set by the module, such as + +    # This will define the following variables: +    # +    #     Foo_FOUND         - True if the system has the Foo library +    #     Foo_VERSION       - The version of the Foo library which was found +    # +    # and the following imported targets: +    # +    #     Foo::Bar +    # +    # The following compatibility variables will also be defined, although +    # the imported targets should be used instead: +    # +    #     Foo_LIBRARIES      - Link to these to use the Foo library +    #     Foo_INCLUDES_DIRS  - Include directory for the Foo library +    #     Foo_DEFINITIONS    - Compiler flags required to link against the Foo library +    #     Foo_VERSION - The version of the Foo library which was found + +Don't forget to add copyright and license notices.  Any module distributed with +extra-cmake-modules must use the BSD 2-clause or 3-clause license: + +    # Copyright 2014 Your Name <your@email> +    # +    # 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 copyright +    #    notice, this list of conditions and the following disclaimer. +    # 2. Redistributions in binary form must reproduce the copyright +    #    notice, this list of conditions and the following disclaimer in the +    #    documentation and/or other materials provided with the distribution. +    # 3. The name of the author may not be used to endorse or promote products +    #    derived from this software without specific prior written permission. +    # +    # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + + +Version Requirements +-------------------- + +The modules provided by extra-cmake-modules typically assume a minimum CMake +version.  This is particularly relevant with imported targets, which are not +supported in old CMake versions.  You can (and should) enforce this in the +following way: + + +    if(CMAKE_VERSION VERSION_LESS 2.8.12) +        message(FATAL_ERROR "CMake 2.8.12 is required by FindFoo.cmake") +    endif() +    if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) +        message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindFoo.cmake") +    endif() + +This provides developers and users with helpful error messages, rather than the +projects failing for mysterious reasons with old CMake versions. + + +Finding the Package +------------------- + +Now the actual libraries and so on have to be found.  The code here will +obviously vary from module to module (that, after all, is the point of the Find +modules), but there tends to be a common pattern for libraries. + +First, we try to use `pkg-config` to find the library.  Note that we cannot rely +on this, as it may not be available, but it provides a good starting point. + +    find_package(PkgConfig) +    pkg_check_modules(PC_Foo QUIET Foo) + +This should define some variables starting `PC_Foo_` that contain the +information from the `.pc` file.  We can use this to set `Foo_DEFINITIONS`: + +    set(Foo_DEFINITIONS ${PC_Foo_CFLAGS_OTHER}) + +Now we need to find the libraries and include files; we use the information from +`pkg-config` to provide hints to CMake about where to look: + +    find_path(Foo_INCLUDE_DIR +        NAMES +            foo.h +        PATHS +            ${PC_Foo_INCLUDEDIR} +            ${PC_Foo_INCLUDE_DIRS} +        PATH_SUFFIXES +            Foo # if you need to put #include <Foo/foo.h> in your code +    ) +    find_library(Foo_LIBRARY +        NAMES +            foo +        PATHS +            ${PC_Foo_LIBDIR} +            ${PC_Foo_LIBRARY_DIRS} +    ) + +If you have a good way of getting the version (from a header file, for example), +you can use that information to set `Foo_VERSION`.  Otherwise, attempt to +use the information from `pkg-config`: + +    set(Foo_VERSION ${PC_Foo_VERSION}) + + +Finishing Up +------------ + +Now we can use `FindPackageHandleStandardArgs` to do most of the rest of the +work for us. + +    include(FindPackageHandleStandardArgs) +    find_package_handle_standard_args(Foo +        FOUND_VAR Foo_FOUND +        REQUIRED_VARS +            Foo_LIBRARY +            Foo_INCLUDE_DIR +        VERSION_VAR Foo_VERSION +    ) + +This will check that the `REQUIRED_VARS` contain values (that do not end in +`-NOTFOUND`) and set `Foo_FOUND` appropriately.  It will also cache those +values.  If `Foo_VERSION` is set, and a required version was passed to +`find_package`, it will check the requested version against the one in +`Foo_VERSION`.  It will also print messages as appropriate; note that if +the package was found, it will print the contents of the first required variable +to indicate where it was found. + +We add an imported target for the library.  Note that we do this after calling +`find_package_handle_standard_args` so that we can use the `Foo_FOUND` variable. +Imported targets should be namespaced (hence the `Foo::` prefix); CMake will +recognize that values passed to `target_link_libraries` that contain `::` in +their name are supposed to be imported targets (rather than just library names), +and will produce appropriate diagnostic messages if that target does not exist. + +    if(Foo_FOUND AND NOT TARGET Foo::Foo) +        add_library(Foo::Foo UNKNOWN IMPORTED) +        set_target_properties(Foo::Foo PROPERTIES +            IMPORTED_LOCATION "${Foo_LIBRARY}" +            INTERFACE_COMPILE_OPTIONS "${Foo_DEFINITIONS}" +            INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}" +        ) +    endif() + +One thing to note about this is that the `INTERFACE_INCLUDE_DIRECTORIES` and +similar properties should only contain information about the target itself, and +not any of its dependencies.  Instead, those dependencies should also be +targets, and CMake should be told that they are dependencies of this target. +CMake will then combine all the necessary information automatically. + +We should also provide some information about the package, such as where to +download it. + +    include(FeatureSummary) +    set_package_properties(Foo PROPERTIES +        URL http://www.foo.example.com/ +        DESCRIPTION "A library for doing useful things") + +Most of the cache variables should be hidden in the `ccmake` interface unless +the user explicitly asks to edit them: + +    mark_as_advanced( +        Foo_INCLUDE_DIR +        Foo_LIBRARY +    ) + +If this module replaces an older version, you should set compatibility variables +to cause the least disruption possible. + +    # compatibility variables +    set(Foo_LIBRARIES ${Foo_LIBRARY}) +    set(Foo_INCLUDE_DIRS ${Foo_INCLUDE_DIR}) +    set(Foo_VERSION_STRING ${Foo_VERSION}) + +Note that we do not wish to pass `Foo_LIBRARIES` to `find_library`, or +`Foo_INCLUDE_DIRS` to `find_path`, as the variables passed to those commands +will be stored in the cache for the user to override. + + +Components +---------- + +If your find module has multiple components, such as a package that provides +multiple libraries, the following pattern can be helpful.  First (after the +version checks), define what components are available: + +    set(knownComponents +        Bar +        Baz +    ) + +Determine which components we need to find.  Note that `Foo_FIND_COMPONENTS` is +defined if `find_package` was passed the `COMPONENTS` option. + +    if (Foo_FIND_COMPONENTS) +        set(requiredComponents ${Foo_FIND_COMPONENTS}) +    else() +        set(requiredComponents ${knownComponents}) +    endif() + +Translate component names into names to pass to `pkg-config`, and check for any +unknown components: + +    unset(unknownComponents) +    foreach(comp ${requiredComponents}) +        list(FIND knownComponents ${comp} index) +        if("${index}" STREQUAL "-1") +            list(APPEND unknownComponents "${comp}") +        endif() +    endforeach() +     +    if(DEFINED unknownComponents) +        set(msgType STATUS) +        if(Foo_FIND_REQUIRED) +            set(msgType FATAL_ERROR) +        endif() +        if(NOT Foo_FIND_QUIETLY) +            message(${msgType} "Foo: requested unknown components ${unknownComponents}") +        endif() +        return() +    endif() + +Now we create a macro to handle each component.  The logic is very similar to +that of a Find module without components; in fact, most of it could be replaced +with `find_package` calls that use either other `Find*.cmake` files or +`*Config.make` files. + +    include(FindPackageHandleStandardArgs) +    find_package(PkgConfig) + +    macro(_foo_handle_component _comp) +        set(_header) +        set(_lib) +        set(_pkgconfig_module) +        if("${_comp}" STREQUAL "Bar") +            set(_header "Foo/bar.h") +            set(_lib "bar") +            set(_pkgconfig_module "foo-bar") +        elseif("${_comp}" STREQUAL "Baz") +            set(_header "Foo/baz.h") +            set(_lib "baz") +            set(_pkgconfig_module "foo-baz") +        endif() + +        pkg_check_modules(PC_Foo_${_comp} QUIET ${_pkgconfig_module}) + +        find_path(Foo_${_comp}_INCLUDE_DIR +            NAMES ${_header} +            HINTS ${PC_Foo_${_comp}_INCLUDE_DIRS} +        ) +        find_library(Foo_${_comp}_LIBRARY +            NAMES ${_lib} +            HINTS ${PC_Foo_${_comp}_LIBRARY_DIRS} +        ) +        set(Foo_${_comp}_DEFINITIONS ${Foo_${_comp}_CFLAGS_OTHER}) + +        # compatibility variables +        if(Foo_${_comp}_INCLUDE_DIR AND Foo_${_comp}_LIBRARY) +            list(APPEND Foo_DEFINITIONS ${Foo_${_comp}_DEFINITIONS}) +            list(APPEND Foo_INCLUDE_DIRS ${Foo_${_comp}_INCLUDE_DIR}) +            list(APPEND Foo_LIBRARIES ${Foo_${_comp}_LIBRARY}) +        endif() + +        set(Foo_${_comp}_VERSION "${PC_Foo_${_comp}_VERSION}") +        if(NOT Foo_VERSION) +            set(Foo_VERSION ${Foo_${_comp}_VERSION}) +        endif() + +        find_package_handle_standard_args(Foo_${_comp} +            FOUND_VAR +                Foo_${_comp}_FOUND +            REQUIRED_VARS +                Foo_${_comp}_LIBRARY +                Foo_${_comp}_INCLUDE_DIR +            VERSION_VAR +                Foo_${_comp}_VERSION +        ) + +        mark_as_advanced( +            Foo_${_comp}_LIBRARY +            Foo_${_comp}_INCLUDE_DIR +        ) + +        if(Foo_${_comp}_FOUND AND NOT TARGET Foo::${_comp}) +            add_library(Foo::${_comp} UNKNOWN IMPORTED) +            set_target_properties(Foo::${_comp} PROPERTIES +                IMPORTED_LOCATION "${Foo_${_comp}_LIBRARY}" +                INTERFACE_COMPILE_OPTIONS "${Foo_${_comp}_DEFINITIONS}" +                INTERFACE_INCLUDE_DIRECTORIES "${Foo_${_comp}_INCLUDE_DIR}" +            ) +        endif() +    endmacro() + +And finish off with everything else: + +    foreach(comp ${requiredComponents}) +        _foo_handle_component(${comp}) +    endforeach() + +    # compatibility variables +    if (Foo_INCLUDE_DIRS) +        list(REMOVE_DUPLICATES Foo_INCLUDE_DIRS) +    endif() +    if (Foo_DEFINITIONS) +        list(REMOVE_DUPLICATES Foo_DEFINITIONS) +    endif() +    set(Foo_VERSION_STRING ${Foo_VERSION}) + +    find_package_handle_standard_args(Foo +        FOUND_VAR +            Foo_FOUND +        REQUIRED_VARS +            Foo_LIBRARIES +            Foo_INCLUDE_DIRS +        VERSION_VAR +            Foo_VERSION +        HANDLE_COMPONENTS +    ) + +    include(FeatureSummary) +    set_package_properties(Foo PROPERTIES +        URL http://www.foo.example.com/ +        DESCRIPTION "A set of libraries for doing useful things") + + +Other Macros +------------ + +Some Find modules will wish to provide useful macros related to the package. +For example, the FindSharedMimeInfo module provides an `update_xdg_mimetypes` +macro.  The main thing to note is that you should probably make this a function, +rather than a macro, to avoid polluting the global namespace with temporary +variables. + | 
