CMake: How do I change properties on subdirectory project targets?
Asked Answered
Y

2

7

I'm trying to organize the targets in my subproject (in this case poco), but I've come to find that properties can't be modified for ALIAS targets. I want the targets in my external project to be in their own folder, instead of sprawled out everywhere in the project tree (say the visual studio generator). Is there an easier way to add projects with my own properties?

So instead of:

- CMakePredefinedTargets
    - ALL_BUILD
    - INSTALL
    - ...
- MyTargets
    - SomeLibrary
    - SomeExe
- CppUnit
- Crypto
- Data
- ...

I want:

- CMakePredefinedTargets
    - ALL_BUILD
    - INSTALL
    - ...
- MyTargets
    - SomeLibrary
    - SomeExe
- Poco
    - CppUnit
    - Crypto
    - Data
    - ...

My attempt:

function(add_subdirectory_with_folder folder_name)
    function(add_library name type)
    _add_library(${ARGV})

    set_target_properties(${name}
        PROPERTIES
        FOLDER "${folder_name}"
    )
    endfunction()
    add_subdirectory(${ARGN})
endfunction()

# External Libs
add_subdirectory_with_folder("Poco" libs/poco)

Example target from the poco library:

add_library( "${LIBNAME}" ${LIB_MODE} ${SRCS} )
add_library( "${POCO_LIBNAME}" ALIAS "${LIBNAME}")
set_target_properties( "${LIBNAME}"
    PROPERTIES
    VERSION ${SHARED_LIBRARY_VERSION} SOVERSION ${SHARED_LIBRARY_VERSION}
    OUTPUT_NAME ${POCO_LIBNAME}
    DEFINE_SYMBOL JSON_EXPORTS
    )

My goal is to make it so I don't have to fork and maintain my own versions of libraries that I want to use just for quality of life tweaks. Is there a different method I can use to organize the project tree for IDEs? I know externalproject_add exists, but I do not think this has the facilities I am looking for. I will be adding other projects in the future in the form of git-submodules, but depending on if there is an easier method for this I'll explore other avenues.

EDIT:

To clarify, I'm already using a separate CMakeLists.txt for each module of my own project, plus a top level CMakeLists.txt which ties them all together, as well as collecting external libraries that my targets rely on. I want to modify the targets of external libraries without having to fork and maintain them myself just so I have nice folder structures in visual studio, xcode, or others. Linux obviously doesn't matter as much since most editing tools are folder based already.

Yon answered 13/7, 2017 at 23:3 Comment(2)
You can add a CmakeLists.txt in each directory if you want. Create one for each project and include directories from the main one. โ€“ Piracy
@skypjack, you misunderstand. In my own project I already do this, but I want to modify the properties of someone else's project without modifying the project itself. There's no need to fork Poco when all I need it for is quality of life. โ€“ Yon
P
10

I've given your example a try and here are my two variants:

  1. Using the BUILDSYSTEM_TARGETS and SUBDIRECTORIES directory properties to evaluate a list of target names in the directory that "does not include any Imported Targets or Alias Targets":

    cmake_minimum_required(VERSION 3.7)
    
    project(AliasFolderSub)
    
    set_property(GLOBAL PROPERTY USE_FOLDERS TRUE)
    
    function(get_all_targets _result _dir)
        get_property(_subdirs DIRECTORY "${_dir}" PROPERTY SUBDIRECTORIES)
        foreach(_subdir IN LISTS _subdirs)
            get_all_targets(${_result} "${_subdir}")
        endforeach()
        get_property(_sub_targets DIRECTORY "${_dir}" PROPERTY BUILDSYSTEM_TARGETS)
        set(${_result} ${${_result}} ${_sub_targets} PARENT_SCOPE)
    endfunction()
    
    function(add_subdirectory_with_folder _folder_name _folder)
        add_subdirectory(${_folder} ${ARGN})
        get_all_targets(_targets "${_folder}")
        foreach(_target IN LISTS _targets)
            set_target_properties(
                ${_target}
                PROPERTIES FOLDER "${_folder_name}"
            )
        endforeach()
    endfunction()
    
    # External Libs
    add_subdirectory_with_folder("Poco" libs/poco)
    
  2. By transforming the FOLDER target property into something that is inherited from a directory property of the same name. This can be done using define_property() to redefine the FOLDER property as INHERITED:

    With the INHERITED option the get_property() command will chain up to the next higher scope when the requested property is not set in the scope given to the command. DIRECTORY scope chains to GLOBAL. TARGET, SOURCE, and TEST chain to DIRECTORY.

    cmake_minimum_required(VERSION 2.6)
    
    project(AliasFolderSub)
    
    set_property(GLOBAL PROPERTY USE_FOLDERS TRUE)
    define_property(
        TARGET
        PROPERTY FOLDER
        INHERITED
        BRIEF_DOCS "Set the folder name."
        FULL_DOCS  "Use to organize targets in an IDE."
    )
    
    function(add_subdirectory_with_folder _folder_name _folder)
        add_subdirectory(${_folder} ${ARGN})
        set_property(DIRECTORY "${_folder}" PROPERTY FOLDER "${_folder_name}")
    endfunction()
    
    # External Libs
    add_subdirectory_with_folder("Poco" libs/poco)
    

    ๐“๐“ธ๐“ฝ๐“ฎ: Using define_property() to redefine an existing property's scope is an undocumented behavior of CMake.

