How to Set Visual Studio Filters for Nested Sub Directory Using Cmake

How to set Visual Studio Filters for nested sub directory using cmake

There are several ready to use or adaptable solutions out there to mimic a Source Tree behavior like in Eclipse with CMake for Visual Studio (e.g. ADD_SRC_SUBFOLDER DESTINATION_SRCS from Zobra or GroupSources from Luca).

Here is my reduced version for your use case:

cmake_minimum_required(VERSION 2.8.10)

project(Main CXX)

set(
source_list
"File.cpp"
"File.hpp"
"Dir/File1.cpp"
"Dir/File1.hpp"
"Dir/File2.cpp"
"Dir/File2.hpp"
)

add_executable(Main ${source_list})

foreach(source IN LISTS source_list)
get_filename_component(source_path "${source}" PATH)
string(REPLACE "/" "\\" source_path_msvc "${source_path}")
source_group("${source_path_msvc}" FILES "${source}")
endforeach()

See the documentation of source_group() that you have to give the sub-directories with double backslashes.

For the reason why I replaced your file(GLOB ...) with a dedicated list of all source files I like to quote from CMake's file() command documentation:

We do not recommend using GLOB to collect a list of source files from
your source tree. If no CMakeLists.txt file changes when a source is
added or removed then the generated build system cannot know when to
ask CMake to regenerate.

And here is my fail-safe version (that checks for absolute paths) to be used as a function:

function(assign_source_group)
foreach(_source IN ITEMS ${ARGN})
if (IS_ABSOLUTE "${_source}")
file(RELATIVE_PATH _source_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source}")
else()
set(_source_rel "${_source}")
endif()
get_filename_component(_source_path "${_source_rel}" PATH)
string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
source_group("${_source_path_msvc}" FILES "${_source}")
endforeach()
endfunction(assign_source_group)

Which you would call in the example with

assign_source_group(${source_list})

cmake: how to create visual studio filters

See How to set Visual Studio Filters for nested sub directory using cmake

Just be aware that

  • the source_group() command only works in combination with add_library() or add_executable() commands listing the same sources (the paths must match)
  • the source_group() command does not check if the file actually exists (so it takes anything you give it and during project file generation it tries to match the given source group file names against files used in the project)

I have given your code a try by adding a corresponding add_library() target and it works as expected (CMake 3.3.2 and VS2015):

set(VD_SRC "${VisualDesigner_SOURCE_DIR}/src/visualdesigner")

file(GLOB_RECURSE SRC_UI
"${VD_SRC}/ui/*.cpp"
"${VD_SRC}/ui/*.h"
)
file(GLOB_RECURSE SRC_IMPORT
"${VD_SRC}/import/*.cpp"
"${VD_SRC}/import/*.h"
)

add_library(VisalDesigner ${SRC_UI} ${SRC_IMPORT})

source_group("ui" FILES ${SRC_UI})
source_group("import" FILES ${SRC_IMPORT})

Results in

Solution Explorer with Filters

Here is a more generalized version taken from Visual Studio as an editor for CMake friendly project:

set(_src_root_path "${VisualDesigner_SOURCE_DIR}/src/visualdesigner")
file(
GLOB_RECURSE _source_list
LIST_DIRECTORIES false
"${_src_root_path}/*.c*"
"${_src_root_path}/*.h*"
)

add_library(VisualDesigner ${_source_list})

foreach(_source IN ITEMS ${_source_list})
get_filename_component(_source_path "${_source}" PATH)
file(RELATIVE_PATH _source_path_rel "${_src_root_path}" "${_source_path}")
string(REPLACE "/" "\\" _group_path "${_source_path_rel}")
source_group("${_group_path}" FILES "${_source}")
endforeach()

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

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

  • Directory properties and subdirectories
  • "make dist" equivalent in CMake
  • How to set Visual Studio Filters for nested sub directory using cmake

Using cmake to create main and sub-projects, and be able to compile them individually

There are a few strategies which may work, some of which could be combined.

STRATEGY 1:

If you are using the Unix Makefiles generator with CMake, then simply invoking make from the build output dir that corresponds to the subdirectory you want to build should largely accomplish what you describe. Targets from other directories won't get built unless they are needed by a target for the subdirectory you are building in (or a subdirectory below that). So while you do run CMake from the root directory, you still only build just the subdirectory(s) you want.

Pros: Easy, requires no changes to your CMakeLists.txt files.

Cons: Only works with Unix Makefiles generator, requires all parts of the source tree to be processed by CMake whether you want to build them or not.

STRATEGY 2:

