[CMake] How do you handle recursive dependencies in CMake

Cfyz cfyzium at gmail.com
Tue Jun 28 08:41:08 EDT 2016


Hello.

I've posted a message a few days ago on a very similar subject. Didn't know
there was a discussion already.

The main problem in my opinion is that CMake target exporting system does
not handle transitive dependencies at all. CMake handles them correctly at
the build step, but not at the export. As I've noted in my message there is
a small hint on that being a conscious decision: documentation about
target_link_libraries and relevant target properties advises not to include
paths to dependencies into the target interface as it would hard-code
dependencies' locations. Personally I do not agree with that as I see
neither any way around specifying paths to dependencies not any substantial
harm in that. Maybe I am missing something but as far as I've found there
was not discussion about this matter before.

With that there are some ways to make transitive dependencies work. Though
you'll have to forsake most of CMake's export machinery. Probably the
easiest way is to make each library export it's full interface in the
Foo_INCLUDE_DIRS / Foo_LIBRARIES variables. By writing the AConfig.cmake by
hand you can figure out and export the full path to the A. Therefore
AConfig.cmake will essentially be:

  get_filename_component(A_CONFIG_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
  find_library(A_LIB "A" HINTS ${A_CONFIG_DIR}/../../../lib)
  set(A_INCLUDE_DIRS ${A_CONFIG_DIR}/../../../include)
  set(A_LIBRARIES ${A_LIB})
  set(A_FOUND TRUE)

>From here on, every other library which care about its dependencies will
have to export its interface in a similar way. However, since BConfig.cmake
cannot find A by itself, it will need some help from the project
configuration step. B project will need to gather its interface and do a
configure_file() fixing B's interface dependencies in it:

  find_package(A)
  list(APPEND B_INTERFACE_INCLUDE_DIRS ${A_INCLUDE_DIRS})
  list(APPEND B_INTERFACE_LIBRARIES ${A_LIBRARIES}) # here comes the full
path to A library
  ...
  configure_file(BConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/BConfig.cmake @ONLY)

Where BConfig.cmake.in may look like:

  get_filename_component(B_CONFIG_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
  find_library(B_LIB "B" HINTS ${B_CONFIG_DIR}/../../../lib)
  set(B_INCLUDE_DIRS ${B_CONFIG_DIR}/../../../include
@B_INTERFACE_INCLUDE_DIRS@)
  set(B_LIBRARIES ${B_LIB} @B_INTERFACE_LIBRARIES@)
  set(B_FOUND TRUE)

Now find_package(B) will export B_INCLUDE_DIRS / B_LIBRARIES which will
list full paths to everything necessary to build against it.

At my company we usually use static libraries so that it's rarely a problem
but with dynamic libraries (.so) this approach will not be enough to run
the app. Searching for dynamic libraries is not exactly the build system
responsibility, though and greatly depends on the platform and the way you
distribute the resulting application. If anything it always possible to
make another transitive Foo_RPATHS property.

This 'simple' approach does not work with libraries that export 'imported
targets' only (if I recall correctly, the Qt find module exports Qt::XYZ
targets but not Qt_LIBRARIES) so that there is no interface variables to
merge. In this case one would need to iterate over imported targets and
merge their INTERFACE_xxx properties in a similar way.

I believe this to be an oversight. Everything should be fine if there was
an option to resolve and export transitive dependencies along the targets
in CMake export() command. Probably this may be fixed by an external helper
script (similar to CMakePackageConfigHelpers module) providing some
function that takes a list of targets, analyzes them and writes a config
file complete with targets and their dependencies.

On 28 June 2016 at 13:35, Sven Baars <s.baars at rug.nl> wrote:

> Hey Ray,
>
> Project A is used by many projects, and so is project B. So
> ExternalProject is not the right solution. I just want to be able to fix
> project B in such a way that people, including myself, can easily use it
> without knowledge of where project A is located. The packages will all
> be used mostly on supercomputers, where users never have rights to
> install anything, and even if they install them as a module, it is still
> in a non-standard path. As you can see from the example, this works fine
> for project B which depends on A, but not for project C which depends on
> B. Even if you set the PATH correctly.
>
> Sven
>
> On 06/28/2016 12:25 PM, Raymond Wan wrote:
> > Hi Sven,
> >
> >
> > On Tue, Jun 28, 2016 at 6:03 PM, Sven Baars <s.baars at rug.nl> wrote:
> >> The packages I use are installed in a non-standard path, because I don't
> >> have access to the system directories on most systems I work on, but are
> >> used by many other libraries. They are also all separate packages, not
> >> packages in the same source tree. I did not see my attachment on the
> >> mailing list archive, so instead I just put it on Github. It can be
> >> found here:
> >>
> >> https://github.com/Sbte/cmake-example
> >>
> >> I hope the example shows my workflow, and also what doesn't work for me.
> >
> > Hmmmm, in that case, I should probably remain quiet as this is not
> > quite something I've had to do.
> >
> > Maybe the only advice I can give is, if you plan to distribute your
> > work to others, what are your expectations in terms of where do you
> > think users should install these dependencies.
> >
> > For example, if it's a system-level directory, then maybe you can
> > consider a FIND_PACKAGE solution which has a list of default paths to
> > search.  When you're developing on your own computer, you just change
> > the default path to include paths in your home directory.
> >
> > There is also the ExternalProject () directive which you could
> > consider [https://cmake.org/cmake/help/v3.3/module/ExternalProject.html]
> > .  This would retrieve the dependencies from outside sources (i.e.,
> > repositories) and build them.  This would keep everything within the
> > same build/ path and I haven't done that (and didn't mention it
> > before) because all of my packages are within the same source tree.
> >
> > So, still not quite your situation, though.
> >
> > I hope this helps!  Perhaps others can chime in and help you with
> > exactly what you're stuck with...
> >
> > Ray
>
> --
>
> Powered by www.kitware.com
>
> Please keep messages on-topic and check the CMake FAQ at:
> http://www.cmake.org/Wiki/CMake_FAQ
>
> Kitware offers various services to support the CMake community. For more
> information on each offering, please visit:
>
> CMake Support: http://cmake.org/cmake/help/support.html
> CMake Consulting: http://cmake.org/cmake/help/consulting.html
> CMake Training Courses: http://cmake.org/cmake/help/training.html
>
> Visit other Kitware open-source projects at
> http://www.kitware.com/opensource/opensource.html
>
> Follow this link to subscribe/unsubscribe:
> http://public.kitware.com/mailman/listinfo/cmake
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://public.kitware.com/pipermail/cmake/attachments/20160628/1d1cb655/attachment.html>


More information about the CMake mailing list