CMake Tutorial
Generating LCM type bindings with CMake
This tutorial will walk you through writing a CMakeLists.txt
to generate
bindings for your LCM message types. Please note that this is not meant to
serve as a general CMake tutorial. This tutorial assumes that you are already
familiar with CMake.
Note
This tutorial assumes that you are using CMake 3.1 or later. Some of the
features used, especially those related to the creation of a convenient
INTERFACE
library for C++ bindings, are not available in older versions of
CMake. If you are using an older version of CMake, you may need to refer to
the implementation details of LCM’s helper functions, found in
lcmUtilities.cmake
, in order to manually accomplish the tasks that the
helper functions would normally do. Note that all of the helper functions
require CMake 3.1 or later on Windows, and that it is much more difficult to
use lcm-gen
on Windows from within CMake prior to CMake 3.1.
Initial Setup
The very first thing you’ll want to do is to find LCM and include its “use
file”. (A “use file” is a CMake script provided with some packages that adds
utility functions for using that package. Many, including LCM’s, provide
additional documentation of their utility functions in the use file itself.)
Depending on how you will be using LCM, as well as personal preference, this
can be done in your project’s root CMakeLists.txt
. This is also a good time
to look for additional language components you may want, such as Python and
Java. (If you will not need bindings for these languages, you can omit those
parts. Alternatively, you may want to make them REQUIRED
.)
find_package(lcm REQUIRED)
include(${LCM_USE_FILE})
find_package(PythonInterp)
find_package(Java)
if(JAVA_FOUND)
include(UseJava)
endif()
The rest of the CMake logic we will show will typically go in the
CMakeLists.txt
that is located with your LCM type files.
A Simple Example
We’ll start with a very simple example that generates a STATIC
library of
C language bindings:
lcm_wrap_types(
C_SOURCES c_sources
C_HEADERS c_headers
my_type_1.lcm
my_type_2.lcm
...
)
lcm_add_library(my_lcmtypes C STATIC ${c_sources} ${c_headers})
target_include_directories(my_lcmtypes INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
The first function creates rules to invoke lcm-gen
to generate the binding
source files. The names c_sources
and c_headers
are the names of variables
that will receive these lists of files. You can see where we use them again to
create the C library. We chose these names as they are both simple and clear,
but you can use whatever names you like.
The second function creates a C library, which is STATIC
, from the specified
sources. Including the header files is optional, but may be beneficial to some
IDE’s. This has a couple advantages over plain old add_library
. First, it
will link the library to LCM for you. Second, and more important, it will set
up an additional target to ensure that all of the named bindings have been
generated before the sources are compiled. This is important for some
generators when one LCM type references another; otherwise, the build tool
might try to compile the source file for one type before the header for the
referenced type has been generated, resulting in a spurious build error.
Before we call it a day, it would be nice if consumers could find the library’s headers. We do that by adding an interface include directory, like so:
target_link_libraries(my_lcmtypes INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
Note the use of $<BUILD_INTERFACE>
to tell CMake that the include directory
should not be included in the installed version of the library. This prevents
details of your build from leaking into the install. We’ll handle the include
directory for the installed library differently. (If there are no local
consumers of your library, you can skip this step.)
The include directory should be the directory where the bindings are generated.
Since we didn’t pass a DESTINATION
to lcm_wrap_types
, it defaulted to the
current subdirectory of the build tree (CMAKE_CURRENT_BINARY_DIR
).
C++
We probably want to generate at least C++ bindings also. Let’s do that now:
lcm_wrap_types(
C_EXPORT my_lcmtypes
C_SOURCES c_sources
C_HEADERS c_headers
CPP_HEADERS cpp_headers
my_type_1.lcm
my_type_2.lcm
...
)
# ...logic for C library...
lcm_add_library(my_lcmtypes-cpp CPP ${cpp_headers})
target_include_directories(my_lcmtypes-cpp INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
None of this should look surprising after seeing how the C library was created.
The additional argument to lcm_wrap_types
, CPP_HEADERS
, serves both to tell
lcm_wrap_types
to generate C++ bindings, and what variable should receive the
list of C++ headers. C_SOURCES
and C_HEADERS
work very much the same way;
by omitting both, we can skip generation of C bindings. (Unlike CPP_HEADERS
,
C_SOURCES
and C_HEADERS
are coupled; it is an error to specify one without
the other.)
“But wait,” you may be thinking to yourself, “aren’t LCM C++ bindings header
only?” Indeed they are, which is why this “library” is really an INTERFACE
library. An INTERFACE
library in CMake is just a fancy way of hanging usage
requirements (like the target_include_directories
, above) off of a target so
that consumers can consume a logical target with the same convenience that they
can consume targets that have actually binary objects backing them.
There is one critical caveat to this, however. Just like our C library needed to ensure that the bindings are generated before the sources are built, consumers of the C++ bindings need to ensure that the bindings are actually built before the consumer.
CMake 3.3 added the ability to hang dependencies off of INTERFACE
libraries,
and lcm_add_library
will do this for you for the C++ library just as for the
C library. Earlier versions of CMake lack this wonderfully convenient feature,
but fortunately, LCM can help us out. If your project might be build with CMake
earlier than 3.3, using lcm_target_link_libraries
when linking a consumer to
a C++ bindings library such as shown above will automagically set up a
dependency to ensure that the bindings are generated before trying to build the
consumer. Just use lcm_target_link_libraries
with the same arguments as you
would use target_link_libraries
. (If your project enforces use of CMake 3.3
or later, or if the bindings you are consuming are build by an external
project, just use target_link_libraries
.)
Python and Java
C and C++ are great, but you may well have users that want to consume your LCM
types with Python or Java. Just as when we added C++, we’ll start by adding
some additional arguments to lcm_wrap_types
. Depending on whether you
generate these bindings always, or opportunistically when Python and/or Java
are available, you may want to make this logic conditional, as in the approach
shown here:
if(PYTHONINTERP_FOUND)
set(python_args PYTHON_SOURCES python_sources)
endif()
if(JAVA_FOUND)
set(java_args JAVA_SOURCES java_sources)
endif()
lcm_wrap_types(
C_EXPORT my_lcmtypes
C_SOURCES c_sources
C_HEADERS c_headers
CPP_HEADERS cpp_headers
${python_args}
${java_args}
my_type_1.lcm
my_type_2.lcm
...
)
If you require Python and/or Java, you can of course simply inline the corresponding arguments as is done for C and C++. Note that the main reason to require that Python is found is so that we can match its version in order to install things to the right place. If you have some other means of determining the correct install location, you could skip requiring that Python is found.
Python doesn’t need to “build” anything (and we’ll come back to installation in a moment), so for now, the only other change is to build the JAR:
if(JAVA_FOUND)
add_jar(my_lcmtypes-jar
OUTPUT_NAME my_lcmtypes
INCLUDE_JARS lcm-java
SOURCES ${java_sources}
)
endif()
As before, if you require Java, you can omit the JAVA_FOUND
check.
Installing Everything
First, let’s revisit our variable names:
if(PYTHONINTERP_FOUND)
set(python_args PYTHON_SOURCES python_install_sources)
endif()
if(JAVA_FOUND)
set(java_args JAVA_SOURCES java_sources)
endif()
lcm_wrap_types(
C_EXPORT my_lcmtypes
C_SOURCES c_sources
C_HEADERS c_install_headers
CPP_HEADERS cpp_install_headers
${python_args}
${java_args}
my_type_1.lcm
my_type_2.lcm
...
)
Note that we’ve added _install_
to a few of the variable names. While this is
in no way necessary (recall that the variable names can be whatever you like),
it serves to clearly indicate which variables hold the names of files that need
to be installed, and which are only files to be compiled.
We’ll skip repeating the rest of the existing logic, but don’t forget to change the names of these variables in the other locations they appear.
Now, we’ll install everything:
lcm_install_headers(DESTINATION include
${CMAKE_CURRENT_BINARY_DIR}/my_lcmtypes_export.h
${c_install_headers}
${cpp_install_headers}
)
if(PYTHONINTERP_FOUND)
lcm_install_python(${python_install_sources})
endif()
install(TARGETS my_lcmtypes my_lcmtypes-cpp
EXPORT ${PROJECT_NAME}Targets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib${LIB_SUFFIX}
ARCHIVE DESTINATION lib${LIB_SUFFIX}
INCLUDES DESTINATION include
)
if(JAVA_FOUND)
install_jar(my_lcmtypes-jar share/java)
endif()
The two LCM-provided helper functions we used here, lcm_install_headers
and
lcm_install_python
, are convenience functions that will preserve subdirectory
components (particularly, the package name subdirectory of the C++ headers) of
the files being installed. Additionally, lcm_install_python
chooses the
correct destination directory by default. We also use INCLUDES DESTINATION
when installing the libraries to set the interface include directories on the
libraries. This directory should match the DESTINATION
to which the headers
are installed.
Note that we specified an EXPORT
for the C/C++ libraries, but do not show
installing the target exports file or creating build-tree exports. This is
because these procedures a) are not specific to LCM, and b) must be done in a
single location for the entire project. For similar reasons, we also did not
show exporting the JAR file. (LCM itself may be used as an example of these
tasks.)
Note that exporting JAR files requires CMake 3.7 or later, or copying
UseJava.cmake
into your project. See LCM itself for an example of the latter.
(Note that consumers of exported JAR’s don’t require CMake 3.7; the created
export files are perfectly usable with much older versions of CMake.)
Other Useful Tidbits
We did not cover every possible argument to lcm_wrap_types
. Most of the
options accepted by lcm-gen
are available through lcm_wrap_types
. See
lcmUtilities.cmake
and lcm-gen --help
for more information on what options
are available.