As mentioned by @Emil in his comment to your question, you could simply just build the specific target(s) you want from the top level of your build output directory. This assumes you know the relevant targets you want to build though, or put another way, it requires you to have a good understanding of what targets are provided by which subdirectories.

Pros: Flexible, works with any CMake generator, requires no changes to your CMakeLists.txt files.

Cons: Requires some knowledge of what each subdirectory provides, requires all parts of the source tree to be processed by CMake whether you want to build them or not.

STRATEGY 3:

In the top level CMakeLists.txt file, you could make the add_subdirectory() call for each subdirectory depend on an option variable. This would allow you to turn inclusion of each subdirectory on or off individually, giving you precise control over what gets built. You could build everything, just one subdirectory or a set of subdirectories (more flexible than your requirements, but potentially useful).

Conditionally including a subdirectory with an option variable would typically be done something like this:

option(BUILD_SUBDIR1 "Enable building subdir1" ON)
option(BUILD_SUBDIR2 "Enable building subdir2" ON)

if(BUILD_SUBDIR1)
add_subdirectory(subdir1)
endif()
if(BUILD_SUBDIR2)
add_subdirectory(subdir2)
endif()

If some subdirectories depend on others, then the logic for whether to add a subdirectory or not would need to account for that (i.e. implement dependencies between options). It shouldn't be hard to do if required.

Pros: Flexibility to build precisely whatever set of subdirectories you want in one build, CMake only has to process those subdirectories you are interested in (saves time for huge project trees and allows you skip problematic parts of the project tree) and works for any CMake generator.

Cons: You need separate build directories if you want to build different subdirectories individually. You can't simply cd to a different part of your build output directory structure and run a build from there, for example. You still have just one source tree, but you have multiple build output trees. In practice, this may not be an issue depending on what you ultimately want to be able to do (I regularly have multiple build dirs for a given source tree, but that's usually for Debug and Release builds rather than different sets of CMake options). Also requires an understanding of dependencies between subdirectories. Requires mild changes to top level CMakeLists.txt file.


Hopefully one of the above options or some combination thereof gives you some ideas for how to ultimately achieve what you are aiming for. I had a quick look at the top level CMakeLists.txt in the github project you linked to and it looks like it already makes use of options like in strategy 3, just not quite to turn on/off individual subdirectories.

How to keep source folders hierarchy on solution explorer?

Use the source_group command.

source_group(<name> [FILES <src>...] [REGULAR_EXPRESSION <regex>])

Defines a group into which sources will be placed in project files. This is intended to set up file tabs in Visual Studio. The options are:

FILES
Any source file specified explicitly will be placed in group . Relative paths are interpreted with respect to the current source directory.

REGULAR_EXPRESSION
Any source file whose name matches the regular expression will be placed in group .

Visual Studio as an editor for CMake friendly project

I see four possible approaches:

  1. Using a commercial product like Visual GDB or WinGDB you could e.g. import remote Linux projects to Visual Studio.

  2. You create a new empty C++ project at the root of your sources and use the trick described in Importing an existing source file in Visual Studio 2012 (but this seems to require a newer version of Visual Studio > 2012).

    With "Show All Files" and "Include in Project" I was able to get all sources/headers with their directory structure (tested with Visual Studio 2013/2015).

  3. You could mock all functions beside the most basic ones (like add_library() or message()) and try to get the original CMake project to generate a Visual Studio solution. E.g. you make your own VSToolchain.cmake or PreLoad.cmake with empty implementations:

    macro(add_custom_command)
    endmacro()

    macro(add_custom_target)
    endmacro()

    macro(set_property)
    endmacro()

    ...

    I admit this approach has it's limits.

  4. You're writing a special main CMakeLists.txt to collect all sources/headers into a new solution like:

    cmake_minimum_required(VERSION 2.8)

    project(MyProject C CXX)

    set(_src_root_path "${CMAKE_CURRENT_SOURCE_DIR}")
    file(
    GLOB_RECURSE _source_list
    LIST_DIRECTORIES false
    RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
    "${_src_root_path}/*.c*"
    "${_src_root_path}/*.h*"
    )

    add_library(MySources ${_source_list})

    foreach(_source IN ITEMS ${_source_list})
    get_filename_component(_source_path "${_source}" PATH)
    string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
    source_group("${_source_path_msvc}" FILES "${_source}")
    endforeach()

    Just put it somewhere (in my example the root of the sources; but you could just change _src_root_path) and generate a Visual Studio project containing your sources/headers structured by directories (see How to set Visual Studio Filters for nested sub directory using cmake).



Related Topics



Leave a reply



Submit