[CMake] Modular build system for repository with libraries shared between multiple targets.

Mateusz Zych mte.zych at gmail.com
Mon Aug 13 14:40:57 EDT 2018


Hi Michael, thanks for reply!

Essentially *I don't want to artificially couple app executable and tool
shared library*
by defining repo project with both targets.

I have three main motivations:


1) I want to be able to load into an IDE (for example CLion, Visual Studio,
Qt Creator)
only one target that at the time (plus its dependencies).
That way IDE will not have to process code that I'm not interested at the
moment,
so I could get very fast code navigation, finding usages, code completion,
etc.

For example if I would want to work on util static library in CLion,
I would just load //repo/util/CMakeLists.txt and everything would work
great.
However, if I would want to work only on app executable,
I would be forced to load tool shared library, because I would point to
//repo/CMakeLists.txt.


2) I also want to be able to selectively check out code from repository,
so I won't be forced to download code that I don't need at the moment.

For example if I would want to work on tool shared library,
I don't want to be forced to check out //repo/app from the repository.
I think that checking out //repo/util and //repo/tool should work just fine
(plus or minus //repo/CMakeLists.txt).


3) I want to be able to analyse source code (including CMake source code)
form just part of the repo and be able to have complete knowledge of that
part (local reasoning).

For example, if I need to analyse code defining tool shared library,
I have to know that there is another place in the CMake source code (in
this case //repo/CMakeLists.txt),
which will add sub-directories with targets satisfying dependencies of tool
target (in this case add_subdirectory(util)).

The point is project repo is a global entity, with which every shared
target needs to synchronize with.
I know that I presented simple example, but think about repo with large
number of projects.
I as a developer need to manually make sure that this global list is
defined correctly - it's not something enforced automatically.

It would be much better if each project defining a target with dependencies
would add sub-directories satisfying its dependencies - no global
synchronization point needed.
Unfortunately this is not currently possible, since calling
add_subdirectory() twice is an error.

To me this situation is very similar to how header files work in C++.
It is not a good idea to write a header file, which requires another header
file included before it.
Header files should be stand-alone and I think that similar rule should
apply for CMake projects.


I hope my arguments makes sense to you and my motivation is clear.

Thank you, Mateusz Zych

2018-08-13 18:37 GMT+02:00 Michael Ellery <mellery451 at gmail.com>:

> it’s not clear to me why you are trying to do this multiple project
> thing…I mean why not just?:
>
> project(myrepo)
> add_subdirectory(util)
> add_subdirectory(tool)
> add_subdirectory(app)
>
> and then if you only want to build util for instance:
>
> cd build && cmake .. && cmake --build . --target=util
>
> is there some other reason using the targets generated by the project
> doesn’t work for you?
>
> -Mike
>
> > On Aug 12, 2018, at 5:45 AM, Mateusz Zych <mte.zych at gmail.com> wrote:
> >
> > Hi, I'm trying to understand what's the recommended way of
> > implementing CMake build system in the following repository:
> >
> > repo
> > |
> > |___ util
> > |    |___ include
> > |    |    |___ util
> > |    |         |___ util.h
> > |    |
> > |    |___ source
> > |    |    |___ util.cpp
> > |    |
> > |    |___ CMakeLists.txt
> > |
> > |___ tool
> > |    |___ include
> > |    |    |___ tool
> > |    |         |___ tool.h
> > |    |
> > |    |___ source
> > |    |    |___ tool.cpp
> > |    |
> > |    |___ CMakeLists.txt
> > |
> > |___ app
> >      |___ app.cpp
> >      |
> >      |___ CMakeLists.txt
> >
> > Essentially this repo contains static library util, shared library tool
> and executable app.
> >
> > Both executable app and shared library tool depend on static library
> util,
> > but they don't depend on each other.
> > This can be represented by dependency graph like so:
> >
> > app   tool
> >  |    |
> >  |    |
> >  V    V
> >   util
> >
> > The reason why static library util is placed in the root of repo,
> > is the fact that it's shared by both executable app and shared library
> tool.
> >
> > Writing //repo/util/CMakeLists.txt is very easy:
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > project(util)
> >
> > add_library(util STATIC)
> > target_sources(util PRIVATE include/util/util.h
> >                                   source/util.cpp)
> > target_include_directories(util PUBLIC include)
> >
> > However, implementing //repo/tool/CMakeLists.txt
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > # no project definition
> >
> > add_library(tool SHARED)
> > target_sources(tool PRIVATE include/tool/tool.h
> >                                   source/tool.cpp)
> > target_include_directories(tool PUBLIC include)
> > target_link_libraries(tool PRIVATE util)
> >
> > and //repo/app/CMakeLists.txt is more challenging.
> >
> > cmake_minimum_required (VERSION 3.11)
> >
> > # no project definition
> >
> > add_executable(app)
> > target_sources(app PRIVATE app.cpp)
> > target_link_libraries(app PRIVATE util)
> >
> > I understand CMake project as a set of targets, which are standalone
> (can be built by themselves).
> > Source: https://stackoverflow.com/questions/26878379/in-cmake-
> what-is-a-project
> >
> > This meas that I can define project util consisting of one target
> (static library util),
> > because target util doesn't have any dependencies, meaning it's
> standalone.
> > Unfortunately defining project tool and project app is problematic,
> > because executable app and shared library tool do depend on static
> library util.
> >
> > I found two solutions to this issue, but neither seems "correct" to me:
> >
> > 1. Create //repo/CMakeLists.txt and define project repo which will add
> util, tool and app as sub-directories.
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > project(repo)
> > add_subdirectory(util)
> > add_subdirectory(tool)
> > add_subdirectory(app)
> >
> > This is the cleanest solution, but it has a serious drawback of not
> defining project app and project tool,
> > which could be built independently from each other.
> >
> > I thought that I will be able to mitigate this issue by
> > defining multiple projects in //repo/CMakeLists.txt each with its own
> separate set of targets.
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > project(tool)
> > add_subdirectory(util)
> > add_subdirectory(tool)
> >
> > project(app)
> > add_subdirectory(util)
> > add_subdirectory(app)
> >
> > project(repo)
> > add_subdirectory(util)
> > add_subdirectory(tool)
> > add_subdirectory(app)
> >
> > This is not possible, since one CMakeLists.txt can define only single
> project and
> > calling add_subdirectory() twice on the same directory is an error.
> > I believe these constraints arise from the fact that
> > CMake projects are not providing any scoping / grouping mechanism for
> targets.
> > Source: https://cmake.org/pipermail/cmake/2009-June/030188.html
> >
> > It is possible to create W/A mitigating this issue,
> > by using if(), elseif() and else() commands in //repo/CMakeLists.txt,
> > but this solution looks to me like an awful hack.
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > if(TOOL)
> >       project(tool)
> >       add_subdirectory(util)
> >       add_subdirectory(tool)
> > elseif(APP)
> >       project(app)
> >       add_subdirectory(util)
> >       add_subdirectory(app)
> > else()
> >       project(repo)
> >       add_subdirectory(util)
> >       add_subdirectory(tool)
> >       add_subdirectory(app)
> > endif()
> >
> > Making matters worse this approach requires global handling of
> sub-directories in a project,
> > meaning that knowledge of the whole dependency graph is required to add
> sub-directories in correct order.
> > This breaks down the most desirable properties of modern CMake -
> modularity and isolation of targets.
> >
> > 2. Define project as a self-containing set of targets by adding
> sub-directories were they are required.
> >
> > In this approach //repo/tool/CMakeLists.txt would add upper directory
> util
> > by defining not only source directory, but also binary directory.
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > project(tool)
> > add_subdirectory(../util util)
> >
> > add_library(tool SHARED)
> > target_sources(tool PRIVATE include/tool/tool.h
> >                                   source/tool.cpp)
> > target_include_directories(tool PUBLIC include)
> > target_link_libraries(tool PRIVATE util)
> >
> > Using the exact same technique //repo/app/CMakeLists.txt would also add
> upper directory util.
> >
> > cmake_minimum_required (VERSION 3.11)
> >
> > project(app)
> > add_subdirectory(../util util)
> >
> > add_executable(app)
> > target_sources(app PRIVATE app.cpp)
> > target_link_libraries(app PRIVATE util)
> >
> > This approach allows each project to be stand-alone,
> > because all sub-directories required by targets directly defined by that
> project are added directly in place.
> >
> > This is huge advantage compared to the first solution,
> > because project is responsible only for adding sub-directories,
> > which are required by direct dependencies of targets defined directly in
> that project.
> >
> > All sub-directories required for satisfying project's indirect
> dependencies
> > will be added automatically by projects defining targets,
> > which are direct dependencies of targets defined directly in that
> project.
> >
> > For example, if target util would depend on target foo,
> > then in project util sub-directory foo would be added to satisfy that
> dependency.
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > project(util)
> > add_subdirectory(../foo foo)
> >
> > add_library(util STATIC)
> > target_sources(util PRIVATE include/util/util.h
> >                                   source/util.cpp)
> > target_include_directories(util PUBLIC include)
> > target_link_libraries(util PRIVATE foo)
> >
> > So in case of app project util sub-directory would be added first,
> > then foo sub-directory would be added by the util project.
> > Effectively adding sub-directory will create a cascade of
> add_subdirectory() commands,
> > meaning that sub-directories would be added transitively.
> >
> > This approach doesn't require knowledge of the whole dependency graph -
> just direct dependencies are needed to be known.
> > Now targets can be modular and isolated, which is what modern CMake is
> all about.
> >
> >
> > Unfortunately this approach has major flaw - it doesn't allow sharing
> libraries between targets.
> > It's a trap giving false illusion, that knowledge of whole dependency
> graph is not required.
> >
> > Consider adding repo project in //repo/CMakeLists.txt.
> >
> > cmake_minimum_required(VERSION 3.11)
> >
> > project(repo)
> > add_subdirectory(tool)
> > add_subdirectory(app)
> >
> > This will immediately fail,
> > because directory util will be added twice - first time by project tool
> and second time by project app.
> > Essentially in this approach projects cannot share libraries,
> > because it is required to  control globally how sub-directories are
> added to make sure that they are added exactly once.
> >
> > Considering that both solutions feels like complete failures,
> > what is recommended way of implementing CMake build system in described
> repository?
> >
> > Am I missing something or CMake legitimately lacks mechanisms for
> handling this case properly?
> > Honestly it's hard to believe that CMake would lack support for this
> case,
> > considering this is such a widely used and flexible build system.
> >
> > I would really appreciate help with solving this issue. :-)
> > Thank you, Mateusz Zych
> > --
> >
> > 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:
> > https://cmake.org/mailman/listinfo/cmake
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://cmake.org/pipermail/cmake/attachments/20180813/aefa0e8c/attachment-0001.html>


More information about the CMake mailing list