Create config file for CMake project
The config file for smalllib
package should define Eigen3::Eigen
target. The most direct approach is to use find_dependency macro for Eigen3 as you use find_package
in the CMakeLists.txt
for the package itself:
Config.cmake.in (this file is used as a template for the resulted config file):
# Get definition of 'find_dependency' macro
include(CMakeFindDependencyMacro)
# Find Eigen3. This will create 'Eigen3::Eigen' target
find_dependency(Eigen3)
# Include CMake-generated config file for the 'smalllib' target
# which uses 'Eigen3::Eigen' target.
include(${CMAKE_CURRENT_LIST_DIR}/smalllib.cmake)
This way is described in the CMake documentation about the packaging.
What is the use of .cmake files in install function of CMakeLists.txt?
You may find the introductory description here helpful.
The <Package>Config.cmake
files are package configuration files. They are useful for providing a minimal set of information about an installed package, so the consumer of the package can easily use it in their CMake project. As a package maintainer of a CMake-based project, you are highly encouraged (and frankly, expected) to provide such a file, as this is the most efficient way for others to integrate your library into their project.
The consumer of your package will typically use find_package
to find the installed package:
find_package(SomePackage REQUIRED)
The search procedure for find_package
Config Mode will look for one of the following package configuration files to pull SomePackage
into a CMake project:
SomePackageConfig.cmake
somepackage-config.cmake
By providing one of these files (as the install
command supports), you make it easy for others to use your package in their own CMake project.
Craig Scott, a CMake co-maintainer, gave an in-depth presentation at CppCon 2019 providing a wealth of information about this topic.
<Package>Config.cmake for a library package
The script formfactorTargets.cmake
, generated by CMake via
install(EXPORT formfactorTargets
FILE formfactorTargets.cmake
DESTINATION cmake
)
defines only targets, which has been added to the export set with that name (formfactorTargets
).
Since you have added only a single library to it
set(lib formfactor)
...
install(
TARGETS ${lib}
EXPORT formfactorTargets
LIBRARY DESTINATION lib
RUNTIME DESTINATION lib
ARCHIVE DESTINATION lib)
the script defines only a library target formfactor
.
Linking with that target is the "modern" usage of find_package
results:
# downstream
find_package(formfactor REQUIRED CONFIG)
target_link_libraries(<my executable> PUBLIC formfactor)
It is up to your Config.cmake.in
script (which will be installed as formfactorConfig.cmake
, that is read by the find_package(formfactor)
) to provide additional information for your package and/or its additional usage.
E.g. you could set variables formfactor_INCLUDE_DIRS
and formfactor_LIBRARIES
so a downstream could use your library via "old way", assuming variable formfactor_INCLUDE_DIR
to contain include directories and variable formfactor_LIBRARIES
to contain a library file(s) which are needed to link with:
find_package(formfactor REQUIRED CONFIG)
include_directories(${formfactor_INCLUDE_DIRS})
add_executable(my_exe <sources>)
target_link_libraries(my_exe PUBLIC ${formfactor_LIBRARIES})
It is quite difficult for you (as a package's developer) to provide absolute path to the library. But you could assign variable formfactor_LIBRARIES
in a
way which "just works":
# Config.cmake.in
# Value of 'formfactor_INCLUDE_DIRS' is real: it contains the include directory.
set(formfactor_INCLUDE_DIRS @CMAKE_INSTALL_PREFIX@/include)
# Value of 'formfactor_LIBRARIES' is fake: it doesn't contain a library path.
# But linking with ${formfactor_LIBRARIES} will work, as it will link
# to the **target**, and CMake will extract a library path from it.
set(formfactor_LIBRARIES formfactor)
# In any case we need to include the export script generated by CMake:
# exactly this script defines 'formfactor' target.
include(${CMAKE_CURRENT_LIST_DIR}/formfactorTargets.cmake)
What's the use of configure_package_config_file option INSTALL_DESTINATION
The function configure_package_config_file only generates the file specified as the second argument. This function neither installs the generated file nor affects on its installation. So, its arguments can only affect on the content of the generated file.
If you look into the generated script FooConfig.cmake
, then you could find the line like
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
This is how the script calculates installation prefix based on the path to the FooConfig.cmake
file.
E.g. when you install the package with prefix /usr/local
and your FooConfig.cmake
script will be located in the directory /usr/local/lib/Foo/cmake
, then the quoted path will be expanded into /usr/local/lib/Foo/cmake/../../../
, which corresponds to /usr/local/
.
Number of components ../
in the script equals to the number of components in INSTALL_DESTINATION
parameter.
Why 'PACKAGE_PREFIX_DIR' is needed
Assume a package installs all public headers into directory include/
(relative to installation prefix), and config script needs to deliver that directory to the user via variable FOO_INCLUDE_DIR
.
The most direct way would be to write in the script following:
# FooConfig.cmake.in
...
set(FOO_INCLUDE_DIR @CMAKE_INSTALL_PREFIX@/include)
so configure_package_config_file
would substitute real install prefix instead of @CMAKE_INSTALL_PREFIX@
.
But embedding absolute path into the config file prevents the package to be relocatable. So, once installed, the package could be used only from the installation directory, and cannot be copied elsewhere.
For relocatable package a config file computes all installation paths relative to the path of the config file itself, because it is the only path known to the script when it is called by find_package
. It is like an executable can compute paths to the files located near to that executable.
If the script is installed into lib/Foo/cmake/FooConfig.cmake
, then relative path to the include directory would be ../../../include
, so one could use following assignment in that script:
# FooConfig.cmake.in
...
set(FOO_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../include)
So, when find_package(Foo)
will execute this script, it expands the variable CMAKE_CURRENT_LIST_DIR to the actual directory with the possibly relocated script.
Operating with relative paths requires much attention from the coder, so CMake allows to automate this task:
# CMakeLists.txt
...
# Path to the include directory in the installation tree
set(INCLUDE_DIR 'include')
configure_package_config_file(FooConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/FooConfig.cmake
INSTALL_DESTINATION lib/Foo/cmake
# Tell CMake to handle variable `INCLUDE_DIR` as a path
# under installation tree.
PATH_VARS INCLUDE_DIR
)
# FooConfig.cmake.in
@PACKAGE_INIT@
# Here we could use `@PACKAGE_INCLUDE_DIR@` as reference
# to variable 'INCLUDE_DIR' set in the CMakeLists.txt.
set_and_check(FOO_INCLUDE_DIR "@PACKAGE_INCLUDE_DIR@")
If you look into the generated file, you will find that @PACKAGE_INCLUDE_DIR@
is expanded into ${PACKAGE_PREFIX_DIR}/include
, which uses the variable PACKAGE_PREFIX_DIR
.
How to fix Could not find a package configuration file ... error in CMake?
Rants from my soul:
It sucks when you have to use any library foo
when the author fails to provide a foo-config.cmake
for you to use easily by invoking find_package(foo)
. It's absolutely outrageous when a reasonably modern project still uses hand written Makefiles as its build system. I myself is stuck with a much worse constructed SDK than yours right now.
Short answer:
Since the author of the SDK fails to provide a config file to support your cmake usage, if you still insists on invoking find_package
on the library (and you should!), you are required to write your own Module
file to clean up their mess. (Yeah, you are doing the work for the library authors).
To truly achieve cross platform usage, you should write a Findrplidar.cmake
module file to find the libraries for you.
To write a reasonable module file, you would most likely use API find_path
for header files and find_library
for libs. You should check out its docs and try using them, and maybe Google a few tutorials.
Here is my version of Findglog.cmake
for your reference. (glog authors have updated their code and supports Config
mode. Unfortunately, Ubuntu build doesn't use it, so I still have to write my own file)
find_path(glog_INCLUDE_DIR glog/logging.h)
message(STATUS "glog header found at: ${glog_INCLUDE_DIR}")
find_library(glog_LIB glog)
message(STATUS "libglog found at: ${glog_LIB}")
mark_as_advanced(glog_INCLUDE_DIR glog_LIB)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(glog REQUIRED_VARS
glog_INCLUDE_DIR
glog_LIB
)
if(glog_FOUND AND NOT TARGET glog::glog)
add_library(glog::glog SHARED IMPORTED)
set_target_properties(glog::glog PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${glog_LIB}"
INTERFACE_INCLUDE_DIRECTORIES
"${glog_INCLUDE_DIR}"
)
endif()
And you can use it like this:
find_package(glog)
target_link_libraries(main PRIVATE glog::glog)
Long answer:
The history of developers using cmake is an absolute nightmare. The internet is filled with bad practice/examples of how not to use cmake in your project, including the old official cmake tutorial (which still might be there). Mostly because no one really gives a **** (If I can build my project, who cares if it's cross platform). Another valid reason is that cmake documentations are really daunting to beginners.
This is why I am writing my own answer here, lest you get misguided by Googling elsewhere.
The nightmare is no more. The wait has ended. "The Messiah" of cmake (source) is come. He bringeth hope to asm/C/C++/CUDA projects written in 2020 and on. Here is The Word.
The link above points to the only way how cmake projects should be written and truly achieve cross platform once and for all. Note the material is not easy at all to follow for beginners. I myself spent an entire week to fully grasp what was covered in The Word, when I had become somewhat familiar with cmake concepts at the time (but lost in my old sinful ways).
The so-called "long answer" is actually shorter. It's just a pointer to the real answer. Good luck reading the Word. Embrace the Word, for anything against that is pure heresy.
Response of comment 1-5:
Good questions. A lot of those can be obtained from the Word. But the word is better digested when you become more familiar with CMake. Let me answer them in decreasing of relevance to your problem at hand.
For the ease of discussion, I'll just use libfoo as an example.
Let's say you always wants to use libfoo like this:
find_package(foo)
target_link_libraries(your_exe ... foo::foo)
Pretend foo is installed at the following location:
- /home/dev/libfoo-dev/
- include
- foo
- foo.h
- bar.h
- ...
- lib
- libfoo.so
- share
- foo/foo-config.cmake # This may or may not exist. See discussion.
Q: Only one .h
file. Why?
A: Because in the case of libfoo
(also true for glog
), only one search of header location is necessary. Just like the example from libfoo
,
where foo/foo.h
and foo/bar.h
are at the same location. So their output of find_path
would be the same: /home/dev/libfoo-dev/include
.
Q: Why I'm getting NOTFOUND
for my headers and libs?
A: The function find_path
and find_library
only search locations specify in the documentations. By default they search system locations,
like /usr/include
and /usr/lib
respectively. Refer to the official docs for details on system locations. In the case of libfoo
, however,
they reside in /home/dev/libfoo-dev
. So you must specify these locations in cmake variable CMAKE_PREFIX_PATH
. It's a ;
seperated string.
One would do cmake -D CMAKE_PREFIX_PATH="/home/dev/libfoo-dev;/more/path/for/other/libs/;...;/even/more/path" ....
on the command line.
One very important note: unlike Unix command find
, find_path
will only search specific paths inside /home/dev/libfoo-dev
, not all the way down:include
(usually also include/{arch}
where {arch} is sth like x86_64-linux-gnu
for x86 Linux) for find_path
; lib
variant for find_library
,
respectively. Unusual locations would require passing in more arguments, which is uncommon and most likely unnecessary.
For this very reason, for libfoo
, calling find_path(... foo.h ...)
is undesired. One would want find_path(... foo/foo.h ...)
. Refer to the docs
for more details. You can also try out yourself.
Also for this reason, it is desirable to organize libraries in the usual bin include lib share
quad on Unix-like systems. I'm not familiar with Windows.
Q: Debug
& Release
A: There are several options. The easiest one might be:
- Prepare rplidar debug and release build in two different folders,
/path/to/debug
&/path/to/release
for instance - Passing to
Debug
&Release
build respectively (cmake -D CMAKE_PREFIX_PATH="/path/to/debugORrelease" ....
)
There are definitely others ways, but perhaps requires special care in your Findrplidar.cmake
script (maybe some if
statements).
Q: Why glog::glog
rather than glog
?
A: It's just modern cmake practice, with small benefits. Not important right now. Refer to the Word if you are interested.
Q: You mentioned that you are writing rplidarConfig.cmake
. Instead you should rename the file to Findrplidar.cmake
.
A: CMake philosophy is as such:
- Library authors should write
foo-config.cmake
orfooConfig.cmake
- When they fail to provide one, it sucks. And according to the Messiah, it should be reported as a bug.
- In this case, you as library user, should write
Findfoo.cmake
by guessing how to describe the dependencies for libfoo. For simple libraries, this is not so bad. For complex ones, like Boost, this sucks!
A few side note on this topic:
- Note how
Findfoo.cmake
is written by library users, from guessing. - This is insane! Users shouldn't do this. This is the authors' fault, to put their users in this uncomfortable situation.
- A
foo-config.cmake
file is extremely easy to write for authors of libfoo, IF they follow the Word exactly. - Extremely easy for the authors in the sense that: cmake can take care of everything. It will generate scripts automatically for the authors to use in their
foo-config.cmake
file. - Guaranteed to be cross-platform and easy to use by the users, thanks to cmake.
- However, the reality sucks. Now you have to write
Findfoo.cmake
Q: Why only find_package
& target_link_libraries
?
A: This is what the Word says. It's therefore good practice. Why the Word says so is something you have to find out yourself.
It's not possible for me to explain the gist of the Word in this answer, nor would it be convincing to you. I would just say the following:
It's very easy to write spaghetti CMakeLists that are next to impossible to maintain. The spirit of the Word helps you avoid that by
forcing you to carefully think about:
- library structure: public vs private headers, for example. This makes you think about what to include in your headers and public APIs.
- build specification: what is necessary to build a library you write (what to include; what to link)
- usage requirement: what is necessary for others to use a library you write (what to include; what to link)
- dependencies: what is the relationship of the library you write & its dependencies
- Maybe more
If you think about it, these aspects are crucial to writing a cross-platform and maintainable library.include_directories
, link_directories
and add_definitions
are all very bad practice
(according to lots of sources, including the official documentations of these APIs). Bad practice tends to obscure the aspects above,
and causes problems later on when everything gets integrate together as a whole. E.g. include_directories
will add -I
to compiler for every
target written in the directory of that CMakeLists.txt
. Read this sentence a few times and Spock will tell you it's illogical.
Don't worry. It's okay for now to use them when you are not familiar with the Word (Why else would this be in the last section). Once you know the Word, refactor your CMakeLists when you have time. Bad practice might cause problem later on, when your project becomes more complex. (In my professional experience, 5 very small groups of people is enough to cause a nightmare. By nightmare I mean hard code everything in CMakeLists; Create a git branch for every single different platform/device/environment; Fixing a bug meaning to cherry-pick one commit to each branch. I've been there before knowing the Word.)
The practice of the Word very well utilize the philosophy of modern CMake, to encapsulate build specifications and usage requirements inside
CMake targets. So when target_link_libraries
is called, these properties gets propagated properly.
Related Topics
C++ Unordered_Map Fail When Used with a Vector as Key
Correct Way to Inherit from Std::Exception
C++ on Small-Footprint Microcontrollers
How to Build Libcxx and Libcxxabi by Clang on Centos 7
How to Execute Two Threads Asynchronously Using Boost
Lambdas Require Capturing 'This' to Call Static Member Function
What Exactly Is Va_End For? Is It Always Necessary to Call It
Is It Valid for a Lambda To, Essentially, Close Over Itself
How to Speed Up Floating-Point to Integer Number Conversion
Copy Constructor in C++ Is Called When Object Is Returned from a Function
Print Template Typename at Compile Time
Can Member Functions Be Used to Initialize Member Variables in an Initialization List
Quick and Dirty Way to Profile Your Code
Opencv Read Jpeg Image from Buffer
How to Place a MACro in a Namespace in C++