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

Michael Ellery mellery451 at gmail.com
Mon Aug 13 12:37:09 EDT 2018


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 --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: Message signed with OpenPGP
URL: <https://cmake.org/pipermail/cmake/attachments/20180813/2e9e2703/attachment.sig>


More information about the CMake mailing list