References

Pleuro answered 18/7, 2017 at 20:14 Comment(4)
The first option did not work, but the second did exactly what I wanted. Eventually I'll move these into <root>/cmake modules for more control, but this serves as a good start. Thanks! โ€“ Yon
@Yon You're welcome. And which version of CMake do you use? The first option does require at least CMake version 3.7. The beauty of the second version is, that directory properties do auto-propagate to sub-directories. โ€“ Pleuro
@Yon Updated the first option to iterate over sub-directories also. โ€“ Pleuro
I'm using 3.9.0-rc5. Yea I did get the first one working using the same method as it was bothering me, but the second one is far more elegant. Even though it's undocumented, everything is working as intended, and it's playing nice with my other Third Parties. โ€“ Yon
C
0

https://github.com/andry81/tacklelib
https://github.com/andry81/tacklelib/tree/HEAD/cmake/tacklelib/Project.cmake

cmake_minimum_required(VERSION 3.14)

# enable project folders
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

## cmake builtin search paths and includes

LIST(APPEND CMAKE_MODULE_PATH "${TACKLELIB_CMAKE_ROOT}")

include(tacklelib/Project)
include(tacklelib/EnableTargetsExtension)

project(MyApp)

set(MYLIB_ROOT ...)

# somewhere at the end ...

## project folders

tkl_set_target_folder(CMAKE_CURRENT_LIST_DIR . * .       UTILITY     . util)
tkl_set_target_folder(CMAKE_CURRENT_LIST_DIR . * "tests" EXECUTABLE  . exe)
tkl_set_target_folder(CMAKE_CURRENT_LIST_DIR . * .       "SHARED_LIBRARY;STATIC_LIBRARY" . lib)

tkl_set_target_folder(MYLIB_ROOT * * .       UTILITY     . _3dparty/utility/mylib/util)
tkl_set_target_folder(MYLIB_ROOT * * "tests" EXECUTABLE  . _3dparty/utility/mylib/exe)
tkl_set_target_folder(MYLIB_ROOT * * .       "SHARED_LIBRARY;STATIC_LIBRARY" . _3dparty/utility/mylib/lib)
tkl_set_target_folder(MYLIB_ROOT * "tests" . * . _3dparty/utility/mylib/tests)

CMake command line:

cmake.exe -G "..." "-DTACKLELIB_CMAKE_ROOT=.../_externals/tacklelib/cmake" ...

Project directory structure:

...
_externals/
_out/
include/
src/
CMakeLists.txt

_out - directory with cmake cache and output

Solution layout:

_3dparty
  utility
    mylib
      lib
        mylib
CMakePredefinedTargets
  ALL_BUILD
  INSTALL
  ZERO_CHECK
exe
  myapp
util
  bundle
Confident answered 20/2, 2022 at 19:29 Comment(0)

© 2022 - 2024 โ€” McMap. All rights reserved.