[CMake] Best practices/preferred methods for linking external libraries

Michael Jackson mike.jackson at bluequartz.net
Sun Dec 4 11:59:15 EST 2011



On Dec 1, 2011, at 4:56 PM, Matthew LeRoy wrote:

> We began using CMake a few months ago for a couple of small cross-platform projects, and we're still
> learning all the ins and outs and capabilities CMake has to offer, as well as how to get the most
> out of CMake by using it "The Right Way". Right now, we're trying to figure out how to link
> to external libraries that don't have "find" modules or CMake config-mode files. After lots of
> reading in the wiki, mailing list, etc, it seems like there are several different ways to do
> this, like writing "find" modules and/or config-mode files ourselves, using the LINK_DIRECTORIES()
> command, importing/exporting targets, and others. What we're unsure of is, what is the "preferred"
> or "officially supported" method?
> 
> To be a little more specific, we have two different library projects (call them ProjectA and
> ProjectB) that both use CMake, but are developed independently. Further, ProjectB depends (or will
> depend) on ProjectA; we've just recently gotten to the point on ProjectB where we want to use some
> of the functionality in ProjectA, so we need to link to the ProjectA library(ies). At first we
> thought we needed to write a "find" module for ProjectA -- but we really have very little idea how
> to go about doing that. Other than the wiki page for finding libraries that talks a little about
> writing "find" modules (http://www.vtk.org/Wiki/CMake:How_To_Find_Libraries), is there any other
> documentation for writing a proper "find" module? Is there a particular module that ships with CMake
> that is well-written that we should look at as a guide?

I had a project that I developed a few years back which was very close to your situation. My project, call it "MXA", was the base library that got used in a number of other projects. What I ended up doing was creating a "FindMXA.cmake" file that got configured during the actual build of MXA to make sure all the proper library names and location of resource files were set correctly. Then I would install the MXA project into an external location on my system, say /Users/Shared/Toolkits (OS X). I would manually copy the FindMXA.cmake file to the other projects so that they could have the find module available. In the beginning this made for a lot of manual copying when MXA was changing a bunch but once MXA settled down to a consistent naming scheme the "FindMXA.cmake" file never changed and so I found I did not have to update the "FindMXA.cmake" file in the other projects that depended on MXA.
  I think in more recent years the CMake developers would rather see people create the CMake Export files instead of developing the Find* module for my project. In the end I just didn't have the time to properly investigate and implement the CMake Export file so I never did it.

 I think if the CMake community took a vote we could probably come up with an "Exemplar" Find*.cmake file that is good for someone trying to develop a new one. The issues that arise are that each of the projects that have a "Find*.cmake" file are usually different in subtle ways which leads to issues when you try to simply "copy/paste" from an existing module to create a new module. It really just depends on what your "ProjectA" has installed. I am going to provide my current "FindMXA.cmake" file at the end of this email and leave it open for criticism/corrections. Maybe it will spur a conversation that we can all benefit from.


> 
> Then we came across the wiki page for module maintainers
> (http://www.itk.org/Wiki/CMake:Module_Maintainers), which states in the Introduction that we
> should *not* write find modules for packages that themselves build with CMake, and instead to create
> a CMake package configuration file. We followed the link to the tutorials on CMake Packages, but
> don't fully understand everything that's going on there. Regardless, is this the preferred method?
> 
> Another general question we have is that we may not want to have to build and install ProjectA in
> order to build ProjectB; we may instead just want to provide pre-built binaries and headers for
> ProjectA in ProjectB's source tree in version control -- part of the reason for this is to control
> which version of ProjectA is used by ProjectB. In that case, how would a find module or
> package configuration file find the binaries and headers, since they're not installed in the
> standard install location on the system? Further, regarding "standard install locations": is there a
> particular standard location to install packages on Windows systems, similar to /usr/local (or
> whatever install prefix is specified) on a Unix/Linux system?

I would think that something like "git submodule" or the equivalent for your revision control system would fix this issue. At least with git submodules the "primary" project decides which version of the sub-project gets checked out and used.
   If ProjectA's precompiled binaries are not stable, ie, they change a lot, this could really add a lot of unnecessary bloat to your code repository. But that is just my opinion.

  Besides "C:\Program Files" I don't think there is any "standard" install location on Windows. I think most people just "roll their own" or just assume the root level of "C" drive. 

> 
> Thanks in advance for whatever direction/guidance you can give!
> --
> 

---
Mike Jackson                 www.bluequartz.net


#--- FindMXADataModel.cmake  Begin ----------------------------------
#/* --------------------------------------------------------------------------*
#* This source code has been cleared for public release by the                *
#* US Air Force 88th Air Base Wing Public Affairs Office under                *
#* case number 88ABW-2010-4857 on Sept. 7, 2010.                              *
#* -------------------------------------------------------------------------- */
#--////////////////////////////////////////////////////////////////////////////
#--  Copyright (c) 2009, Michael A. Jackson. BlueQuartz Software
#--  All rights reserved.
#--  BSD License: http://www.opensource.org/licenses/bsd-license.html
#--////////////////////////////////////////////////////////////////////////////

# - Find MXADataModel
# Find the native MXADATAMODEL headers and libraries.
#
#  MXADATAMODEL_INCLUDE_DIRS - where to find MXADataModel.h, etc.
#  MXADATAMODEL_LIBRARIES    - List of libraries when using MXADataModel.
#  MXADATAMODEL_LIBRARY_DEBUG - Debug version of Library
#  MXADATAMODEL_LIBRARY_RELEASE - Release Version of Library
#  MXADATAMODEL_FOUND        - True if MXADataModel found.

# Look for the header file.

############################################
#
# Check the existence of the libraries.
#
############################################
# This macro was taken directly from the FindQt4.cmake file that is included
# with the CMake distribution. This is NOT my work. All work was done by the
# original authors of the FindQt4.cmake file. Only minor modifications were
# made to remove references to Qt and make this file more generally applicable
#########################################################################

MACRO (_adjust_lib_var_names basename)
  IF (${basename}_INCLUDE_DIR)

  # if only the release version was found, set the debug variable also to the release version
  IF (${basename}_LIBRARY_RELEASE AND NOT ${basename}_LIBRARY_DEBUG)
    SET(${basename}_LIBRARY_DEBUG ${${basename}_LIBRARY_RELEASE})
    SET(${basename}_LIBRARY       ${${basename}_LIBRARY_RELEASE})
    SET(${basename}_LIBRARIES     ${${basename}_LIBRARY_RELEASE})
  ENDIF (${basename}_LIBRARY_RELEASE AND NOT ${basename}_LIBRARY_DEBUG)

  # if only the debug version was found, set the release variable also to the debug version
  IF (${basename}_LIBRARY_DEBUG AND NOT ${basename}_LIBRARY_RELEASE)
    SET(${basename}_LIBRARY_RELEASE ${${basename}_LIBRARY_DEBUG})
    SET(${basename}_LIBRARY         ${${basename}_LIBRARY_DEBUG})
    SET(${basename}_LIBRARIES       ${${basename}_LIBRARY_DEBUG})
  ENDIF (${basename}_LIBRARY_DEBUG AND NOT ${basename}_LIBRARY_RELEASE)
  IF (${basename}_LIBRARY_DEBUG AND ${basename}_LIBRARY_RELEASE)
    # if the generator supports configuration types then set
    # optimized and debug libraries, or if the CMAKE_BUILD_TYPE has a value
    IF (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE)
      SET(${basename}_LIBRARY       optimized ${${basename}_LIBRARY_RELEASE} debug ${${basename}_LIBRARY_DEBUG})
    ELSE(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE)
      # if there are no configuration types and CMAKE_BUILD_TYPE has no value
      # then just use the release libraries
      SET(${basename}_LIBRARY       ${${basename}_LIBRARY_RELEASE} )
    ENDIF(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE)
    SET(${basename}_LIBRARIES       optimized ${${basename}_LIBRARY_RELEASE} debug ${${basename}_LIBRARY_DEBUG})
  ENDIF (${basename}_LIBRARY_DEBUG AND ${basename}_LIBRARY_RELEASE)

  SET(${basename}_LIBRARY ${${basename}_LIBRARY} CACHE FILEPATH "The ${basename} library")

  IF (${basename}_LIBRARY)
    SET(${basename}_FOUND 1)
  ENDIF (${basename}_LIBRARY)

ENDIF (${basename}_INCLUDE_DIR )

  # Make variables changeble to the advanced user
  MARK_AS_ADVANCED(${basename}_LIBRARY ${basename}_LIBRARY_RELEASE ${basename}_LIBRARY_DEBUG ${basename}_INCLUDE_DIR )
ENDMACRO (_adjust_lib_var_names)

# Only set MXADATAMODEL_INSTALL to the environment variable if it is blank
if ("${MXADATAMODEL_INSTALL}" STREQUAL "")
    SET (MXADATAMODEL_INSTALL  $ENV{MXADATAMODEL_INSTALL})
endif()
  
# MESSAGE (STATUS "Finding MXADataModel library and headers..." )
#SET (MXADATAMODEL_DEBUG 0)

SET(MXADATAMODEL_INCLUDE_SEARCH_DIRS
  ${MXADATAMODEL_INSTALL}/include
)

SET (MXADATAMODEL_LIB_SEARCH_DIRS
  ${MXADATAMODEL_INSTALL}/lib
  )

SET (MXADATAMODEL_BIN_SEARCH_DIRS
  ${MXADATAMODEL_INSTALL}/bin
)

FIND_PATH(MXADATAMODEL_INCLUDE_DIR 
  NAMES MXA
  PATHS ${MXADATAMODEL_INCLUDE_SEARCH_DIRS}
  NO_DEFAULT_PATH
)

#-- I have found on Windows it is VERY important to be able to know what type of
#-- libraries are avaiable: Debug and/or Release which will lessen the chances of
#-- link warnings due to linking against both debug and release versions of the 
#-- c and c++ runtime libraries. These CMake variables set this up for us.
SET (MXADATAMODEL_SEARCH_DEBUG_NAMES "MXADataModel_debug;MXADataModel_D;libMXADataModel_D;libMXADataModel_debug")
SET (MXADATAMODEL_SEARCH_RELEASE_NAMES "MXADataModel;libMXADataModel")

IF (MXADATAMODEL_DEBUG)
    message (STATUS "MXADATAMODEL_INCLUDE_SEARCH_DIRS: ${MXADATAMODEL_INCLUDE_SEARCH_DIRS}")
    message (STATUS "MXADATAMODEL_LIB_SEARCH_DIRS: ${MXADATAMODEL_LIB_SEARCH_DIRS}")
    message (STATUS "MXADATAMODEL_BIN_SEARCH_DIRS: ${MXADATAMODEL_BIN_SEARCH_DIRS}")
    message (STATUS "MXADATAMODEL_SEARCH_RELEASE_NAMES: ${MXADATAMODEL_SEARCH_RELEASE_NAMES}")
    message (STATUS "MXADATAMODEL_SEARCH_DEBUG_NAMES: ${MXADATAMODEL_SEARCH_DEBUG_NAMES}")
    message (STATUS "MXADATAMODEL_INCLUDE_DIR: ${MXADATAMODEL_INCLUDE_DIR}")
ENDIF (MXADATAMODEL_DEBUG)

# Look for the library.
FIND_LIBRARY(MXADATAMODEL_LIBRARY_DEBUG 
  NAMES ${MXADATAMODEL_SEARCH_DEBUG_NAMES}
  PATHS ${MXADATAMODEL_LIB_SEARCH_DIRS} 
  NO_DEFAULT_PATH
  )
  
FIND_LIBRARY(MXADATAMODEL_LIBRARY_RELEASE 
  NAMES ${MXADATAMODEL_SEARCH_RELEASE_NAMES}
  PATHS ${MXADATAMODEL_LIB_SEARCH_DIRS} 
  NO_DEFAULT_PATH
  )

#-- Part of MXADataModel produced a small command line utility. Finding this
#-- will allow finding of the .dll file on windows.
SET (MXADATAMODEL_IMPORT_GENERATOR_PROG_NAME "ImportGenerator")
IF (WIN32)
    SET (MXADATAMODEL_IMPORT_GENERATOR_PROG_NAME "ImportGenerator.exe")
ENDIF(WIN32)

FIND_PROGRAM(MXADATAMODEL_IMPORT_GENERATOR_PROG
    NAMES ${MXADATAMODEL_IMPORT_GENERATOR_PROG_NAME}
    PATHS ${MXADATAMODEL_BIN_SEARCH_DIRS} 
    NO_DEFAULT_PATH
)

_adjust_lib_var_names(MXADATAMODEL)

# MESSAGE( STATUS "MXADATAMODEL_LIBRARY: ${MXADATAMODEL_LIBRARY}")

# Copy the results to the output variables.
IF(MXADATAMODEL_INCLUDE_DIR AND MXADATAMODEL_LIBRARY)
  SET(MXADATAMODEL_FOUND 1)
  SET(MXADATAMODEL_LIBRARIES ${MXADATAMODEL_LIBRARY})
  SET(MXADATAMODEL_INCLUDE_DIRS ${MXADATAMODEL_INCLUDE_DIR})
  IF (MXADATAMODEL_LIBRARY_DEBUG)
    GET_FILENAME_COMPONENT(MXADATAMODEL_LIBRARY_PATH ${MXADATAMODEL_LIBRARY_DEBUG} PATH)
    SET(MXADATAMODEL_LIB_DIR ${MXADATAMODEL_LIBRARY_PATH})
    SET(MXADATAMODEL_BIN_DIR ${MXADATAMODEL_LIBRARY_PATH})
  ELSEIF(MXADATAMODEL_LIBRARY_RELEASE)
    GET_FILENAME_COMPONENT(MXADATAMODEL_LIBRARY_PATH ${MXADATAMODEL_LIBRARY_RELEASE} PATH)
    SET(MXADATAMODEL_LIB_DIR ${MXADATAMODEL_LIBRARY_PATH})
    SET(MXADATAMODEL_BIN_DIR ${MXADATAMODEL_LIBRARY_PATH})
  ENDIF(MXADATAMODEL_LIBRARY_DEBUG)
  
  IF (MXADATAMODEL_IMPORT_GENERATOR_PROG)
    GET_FILENAME_COMPONENT(MXADATAMODEL_BIN_PATH ${MXADATAMODEL_IMPORT_GENERATOR_PROG} PATH)
    SET(MXADATAMODEL_BIN_DIR  ${MXADATAMODEL_BIN_PATH})
  ENDIF (MXADATAMODEL_IMPORT_GENERATOR_PROG)
  
ELSE(MXADATAMODEL_INCLUDE_DIR AND MXADATAMODEL_LIBRARY)
  SET(MXADATAMODEL_FOUND 0)
  SET(MXADATAMODEL_LIBRARIES)
  SET(MXADATAMODEL_INCLUDE_DIRS)
ENDIF(MXADATAMODEL_INCLUDE_DIR AND MXADATAMODEL_LIBRARY)

# Report the results.
IF(NOT MXADATAMODEL_FOUND)
  SET(MXADATAMODEL_DIR_MESSAGE
    "MXADATAMODEL was not found. Make sure MXADATAMODEL_LIBRARY and MXADATAMODEL_INCLUDE_DIR are set or set the MXADATAMODEL_INSTALL environment variable.")
  IF(NOT MXADATAMODEL_FIND_QUIETLY)
    MESSAGE(STATUS "${MXADATAMODEL_DIR_MESSAGE}")
  ELSE(NOT MXADATAMODEL_FIND_QUIETLY)
    IF(MXADATAMODEL_FIND_REQUIRED)
      # MESSAGE(FATAL_ERROR "${MXADATAMODEL_DIR_MESSAGE}")
      MESSAGE(FATAL_ERROR "MXADataModel was NOT found and is Required by this project")
    ENDIF(MXADATAMODEL_FIND_REQUIRED)
  ENDIF(NOT MXADATAMODEL_FIND_QUIETLY)
ENDIF(NOT MXADATAMODEL_FOUND)

IF(MXADATAMODEL_DEBUG)
 MESSAGE(STATUS "MXADATAMODEL_INCLUDE_DIRS: ${MXADATAMODEL_INCLUDE_DIRS}")
 MESSAGE(STATUS "MXADATAMODEL_LIBRARY_DEBUG: ${MXADATAMODEL_LIBRARY_DEBUG}")
 MESSAGE(STATUS "MXADATAMODEL_LIBRARY_RELEASE: ${MXADATAMODEL_LIBRARY_RELEASE}")
 MESSAGE(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
 message(STATUS "MXADATAMODEL_RESOURCES_DIR: ${MXADATAMODEL_RESOURCES_DIR}")
ENDIF(MXADATAMODEL_DEBUG)


IF (MXADATAMODEL_FOUND)
  INCLUDE(CheckSymbolExists)
  #############################################
  # Find out if MXADATAMODEL was build using dll's
  #############################################
  # Save required variable
  SET(CMAKE_REQUIRED_INCLUDES_SAVE ${CMAKE_REQUIRED_INCLUDES})
  SET(CMAKE_REQUIRED_FLAGS_SAVE    ${CMAKE_REQUIRED_FLAGS})
  # Add MXADATAMODEL_INCLUDE_DIR to CMAKE_REQUIRED_INCLUDES
  SET(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${MXADATAMODEL_INCLUDE_DIRS}")

  CHECK_SYMBOL_EXISTS(MXA_BUILT_AS_DYNAMIC_LIB "MXA/MXAConfiguration.h" HAVE_MXADATAMODEL_DLL)

    IF (HAVE_MXADATAMODEL_DLL STREQUAL "TRUE")
        SET (HAVE_MXADATAMODEL_DLL "1")
    ENDIF (HAVE_MXADATAMODEL_DLL STREQUAL "TRUE")

  # Restore CMAKE_REQUIRED_INCLUDES and CMAKE_REQUIRED_FLAGS variables
  SET(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_SAVE})
  SET(CMAKE_REQUIRED_FLAGS    ${CMAKE_REQUIRED_FLAGS_SAVE})
  #
  #############################################

ENDIF (MXADATAMODEL_FOUND)






More information about the CMake mailing list