30

I have following structure

Main (dir)
      +-- CMakeLists.txt
      +-- File.cpp
      +-- File.hpp
      +-- Dir (dir)
          +-- CMakeLists.txt
          +-- File1.cpp
          +-- File1.hpp
          +-- File2.cpp
          +-- File2.hpp

Main/CMakeLists.txt

CMAKE_MINIMUM_REQUIRED (VERSION 2.8.11)
PROJECT(Main)
FILE(GLOB SOURCE
    "*.hpp"
    "*.cpp"
)

ADD_SUBDIRECTORY(Dir)
ADD_EXECUTABLE(Main ${SOURCE})

Main/Dir/CmakeLists.txt

FILE(GLOB LOCAL_SOURCE
    "*.hpp"
    "*.cpp"
)
SET(SOURCE
    ${SOURCE}
    ${LOCAL_SOURCE}
    PARENT_SCOPE
)

It generated the following structure in Visual Studio
enter image description here

What I want:

enter image description here enter image description here

What I tried:

Main/CMakeLists.txt

CMAKE_MINIMUM_REQUIRED (VERSION 2.8.11)
PROJECT(Main)
FILE(GLOB LOCAL_SOURCE
    "*.hpp"
    "*.cpp"
)

SET(SOURCE 
    ${LOCAL_SOURCE}
)

ADD_SUBDIRECTORY(Dir)

SOURCE_GROUP(Main FILES ${LOCAL_SOURCE})
ADD_EXECUTABLE(Main ${SOURCE})

Main/Dir/CmakeLists.txt

FILE(GLOB LOCAL_SOURCE
    "*.hpp"
    "*.cpp"
)
SET(SOURCE
    ${SOURCE}
    ${LOCAL_SOURCE}
    PARENT_SCOPE
)

SOURCE_GROUP(Dir FILES ${LOCAL_SOURCE})

What I get:

enter image description here

Please help me regarding this.

  • I do not want to use single CmakeFile.txt in Main directory having filters
  • Actual structure is many layers deep nesting structure. So please suggest the solution which will work for any level sub directory
joeshmo
  • 158
  • 1
  • 13
Jai
  • 1,292
  • 4
  • 21
  • 41

4 Answers4

26

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})
Kevin
  • 16,549
  • 8
  • 60
  • 74
Florian
  • 39,996
  • 9
  • 133
  • 149
  • It working but it is filtering as : E: > Code > Demo > Main > file.cpp & E: > Code > Demo > Main > Dir > file1.cpp – Jai Jul 15 '15 at 07:12
  • @Jai If you still want to use the `file(GLOB ...)` you need to add a `RELATIVE`. So in your case `file(GLOB_RECURSE source_list RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp" "*.hpp")`. – Florian Jul 15 '15 at 07:18
  • thank you, I achieved my requirement using some manipulation in you given cmake file. – Jai Jul 15 '15 at 07:35
  • Are you aware of any plans to support this out of the box in the future? – lanoxx Jul 19 '17 at 06:16
  • 1
    @lanoxx It's part of VS2017. You can now simply do `File/Open/Folder` with the folder containing your main `CMakeLists.txt` file. – Florian Jul 19 '17 at 19:51
  • It's an accepted answer but just as @Jai said, it doesn't do what the question asks. – KulaGGin Aug 26 '22 at 16:36
21

As of CMake 3.8, the source_group command offers a TREE argument to recursively search the files paths of your sources, and structures the source groups to match your file system structure. Now, this offers a much cleaner solution:

project(Main)

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

add_executable(Main ${SOURCE_LIST})

# Create the source groups for source tree with root at CMAKE_CURRENT_SOURCE_DIR.
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST})
starball
  • 20,030
  • 7
  • 43
  • 238
Kevin
  • 16,549
  • 8
  • 60
  • 74
5

I wanted to comment on https://stackoverflow.com/users/3987854/squareskittles 's answer, but I couldn't due to the lack of "reputation" ?

Anyway,

source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST})

works like a charm, but I also needed to set this:

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
sin
  • 89
  • 1
  • 2
1

I know that using the CMAKE glob function is usually frowned upon: Why is CMAKE glob evil, but in my case I found it to better than explicitly naming each file. I figured I would include a modified version of Florian's answer using GLOB.

# This code sorts the project files as they appear in the root directory

# Generate a list of all .c & .h files in the current directory and sub directores.
file(
     GLOB_RECURSE source_list RELATIVE
     "${CMAKE_CURRENT_SOURCE_DIR}"
     *.c *.h
    )
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()  
message(STATUS "Tree reorganized")
joeshmo
  • 158
  • 1
  • 13
  • This is just the same as accepted answer by Florian and is just as broken: produces filters for all the absolute paths like: E: > Code > Demo > Main > file.cpp & E: > Code > Demo > Main > Dir > file1.cpp. – KulaGGin Aug 26 '22 at 16:47