Skip to content

Commit

Permalink
Add documentation on CMake package installation process.
Browse files Browse the repository at this point in the history
- A lot of users find the CMake package installation process confusing,
  particularly the use of imported targets when exporting their own
  projects which use Ceres.
- This patch adds a brief description of the overall process and the
  main bear-trap users are ensnared by.

Change-Id: I44c022bbd18a393868bf88ea9ddd807c5e08abc9
  • Loading branch information
alexsmac committed Apr 6, 2015
1 parent e2a7169 commit d95f919
Showing 1 changed file with 179 additions and 17 deletions.
196 changes: 179 additions & 17 deletions docs/source/building.rst
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ Options controlling Ceres dependency locations
----------------------------------------------

Ceres uses the ``CMake``
`find_package <http://www.cmake.org/cmake/help/v2.8.12/cmake.html#command:find_package>`_
`find_package <http://www.cmake.org/cmake/help/v3.2/command/find_package.html>`_
function to find all of its dependencies using
``Find<DEPENDENCY_NAME>.cmake`` scripts which are either included in Ceres
(for most dependencies) or are shipped as standard with ``CMake``
Expand Down Expand Up @@ -621,50 +621,212 @@ Using Ceres with CMake
======================

Once the library is installed with ``make install``, it is possible to
use CMake with `FIND_PACKAGE()
<http://www.cmake.org/cmake/help/v2.8.10/cmake.html#command:find_package>`_
in order to compile **user code** against Ceres. For example, for
use CMake with `find_package()
<http://www.cmake.org/cmake/help/v3.2/command/find_package.html>`_
in order to compile **user code** against Ceres. For example, to compile
`examples/helloworld.cc
<https://ceres-solver.googlesource.com/ceres-solver/+/master/examples/helloworld.cc>`_
the following CMakeList.txt can be used:
in a separate, standalone project, the following CMakeList.txt can be used:

.. code-block:: cmake
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
cmake_minimum_required(VERSION 2.8)
PROJECT(helloworld)
project(helloworld)
FIND_PACKAGE(Ceres REQUIRED)
INCLUDE_DIRECTORIES(${CERES_INCLUDE_DIRS})
find_package(Ceres REQUIRED)
include_directories(${CERES_INCLUDE_DIRS})
# helloworld
ADD_EXECUTABLE(helloworld helloworld.cc)
TARGET_LINK_LIBRARIES(helloworld ${CERES_LIBRARIES})
add_executable(helloworld helloworld.cc)
target_link_libraries(helloworld ${CERES_LIBRARIES})
Understanding the CMake Package System
----------------------------------------

Although a full tutorial on CMake is outside the scope of this guide, here
we cover some of the most common CMake misunderstandings that crop up
when using Ceres. For more detailed CMake usage, the following references are
very useful:

- The `official CMake tutorial <http://www.cmake.org/cmake-tutorial/>`_

Provides a tour of the core features of CMake.

- `ProjectConfig tutorial <http://www.cmake.org/Wiki/CMake/Tutorials/How_to_create_a_ProjectConfig.cmake_file>`_ and the `cmake-packages documentation <http://www.cmake.org/cmake/help/git-master/manual/cmake-packages.7.html>`_

Cover how to write a ``ProjectConfig.cmake`` file, discussed below, for
your own project when installing or exporting it using CMake. It also covers
how these processes in conjunction with ``find_package()`` are actually
handled by CMake. The
`ProjectConfig tutorial <http://www.cmake.org/Wiki/CMake/Tutorials/How_to_create_a_ProjectConfig.cmake_file>`_
is the older style, currently used by Ceres for compatibility with older
versions of CMake.

.. NOTE :: **Targets in CMake.**
All libraries and executables built using CMake are represented as
*targets* created using
`add_library()
<http://www.cmake.org/cmake/help/v3.2/command/add_library.html>`_
and
`add_executable()
<http://www.cmake.org/cmake/help/v3.2/command/add_executable.html>`_.
Targets encapsulate the rules and dependencies (which can be other targets)
required to build or link against an object. This allows CMake to
implicitly manage dependency chains. Thus it is sufficient to tell CMake
that a library target: ``B`` depends on a previously declared library target
``A``, and CMake will understand that this means that ``B`` also depends on
all of the public dependencies of ``A``.
When a project like Ceres is installed using CMake, in addition to the
public headers and compiled libraries, a set of CMake-specific project
configuration files are also installed to: ``<INSTALL_ROOT>/share/Ceres``.
When `find_package
<http://www.cmake.org/cmake/help/v3.2/command/find_package.html>`_
is invoked, CMake checks various standard install locations (including
``/usr/local`` on Linux & UNIX systems), for installed CMake configuration files
for the project to be found (i.e. Ceres in the case of ``find_package(Ceres)``).
Specifically it looks for:

- ``<PROJECT_NAME>Config.cmake`` (or ``<lower_case_project_name>-config.cmake``)

Which is written by the developers of the project, and is configured with
the selected options and installed locations when the project is built and
defines the CMake variables: ``<PROJECT_NAME>_INCLUDE_DIRS`` &
``<PROJECT_NAME>_LIBRARIES`` which are used by the caller to import
the project.

The ``<PROJECT_NAME>Config.cmake`` typically includes a second file installed to
the same location:

- ``<PROJECT_NAME>Targets.cmake``

Which is autogenerated by CMake as part of the install process and defines
**imported targets** for the project in the caller's CMake scope.

An **imported target** contains the same information about a library as a CMake
target that was declared locally in the current CMake project using
``add_library()``. However, imported targets refer to objects that have already
been built previously by a different CMake project. Principally, an imported
target contains the location of the compiled object and all of its public
dependencies required to link against it. Any locally declared target can
depend on an imported target, and CMake will manage the dependency chain, just
as if the imported target had been declared locally by the current project.

