I posted the answer below almost four years ago and it seems that it is still occasionally being upvoted. The answer "works" but it problematic in several ways. Also, in the meantime CMake now has FetchContent
which should replace ExternalProject
in most if not all use cases. In fact, anyone reading this should consider using the CMake Package Manager (CPM) which wraps FetchContent
and provides some very nice features while being a relatively minimal step up in complexity over FetchContent
. The new example below uses FetchContent
but could easily be modified to use CPM
.
This code installs libgpiod
but should be usable with only minor changes to for installing other packages built with autotools
.
include(FetchContent)
# This integrates libgpiod into the CMake build process.
# libgpiod uses `autotools`, sometimes referred to as (autohell)[https://www.shlomifish.org/open-source/anti/autohell/].
# While the cmake code below is fairly clean, it was not so easy to discover.
# Here are some notes for things I figured out that may be useful to anyone
# trying to do something similar.
#
# 1. `autotools` typically is used to configure and build in the source directory.
# This is not ideal for CMake, which prefers to build in a separate directory.
# The pattern used below does the trick: set the WORKING_DIRECTORY to the
# binary/build directory, but reference autgen.sh with the source directory path.
# 1b. `autotools` requires packages that we do not typically install in our build
# images. I had to modify our docker image to include pkg-config & autoconf-archive.
# The error messages before doing this were not very helpful.
# 2. `FetchContent` is nicer to use than `ExternalProject`. The structure below
# is similar to how we use `FetchContent` elsewhere.
# 3. `autotools` uses the `CC` and `CXX` environment variables to determine the
# compiler to use. We want to ensure that when cross-compiling these variables
# are set to the correct compiler as defined by the toolchain. The code below
# sets these variables in the environment for the `autogen.sh` script.
# 4. `autotools` uses the `--host` flag to determine the target architecture.
# 5. The INSTALL directory is simply the root of the build directory, e.g.
# `builds/min-x86` in our build system. In that directory all of the built
# library and header artifacts are placed into the `lib` and `include`
# directories, respectively. This is the expected struture for CMake,
# and we largely get it for free by using `FetchContent`.
# 6. We use `pkg-config` to find the installed library. This is nice but requires
# that CMAKE_PREFIX_PATH include the INSTALL directory, which bizarrely is
# not the default.
# 7. `pkg_check_modules` with the `IMPORTED_TARGET` option is able to create
# a modern cmake target for the library. Such targets are always scoped with
# `PkgConfig::`. In this case, the target is `PkgConfig::gpiod`.
set(INSTALL ${PROJECT_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${INSTALL})
find_package(PkgConfig REQUIRED)
FetchContent_Declare(libgpiod
GIT_REPOSITORY "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git"
GIT_TAG v2.0.1
UPDATE_DISCONNECTED ON
)
# If libgpiod is already installed, we can use pkg-config to find it.
pkg_check_modules(gpiod IMPORTED_TARGET libgpiod)
if(NOT libgpiod_POPULATED AND NOT gpiod_FOUND)
FetchContent_Populate(libgpiod)
set(ENV${CC} ${CMAKE_C_COMPILER})
set(ENV{CXX} ${CMAKE_CXX_COMPILER})
# Configure
execute_process(
OUTPUT_QUIET
COMMAND ${libgpiod_SOURCE_DIR}/autogen.sh --host=${CMAKE_SYSTEM_PROCESSOR} -enable-tools=yes --enable-bindings-cxx --prefix=${INSTALL}
WORKING_DIRECTORY ${libgpiod_BINARY_DIR}
)
# Build
execute_process(
OUTPUT_QUIET
COMMAND make
WORKING_DIRECTORY ${libgpiod_BINARY_DIR}
)
# Install
execute_process(
OUTPUT_QUIET
COMMAND make install
WORKING_DIRECTORY ${libgpiod_BINARY_DIR}
)
pkg_check_modules(gpiod REQUIRED IMPORTED_TARGET libgpiod)
endif()
The 4-year old answer is below, which might be useful as reference.
I needed to do something similar but found it surprisingly difficult to get a working solution, despite the example provided here with the accepted answer, and code snippets provided in several other blog posts, the CMake support email listserv archives, etc. For the benefit of others who come across this question, here is my solution.
The external project we wanted to use is libmodbus
, though I believe my solution is general enough to work with any project configured with the standard autoconf
recipe of ./autoconf.sh && configure.sh && make && make install
.
We wanted to add libmodbus
as a submodule of our git repository. We added to our repository at the path <root>/opt/libmodbus
. The CMake
code to configure it is located in <root>/cmake/modbus.cmake
, which is included from our root CMakeLists.txt
using
# libmodbus
include(cmake/modbus.cmake)
The content of cmake/modbus.cmake
is:
include(ExternalProject)
set(MODBUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/opt/libmodbus)
set(MODBUS_BIN ${CMAKE_CURRENT_BINARY_DIR}/libmodbus)
set(MODBUS_STATIC_LIB ${MODBUS_BIN}/lib/libmodbus.a)
set(MODBUS_INCLUDES ${MODBUS_BIN}/include)
file(MAKE_DIRECTORY ${MODBUS_INCLUDES})
ExternalProject_Add(
libmodbus
PREFIX ${MODBUS_BIN}
SOURCE_DIR ${MODBUS_DIR}
DOWNLOAD_COMMAND cd ${MODBUS_DIR} && git clean -dfX && ${MODBUS_DIR}/autogen.sh
CONFIGURE_COMMAND ${MODBUS_DIR}/configure --srcdir=${MODBUS_DIR} --prefix=${MODBUS_BIN} --enable-static=yes --disable-shared
BUILD_COMMAND make
INSTALL_COMMAND make install
BUILD_BYPRODUCTS ${MODBUS_STATIC_LIB}
)
add_library(modbus STATIC IMPORTED GLOBAL)
add_dependencies(modbus libmodbus)
set_target_properties(modbus PROPERTIES IMPORTED_LOCATION ${MODBUS_STATIC_LIB})
set_target_properties(modbus PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${MODBUS_INCLUDES})
A component that uses libmodbus
can declare its dependency as usual:
add_executable(hello_modbus main.cpp)
target_link_libraries(hello_modbus modbus)
A few notes:
- This abuses the
DOWNLOAD_COMMAND
to perform the autogen.sh
step. The git clean -dfX
is probably not necessary (it is a leftover from an earlier version that used the BUILD_IN_SOURCE
option. If you really want to download the code instead of using a git submodule, you'll need to modify this line appropriately.
- We go to the trouble to force a static-only build of the library. Adjust your
configure
command line if you want shared libraries.
- The
set_target_properties
command to set the IMPORTED_LOCATION
will fail without the BUILD_BYPRODUCTS ${MODBUS_STATIC_LIB}
declaration.
- Likewise, the
set_target_properties
command to set the INTERFACE_INCLUDE_DIRECTORIES
will fail without the file(MAKE_DIRECTORY ${MODBUS_INCLUDES})
.