How to write cmake modules for "boost-like" multi-component library? - c++

I'm currently writing a c++ library, that has several "sub libraries", like for example the boost library.
Let's name the library "TestLib" and the sub libraries "Base" and "Ext", where Base contains come basic stuff, that doesn't depend an any other sub library. Ext however depends on some classes of Base.
Each "sub library" should compile into a separate .a or .so file, but they all should share a namespace (TestLib).
Now i'm aiming to write clean cmake scripts in order to achieve this goal.
In the end i want to be able to do something like this in cmake:
find_package(TestLib 0.1 REQUIRED COMPONENTS Base Ext)
or
target_link_libraries(someapplication
PUBLIC
TestLib::Base
)
I have put each "sub library" in a separate git repository and added them as submodules in a new repository that has only a CMakeLists.txt
that just calls add_subdirectory on each of the repos.
Most of the cmake stuff i achieved, i got from this awesome tutorial at
https://pabloariasal.github.io/
And the Base part works as intended (which is no wonder, since it doesn't depend on anything else).
But my problems come with the Ext part. In order for this to compile, i have to link against the Base library, which shouldn't be to hard, and with some trial and error i am sure that i will get it to work.
But i want to do it the right way.
My approach was to
find_package(TestLib COMPONENTS Base)
in the CMakeLists.txt of TestLib.Ext.
But this cant be found since it has no TestLibConfig.cmake.
Which makes sense, but i don't know what to put in this file.
I tried to provide some code that exactly describes my problem, but since this would be too much to post here, i created a github for this purpose:
https://github.com/PowerSupplyTopologies/TestLib
This should contain all the relevant code.
This could be trivial to some of you, but i bet there are more people that could benefit from this approach.
Thank you in advance for any of your thoughts.
EDIT:
The library creation in the CMakeLists.txt of Base is:
set(TARGET_NAME testlibbase)
add_library(${TARGET_NAME}
src/ClassA.cpp
src/ClassB.cpp
)
#Add an alias so that library can be used inside the build tree, e.g. when testing
add_library(TestLib::${TARGET_NAME} ALIAS ${TARGET_NAME})
and
set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/TestLib)
install(TARGETS ${TARGET_NAME}
EXPORT ${TARGET_NAME}-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
#This is required so that the exported target has the name JSONUtils and not jsonutils
set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME Base)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
#Export the targets to a script
install(EXPORT ${TARGET_NAME}-targets
FILE
TestLibBaseTargets.cmake
NAMESPACE
TestLib::
DESTINATION
${INSTALL_CONFIGDIR}
)
#Create a ConfigVersion.cmake file
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/TestLibBaseConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake /TestLibBaseConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/TestLibBaseConfig.cmake
INSTALL_DESTINATION ${INSTALL_CONFIGDIR}
)
#Install the config, configversion and custom find modules
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/TestLibBaseConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/TestLibBaseConfigVersion.cmake
DESTINATION ${INSTALL_CONFIGDIR}
)
##############################################
## Exporting from the build tree
export(EXPORT ${TARGET_NAME}-targets FILE ${CMAKE_CURRENT_BINARY_DIR}/TestLibBaseTargets.cmake NAMESPACE TestLib::)
and of Ext:
set(TARGET_NAME testlibext)
add_library(${TARGET_NAME}
src/ClassC.cpp
)
#Add an alias so that library can be used inside the build tree, e.g. when testing
add_library(TestLib::${TARGET_NAME} ALIAS ${TARGET_NAME})