Crucially, just like any locally declared CMake target, an imported target is
identified by its **name** when adding it as a dependency to another target.

Thus, if in a project using Ceres you had the following in your CMakeLists.txt:

.. code-block:: cmake
find_package(Ceres REQUIRED)
message("CERES_LIBRARIES = ${CERES_LIBRARIES}")
You would see the output: ``CERES_LIBRARIES = ceres``. **However**, here
``ceres`` is an **imported target** created when ``CeresTargets.cmake`` was
read as part of ``find_package(Ceres REQUIRED)``. It does **not** refer
(directly) to the compiled Ceres library: ``libceres.a/so/dylib/lib``. This
distinction is important, as depending on the options selected when it was
built, Ceres can have public link dependencies which are encapsulated in the
imported target and automatically added to the link step when Ceres is added
as a dependency of another target by CMake. In this case, linking only against
``libceres.a/so/dylib/lib`` without these other public dependencies would
result in a linker error.

Although this description covers projects that are **installed** using CMake, it
also holds for projects that are **exported** using CMake using
`export() <http://www.cmake.org/cmake/help/v3.2/command/export.html>`_
instead of
`install() <http://www.cmake.org/cmake/help/v3.2/command/install.html>`_.
When a project is *installed*, the compiled libraries and headers are copied
from the source & build directory to the install location, and it is these
copied files that are used by any client code. When a project is *exported*,
instead of copying the compiled libraries and headers, CMake creates an entry
for the project in ``<USER_HOME>/.cmake/packages`` which contains the path to
the project's build directory which will be checked by CMake during a call to
``find_package()``. The effect of which is that any client code uses the
compiled libraries and headers in the build directory directly, thus not
requiring the project to be installed to be used.

Installing / Exporting a project that uses Ceres
--------------------------------------------------

As described in `Understanding the CMake Package System`_, the contents of
the ``CERES_LIBRARIES`` variable is the **name** of an imported target which
represents Ceres. If you are installing / exporting your *own* project which
*uses* Ceres, it is important to understand that:

**imported targets are not (re)exported when a project which imported them is
exported**.

Thus, when a project ``Foo`` which uses Ceres is exported, its list of
dependencies as seen by another project ``Bar`` which imports ``Foo`` via:
``find_package(Foo REQUIRED)`` will contain: ``ceres``. However, the
definition of ``ceres`` as an imported target is **not (re)exported** when Foo
is exported. Hence, without any additional steps, when processing ``Bar``,
``ceres`` will not be defined as an imported target. Thus, when processing
``Bar``, CMake will assume that ``ceres`` refers only to:
``libceres.a/so/dylib/lib`` (the compiled Ceres library) directly if it is on
the current list of search paths. In which case, no CMake errors will occur,
but ``Bar`` will not link properly, as it does not have the required public link
dependencies of Ceres, which are stored in the imported target defintion.

The solution to this is for ``Foo`` (i.e., the project that uses Ceres) to
invoke ``find_package(Ceres)`` in ``FooConfig.cmake``, thus ``ceres`` will be
defined as an imported target when CMake processes ``Bar``. An example of the
required modifications to ``FooConfig.cmake`` are show below:

.. code-block:: cmake
# When configure_file() is used to generate FooConfig.cmake from
# FooConfig.cmake.in, @Ceres_DIR@ will be replaced with the current
# value of Ceres_DIR being used by Foo. This should be passed as a hint
# when invoking find_package(Ceres) to ensure that the same install of
# Ceres is used as was used to build Foo.
set(CERES_DIR_HINTS @Ceres_DIR@)
# Forward the QUIET / REQUIRED options.
if (Foo_FIND_QUIETLY)
find_package(Ceres QUIET HINTS ${CERES_DIR_HINTS})
elseif (Foo_FIND_REQUIRED)
find_package(Ceres REQUIRED HINTS ${CERES_DIR_HINTS})
else ()
find_package(Ceres HINTS ${CERES_DIR_HINTS})
endif()
# Add Ceres to the list of dependencies for Foo, which will be used
# by the calling project when adding Foo as a dependency to a target.
if (CERES_FOUND)
list(APPEND FOO_INCLUDE_DIRS ${CERES_INCLUDE_DIRS})
list(APPEND FOO_LIBRARIES ${CERES_INCLUDE_DIRS})
endif()
Specify Ceres version
---------------------

Additionally, when CMake has found Ceres it can check the package
version, if it has been specified in the `FIND_PACKAGE()
<http://www.cmake.org/cmake/help/v2.8.10/cmake.html#command:find_package>`_
version, if it has been specified in the `find_package()
<http://www.cmake.org/cmake/help/v3.2/command/find_package.html>`_
call. For example:

.. code-block:: cmake
FIND_PACKAGE(Ceres 1.2.3 REQUIRED)
find_package(Ceres 1.2.3 REQUIRED)
The version is an optional argument.

Local installations
-------------------

If Ceres was installed in a non-standard path by specifying
-DCMAKE_INSTALL_PREFIX="/some/where/local", then the user should add
the **PATHS** option to the ``FIND_PACKAGE()`` command, e.g.,
``-DCMAKE_INSTALL_PREFIX="/some/where/local"``, then the user should add
the **PATHS** option to the ``find_package()`` command, e.g.,

.. code-block:: cmake
FIND_PACKAGE(Ceres REQUIRED PATHS "/some/where/local/")
find_package(Ceres REQUIRED PATHS "/some/where/local/")
Note that this can be used to have multiple versions of Ceres
installed.

0 comments on commit d95f919

Please sign in to comment.