In your meta project TestLib you can create a TestLibConfig.cmake file like described in the CMake documentation.
TestLibConfig.cmake:
set(_supported_components Base Ext)
foreach(_comp ${Test_FIND_COMPONENTS})
if (NOT ";${_supported_components};" MATCHES _comp)
set(TestLib_FOUND False)
set(TestLib_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/TestLib${_comp}Targets.cmake")
endforeach()
ref: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#creating-a-package-configuration-file

Related

Exporting and packaging prebuilt libraries in cmake

instead of asking a question directly, I'll expose my use case and the way I tried (but failed) to solve it.
Say I have:
3 shared libraries A, B and C
A require B and C
A comes with a set of headers
That's it, no extra information, it's provided by a vendor and not possibly subject to any change (modernization/cmake packages, etc).
A should always be packaged with B and C. I should only need to link with A and cmake should transitively link with B and C.
Now, I'd like to make it more "modern cmake" friendly and by able to:
First usecase: Create a repo containing these libs and calling add_subdirectory() from a parent project.
First usecase: Create a package (say debian pkg . deb) containing the relevant AConfig.cmake AConfigVersion.cmake and ATargets.cmake. Then a simple system install of the pkg and a find_package() should to the trick.
What has been done:
I tried using INTERFACE IMPORTED library and INTERFACE.
Because I want to support packaging the libs INTERFACE IMPORTED can't be used (you can't install it as far as I know/tested).
INTERFACE is working fine for the first usecase, using add_subdirectory(), headers are found, everything links, but because the user may not have at this point, the shared lib in is path, he can't run the tests for instance.
Then comes the export part needed to make the shared libs available and to make find_package() work. I succeed to export/package the libs A B C, the headers for A and the files needed for find_package().
But when in a client library D, find_package(A REQUIRED) finds the lib (no messages such as "Could not find a package configuration file provided by "A" ") it doess NOT create any target I can link on. I took a look at the generated ATargets.cmake:
# generated stuff before
add_library(A::A INTERFACE IMPORTED) # This is cannot be used at all, does not create a target I can link on unless I remove IMPORTED
set_target_properties(A::A PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" # that's fine
INTERFACE_LINK_LIBRARIES "A.so;B.so.1.0;C.so.1.0" # that's not fine I need ${_IMPORT_PREFIX}/${CMAKE_INSTALL_LIBDIR}/ before each libs like it did for the headers
)
# generated stuff after
Note that if I remove the IMPORTED in the add_library of the ATargets.cmake and remove the namespace stuff, the target A is correctly accessible in any client cmake project using find_package but it'll NOT link correctly probably because A.so (and B and C) is not referenced using ${_IMPORT_PREFIX}/lib/A.so. I tried adding ${_IMPORT_PREFIX}/lib/ before all libs in INTERFACE_LINK_LIBRARIES, removed the IMPORTED keyword and namespace stuff and guess what, it works perfectly... Now, how do I do that without the trick.
The content of AConfig.cmake.in is:
#PACKAGE_INIT#
include(CMakeFindDependencyMacro)
# Add the targets file
include("${CMAKE_CURRENT_LIST_DIR}/ATargets.cmake")
# check_required_components(#PROJECT_NAME#) # It is commented because unless I apply the fix specified earlier (IMPORTED and namespace removed), during the find_package call I get:
#################
# CMake Error at /usr/lib/cmake/A/AConfig.cmake:8 # (check_required_components):
# Unknown CMake command "check_required_components".
#################
That's pretty much it, the rest of this post is implementation details. This code below can be used to solve use case 1 but the export package and cmake config/target files are not correct.
add_library(A
INTERFACE)
add_library(A::A ALIAS A)
target_include_directories(A
INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>")
target_link_libraries(A
INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/A.so>"
"$<INSTALL_INTERFACE:A.so>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/B.so>"
"$<INSTALL_INTERFACE:B.so>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/C.so>"
"$<INSTALL_INTERFACE:C.so>")
#### install
install(TARGETS A
EXPORT ATargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT A_Runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT A_Runtime NAMELINK_COMPONENT A_Development
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT A_Development)
install(DIRECTORY "lib/"
DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include"
DESTINATION "include")
write_basic_package_version_file(AConfigVersion.cmake
VERSION "${PACKAGE_VERSION}"
COMPATIBILITY SameMajorVersion)
install(EXPORT ATargets
FILE ATargets.cmake
NAMESPACE A::
DESTINATION "lib/cmake/A")
configure_file(AConfig.cmake.in AConfig.cmake #ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/AConfigVersion.cmake"
DESTINATION "lib/cmake/A")
# Then some cpack stuff that is not affecting the work done earlier
In a client lib/cmake project after the installation of the generated package (generated by cpack):
find_package(A)
target_link_libraries(my_project PUBLIC/PRIVATE A::A) # Should bring in the headers and link with A B and C
Documentation:
cmake: create a new library target which consists of a prebuilt library
https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/Exporting-and-Importing-Targets
Possible to add an imported library to target_link_libraries that takes care of include directories too?
Exporting an imported library
https://discourse.cmake.org/t/how-to-control-import-prefix-in-exported-targets-cmake-list-file-generated-by-cmake/2291
Create relocatable package with proper autogenerated config cmake
https://discourse.cmake.org/t/exporting-packages-with-a-custom-find-module/3820/2
Thanks for reading/helping

CMake multiple libraries in different repositories

Introduction
For the last couple of months I have been working on a project consisting of a number of repositories (and there will be more and more repositories as time goes by). Each repository is part of a bigger framework, where one of the repository can depend on any number of the other repositories. Since they are all part of the same framework I would like them to be COMPONENTS of a common CMake namespace. I would also like the user to only have to clone the repositories (and their dependencies) that the user is interested in.
I am new to CMake and have been trying to get this working, on and off, for the last couple of months without much success. I have read and watched a number of talks about "Modern CMake" and searched different forums without any luck. Therefore I hope someone here can help me or at least point me in the right direction. Any help is appreciated!
Short Example
I will now present a short example to make it more clear what I am looking for.
Imagine that we have these repositories:
Rep1 does not depend on any other repository
Rep2 depends on Rep1
Rep3 depends on Rep1 and Rep2
Rep4 depends on Rep3
...
If the user is only interested in Rep2. Then it should be possible to only clone Rep1 and Rep2.
For this I would like to be able to do this:
For Rep2 call find_package(COMMON_NAMESPACE REQUIRED COMPONENTS Rep1) and then target_link_libraries
For Rep3 call find_package(COMMON_NAMESPACE REQUIRED COMPONENTS Rep1 Rep2) and then target_link_libraries
For Rep4 call find_package(COMMON_NAMESPACE REQUIRED COMPONENTS Rep3) and then target_link_libraries
However, I do not know how to get COMPONENTS working when having different repositories for the different COMPONENTS. It is not an alternative to have a common top-level CMakeLists.txt which add_subdirectory is called in for each repository, since I want people to be able to clone each repository separately however they want. If someone knows how to achieve this I would be very grateful if you could explain it to me!
What I Have Now
For now I am instead just trying to solve it like this:
For Rep2 call find_package(Rep1 REQUIRED) and then target_link_libraries(Rep2 PUBLIC COMMON_NAMESPACE::Rep1)
For Rep3 call find_package(Rep1 REQUIRED), find_package(Rep2 REQUIRED) and then target_link_libraries(Rep3 PUBLIC COMMON_NAMESPACE::Rep1 COMMON_NAMESPACE::Rep2)
For Rep4 call find_package(Rep3 REQUIRED) and then target_link_libraries(Rep4 PUBLIC COMMON_NAMESPACE::Rep3)
The first two works. But with the third I get:
Target "Rep4" links to target "COMMON_NAMESPACE::Rep1" but the target was not found.
Perhaps a find_package() call is missing for an IMPORTED target, or an
ALIAS target is missing?
How can this be solved without calling find_package(Rep1 REQUIRED) and find_package(Rep2 REQUIRED) in Rep4?
Current File Structure
Here is an outline of the file structure. "Example" is the common namespace I would like to use:
Rep1
cmake
Rep1_ProjectConfig.cmake.in
include
Example
Rep1
some_header.h
src
CMakeLists.txt
some_cpp.cpp
CMakeLists.txt
Rep2
cmake
Rep2_ProjectConfig.cmake.in
include
Example
Rep2
some_header.h
src
CMakeLists.txt
some_cpp.cpp
CMakeLists.txt
Rep3
cmake
Rep3_ProjectConfig.cmake.in
include
Example
Rep3
some_header.h
src
CMakeLists.txt
some_cpp.cpp
CMakeLists.txt
Rep4
cmake
Rep4_ProjectConfig.cmake.in
include
Example
Rep4
some_header.h
src
CMakeLists.txt
some_cpp.cpp
CMakeLists.txt
...
Current CMake File Content
The top-level CMakeLists.txt file for each repository looks like this:
cmake_minimum_required(VERSION 3.9)
project(RepX_Project
VERSION 1.0
DESCRIPTION "RepX_Project ..."
LANGUAGES CXX
)
add_subdirectory(src)
The CMakeLists.txt file located in src for each repository looks like this:
# The namespace used
set(NS Example)
set(HEADER_LIST
"${PROJECT_SOURCE_DIR}/include/${NS}/RepX/some_header.h"
...
)
set(SRC_LIST
some_cpp.cpp
...
)
# Some of them might depend on one (or more) of the other repositories
find_package(RepY REQUIRED)
...
add_library(RepX SHARED ${SRC_LIST} ${HEADER_LIST})
add_library(${NS}::RepX ALIAS RepX)
set_target_properties(RepX
PROPERTIES
VERSION ${RepX_Project_VERSION}
SOVERSION ${RepX_Project_SOVERSION}
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
target_include_directories(RepX
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)
# If this repository depends on one (or more) of the other repositories
target_link_libraries(RepX
PUBLIC
${NS}::RepY
...
)
target_compile_features(RepX PUBLIC cxx_std_17)
source_group(TREE "${PROJECT_SOURCE_DIR}/include" PREFIX "Header Files" FILES ${HEADER_LIST})
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
)
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PACKAGE_VERSION}
COMPATIBILITY SameMajorVersion
)
install(TARGETS RepX
EXPORT RepXTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(EXPORT RepXTargets
FILE
RepXTargets.cmake
NAMESPACE
${NS}::
DESTINATION
lib/cmake/${PROJECT_NAME}
)
install(FILES
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION
lib/cmake/${PROJECT_NAME}
)
install(DIRECTORY
${PROJECT_SOURCE_DIR}/include/
DESTINATION
include
)
The .cmake.in file for each repository looks like this:
#PACKAGE_INIT#
include("${CMAKE_CURRENT_LIST_DIR}/RepXTargets.cmake")
check_required_components("RepX")
Questions
How to use COMPONENTS when the different components is in their own repository?
What do I have to fix in order for find_package to be able to recursively find all my dependencies?
How to handle CMake namespaces correctly?
Currently I have to create a COMMON_NAMESPACE folder in each include directory for each repository. Also, in the .cmake.in I have to hardcode the target name for each library in each repository for this to work.
Is my .cmake.in file correct? Should I create my own CMake config file also?
Can I improve the CMakeLists.txt files located in the src directories?
Last Note
I really appreciate you taking your time to read this and (hopefully) help me with this. Sorry that this post is so long, I tried being as concise as possible while at the same time showing everything I have in order for you to get the complete picture. Just ask if there is any other information that you would want in order to better/easier help with this, I am happy to provide any additional information that you might need! Hopefully this can help other people as well.
Thank you and have a nice day!

CMake Package Support - Includes and Libraries not found

I am currently developing a software package, for which I'd like to provide the cmake package support (so users can find it with find_package(...)). The Problem is, the package is found but FOO_INCLUDE_DIR and FOO_LIBRARIES is empty.
Within my package I have several modules, each with a CMakeLists file which installs the respective library and headers with:
install(TARGETS ${LIBRARY_NAME} EXPORT FooTargets
RUNTIME DESTINATION ${Foo_RUNTIME_INSTALL_DIR}
LIBRARY DESTINATION ${Foo_LIBRARY_INSTALL_DIR}
ARCHIVE DESTINATION ${Foo_ARCHIVE_INSTALL_DIR}
FRAMEWORK DESTINATION ${Foo_FRAMEWORK_INSTALL_DIR})
# Headers
install(
DIRECTORY include/${LIBRARY_NAME}
DESTINATION include/${PROJECT_NAME}
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
)
The headers for the library are included with target_include_directories like this:
target_include_directories(${LIBRARY_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # for headers when building
$<INSTALL_INTERFACE:${Foo_INC_INSTALL_DIR}> # for client in install mode
)
I checked the folders and all libraries and headers are correctly installed. In my toplevel CMakeLists I export my targets with:
install(
EXPORT FooTargets
DESTINATION ${Foo_CMAKE_CONFIG_INSTALL_DIR}
FILE FooConfig.cmake
)
The config is where I assume it to be (usr/local/lib/cmake/Foo). So everything seems to be correct. When I look into my FooConfig.cmake it says:
# Create imported target realm_densifier_base
add_library(FooLib1 SHARED IMPORTED)
set_target_properties(FooLib1 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "/usr/local/include/Foo"
INTERFACE_LINK_LIBRARIES "...several libraries..."
)
...which is absolutely correct and exactly what I expected. What part of the puzzle is missing? Is INTERFACE_INCLUDE_DIRECTORIES and INTERFACE_LINK_LIBRARIES not the correct flag to be set?
Thanks for the help and best regards,
Alex
Edit:
#Guillaume Racicot already cleared most things up, I only knew the "non target" way of adding headers to my project, that was with include_directories(Foo_INCLUDE_DIRS). However, in the target-world linking against my library Foo was enough. Another thing was that I messed up some directories in the target_include_directories(...) command, so directories were wrong and therefore could not be found in my other project. Thanks for the help!
Why would FOO_INCLUDE_DIR or FOO_LIBRARIES be set? This may be how old find modules worked, but not how config files work. Even newer find modules expose targets instead of directory variables.
When generating an XYZConfig.cmake file, informations about targets will be exported, not informations on the directory.
With such exportation:
install(
EXPORT FooTargets
NAMESPACE Foo::
DESTINATION ${Foo_CMAKE_CONFIG_INSTALL_DIR}
FILE FooConfig.cmake
)
You would expect users of the package to use it like so:
find_package(Foo REQUIRED)
# or PUBLIC ------v
target_link_libraries(bar PRIVATE Foo::FooLib1)
If your package has multiple targets in the export set, then you can link to both or only one
target_link_libraries(bar PRIVATE Foo::FooLib1 Foo::FooLib2)
target_link_libraries(baz PUBLIC Foo::FooLib2) # link to lib2 only
When you link to an exported target like Foo::FooLib1, its public interface will be transitively transmitted to its user. In the example above, bar will inherit properties from the linked target.
So the INTERFACE_INCLUDE_DIRECTORIES of Foo::FooLib1 and Foo::FooLib2 will be appended to bar's INCLUDE_DIRECTORIES. Same for LINK_LIBRARIES.
For baz, not only its INCLUDE_DIRECTORIES will contain Foo::FooLib2 entries, but also its own INTERFACE_INCLUDE_DIRECTORIES will transitively transmit the usage requirements of Foo::FooLib2

How to make cmake find a shared library in a subfolder

I'm trying to learn how to make a shared library. And the following seems to work (please comment if you have some feedback to this method, I basically have no idea what I'm doing).
In my library project, I've put the header files into a folder named "include", and the source files into "src".
My library's CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(mycustomlib)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Include header files
include_directories(include)
# Create shared library
add_library(${PROJECT_NAME} SHARED ${SOURCES})
# Install library
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
# Install library headers
file(GLOB HEADERS include/*.h)
install(FILES ${HEADERS} DESTINATION include)
My application's CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(myprogram)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Create executable
add_executable(${PROJECT_NAME} ${SOURCES})
# Find and link library
find_library(MYCUSTOMLIB mycustomlib)
target_link_libraries(${PROJECT_NAME} ${MYCUSTOMLIB})
And this is working. The problem is that I want to put both the headers and the library into subfolders (specifically: /usr/local/include/mycustomlib/ for the headers, and /usr/local/lib/mycustomlib/ for the library).
So this is my attempt:
My library's new CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(mycustomlib)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Include header files
include_directories(include)
# Create shared library
add_library(${PROJECT_NAME} SHARED ${SOURCES})
# Install library
install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME})
# Install library headers
file(GLOB HEADERS include/*.h)
install(FILES ${HEADERS} DESTINATION include/${PROJECT_NAME})
My application's new CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(myprogram)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Create executable
add_executable(${PROJECT_NAME} ${SOURCES})
# Find and link library
find_library(MYCUSTOMLIB mycustomlib/mycustomlib)
target_link_libraries(${PROJECT_NAME} ${MYCUSTOMLIB})
And this is not working. Now, I'm forced to specify the .so file of the library like this:
find_library(MYCUSTOMLIB mycustomlib/libmycustomlib.so)
How come?
I'll deal with your actual problem first and offer additional comments after that. Technically speaking, you are asking CMake to find a library named mycustomlib/mycustomlib, but what you really want to say is you want find mycustomlib and it can be found in a subdirectory called mycustomlib. A couple of alternative ways to call find_library() to achieve this for your second case would be:
find_library(MYCUSTOMLIB mycustomlib PATH_SUFFIXES mycustomlib)
find_library(MYCUSTOMLIB mycustomlib PATHS /usr/local/lib/mycustomlib)
The latter is making more assumptions than it should about where you have the library installed, so I'd favour the first option. The first option assumes CMake would already find libraries in /usr/local/lib, which it seems it is from your question. You can influence where CMake looks for libraries by modifying CMAKE_PREFIX_PATH and CMAKE_LIBRARY_PATH. I'd expect either of the above options to make your second case work.
Now to other observations. You've requested a very old minimum CMake version in the first line of each of your CMakeLists.txt files. You probably want to consider at the very least making this 2.8 (personally, I'd suggest more like 3.2 or later, but it depends on what your project needs to support).
You have used file globbing to obtain your list of sources and headers. This is not robust and should generally be avoided (see a discussion of this here). You will see plenty of example code use method this for simplicity, but it is not recommended for real world projects (the CMake documentation even says not to use it). Explicitly list out your source and header files individually if you want robust builds.
If you are happy to require CMake 2.8.11 or later (and you should be these days), rather than calling include_directories() which makes everything pick up the header search path you specified, you should prefer to attach the search path requirement to the target that needs it. You do this with target_include_directories(). The equivalent of your code above would be:
target_include_directories(${PROJECT_NAME} PUBLIC include)
This gives much better control of your inter-target dependencies as your project grows in size and complexity. For a more in-depth discussion of this topic, see this article and perhaps also this one (disclosure: I wrote both articles).
Are your library and program totally separate source code repositories? Can they be built in the same project? You can build multiple targets in one CMakeLists.txt file. The project name doesn't have to have any relationship to the names of any of the targets (you often see the PROJECT_NAME variable re-used for the target name in simple examples, which is unfortunate since it suggests a relationship between the two, but for all but simple projects this won't be the case). If they are in the same repository, building them together would be a much simpler build since you wouldn't have to install the library for the executable to find it and link to it.
If they must be built in separate projects, then something like the following for the application's project should get you close:
cmake_minimum_required(VERSION 2.8.11)
project(myprogram)
# List your program's sources here explicitly
add_executable(myprogram src/foo.cpp src/bar.cpp)
# Find and link library
find_library(MYCUSTOMLIB mycustomlib PATH_SUFFIXES mycustomlib)
target_link_libraries(myprogram PUBLIC ${MYCUSTOMLIB})
# Find library's headers and add it as a search path.
# Provide the name of one header file you know should
# be present in mycustomlib's include dir.
find_path(MCL_HEADER_PATH mycustomlib.h PATH_SUFFIXES mycustomlib)
target_include_directories(myprogram PUBLIC ${MCL_HEADER_PATH})
For extra points, you could try to confirm that the header path is in the same area as the library by checking the common path prefix, or you could just derive
the MCL_HEADER_PATH from the MYCUSTOMLIB path by assuming a directory structure. Both approaches have advantages and drawbacks. If you want to explore the latter, the get_filename_component() command will be your friend.
Hopefully that points you in the right direction.

CMake export package that relies on external library

I have a project written using C++ and CMake, using Boost, that I'm trying to make a standalone binary/header package for to allow other people to link against my work. I'm using cmake installers for this. However, I'm running into issues with install(EXPORTS ...) when my library links to an external library. In particular, the Boost library and header directory locations are hard-coded into the exported file, and I can't figure out how to make it work better.
Have an example. (Untested; if it's not clear I can elaborate or fix it.)
CMakeLists.txt:
package(MyLibrary)
set(MyLibrary_VERSION 1.0)
find_component(BOOST 1.55.0 REQUIRED COMPONENTS serialization)
set(INSTALL_INCLUDE_DIR "C:/MyLibrary/include")
set(INSTALL_SRC_DIR "C:/MyLibrary/include")
set(INSTALL_BIN_DIR "C:/MyLibrary/bin")
set(INSTALL_LIB_DIR "C:/MyLibrary/lib")
set(INSTALL_CMAKE_DIR "C:/MyLibrary/cmake")
set(HEADERS myfile.hpp)
set(SOURCES myfile.cpp)
install(FILES ${HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR} COMPONENT headers)
install(FILES ${SOURCES} DESTINATION ${INSTALL_SRC_DIR} COMPONENT sources)
add_library(MyLibrary STATIC
${HEADERS} ${SOURCES})
target_link_libraries(MyLibrary
${Boost_SERIALIZATION_LIBRARY})
target_include_directories(MyLibrary
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR};${Boost_INCLUDE_DIRS}>"
PUBLIC "$<INSTALL_INTERFACE:include;${Boost_INCLUDE_DIRS}>")
install(TARGETS MyLibrary EXPORT MyLibrary-depends
DESTINATION ${INSTALL_LIB_DIR} COMPONENT libraries)
configure_package_config_file(MyLibraryConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
VERSION ${MyLibrary_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
DESTINATION "${INSTALL_CMAKE_DIR}")
install(EXPORT MyLibrary-depends
FILE MyLibrary-depends.cmake
DESTINATION "${INSTALL_CMAKE_DIR}")
MyLibraryConfig.cmake.in
#PACKAGE_INIT#
if (NOT MyLibrary_FOUND)
set(MyLibrary_FOUND 1)
find_package(Boost 1.55.0 COMPONENTS SERIALIZATION)
include(MyLibrary-depends.cmake)
# random directory stuff, etc.
endif()
The issue is that MyProject-depends.cmake ends up with the value of ${Boost_INCLUDE_DIRS} and ${Boost_SERIALIZATION_LIBRARY}, which are both absolute paths and screw up the portability of the install.
I've tried a couple of things, none of which seem to fix all my problems.
target_include_directories:
I tried escaping the $, with the hope that MyProject-depends.cmake would pick up the value of the Boost_INCLUDE_DIRS variable on include-time:
target_include_directories(MyProject
PUBLIC "$<INSTALL_INTERFACE:include;\${Boost_INCLUDE_DIRS}>"
...)
But, of course, INSTALL_INTERFACE thinks that ${Boost_INCLUDE_DIRS} is a relative path and prefixes it wit {$_IMPORT_DIR} which breaks everything.
I can ditch the MyProject-depends.cmake route entirely, and add it into MyProjectConfig.cmake.in:
CMakeLists.txt:
target_include_directories(MyProject
PUBLIC "$<INSTALL_INTERFACE:include>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR};${Boost_INCLUDE_DIRS>")
and MyProjectConfig.cmake.in:
include(MyProject-depends.cmake)
set_target_properties(MyProject
INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}")
That option seems to work but is a pain.
target_link_libraries:
I'm having more trouble with the library linking. I tried the same trick, moving stuff into the MyProjectConfig.cmake.in file for more control, but
target_link_libraries(MyProject ${Boost_SERIALIZATION_LIBRARIES})
doesn't work on imported libraries, and
set_target_properties(MyProject INTERFACE_LINK_LIBRARIES ${Boost_SERIALIZATION_LIBRARY})
fails because ${Boost_SERIALIZATION_LIBRARY} expands to something like optimized;C:/boost/stage/lib/boost_serialization.lib;debug;C:/boost/stage/lib/boost_serialization.libd and set_target_properties doesn't like the keywords.
Now I'm left with some sort of remapping using
"$<$<CONFIG:DEBUG>:${Boost_SERIALIZATION_LIBRARY_DEBUG}>$;<$<CONFIG:RELEASE>:${Boost_SERIALIZATION_LIBRARY_RELEASE}>"
but I'll also have to detect whether or not a debug library is specified... which is doable, but seems like yak shaving to me.
So, sages of the stack... any advice? Is there some obvious module or clever method that I'm overlooking?
(And thanks for making it all the way through!
Also: the cmake install(EXPORTS ...) documentation contains the helpful line "If a library target is included in the export but a target to which it links is not included the behavior is unspecified." Yeah, basically, I'm looking for a workaround.
I ended up with the last target_link_libraries answer, ditching the built-in import structure entirely and writing the CMake module to remap
optimized;C:/boost/stage/lib/boost_serialization.lib;debug;C:/boost/stage/lib/boost_serialization.libd
into
$<$<CONFIG:DEBUG>:${Boost_SERIALIZATION_LIBRARY_DEBUG}>$;<$<CONFIG:RELEASE>:${Boost_SERIALIZATION_LIBRARY_RELEASE}>
Not at all pretty, but it was the best I could come up with. So it goes.