0

I want to collect all the source or header files from a specified folder, also matching a curtain naming convention. I don't want to use GLOBbing, and also couldn't find any examples of an approach using only cmake.

One answer from this question suggests to use ls *.cpp into CMakeLists.txt. So I though of getting a list of sources via invoking a batch script in CMakeLists. But something is wrong. Though it seems that the output is totally correct, CMake can not find those files. The path is (visually) correct: if I manually type it into add_executable, generating will succeed.

While I still want to know how to achieve the initial intent, I am extremely confused about the reason why totally identical strings compare to false:

CMake log:

-- Manually-typed: C:/Repos/cmake-scanner/src/main.cpp
-- Recieved-batch: C:/Repos/cmake-scanner/src/main.cpp

-- Path strings not identical

CollectSources.bat

@echo off
set arg1=%1
set arg2=%2

powershell -Command "$path = '%1'.Replace('\','/'); $headers = New-Object Collections.Generic.List[string]; ls -Name $path/*.%2 | foreach-object{ $headers.Add($path + '/' + $_)}; $headers"

CMakeLists.txt

cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

project(Auto-scanner)

set(HEADERS)
set(SOURCES)

if(WIN32)

execute_process(
    COMMAND CMD /c ${CMAKE_CURRENT_SOURCE_DIR}/CollectSources.bat ${CMAKE_CURRENT_SOURCE_DIR}/include h
    OUTPUT_VARIABLE res
    )

message(STATUS "Found headers: ${res}")

execute_process(
    COMMAND CMD /c ${CMAKE_CURRENT_SOURCE_DIR}/CollectSources.bat ${CMAKE_CURRENT_SOURCE_DIR}/src cpp
    OUTPUT_VARIABLE res2
    )

message(STATUS "Found sources: ${res2}")

set(${HEADERS} ${res})

endif(WIN32)

message(STATUS "Collected headers: ${HEADERS}")


message(STATUS "Manually-typed: C:/Repos/cmake-scanner/src/main.cpp")
message(STATUS "Recieved-batch: ${res2}")

if(NOT "C:/Repos/cmake-scanner/src/main.cpp" STREQUAL "${res2}")
message(STATUS "Path strings not identical")
else()
message(STATUS "Path strings are identical")
endif()

add_executable(${PROJECT_NAME}
    ${res}
    ${res2}
    )

target_include_directories(${PROJECT_NAME}
    PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    )

and project tree:

cmake-scanner
|-include 
|  |-IPublicA.h
|  |-IPublicB.h
|  |-IPublicC.h
|  |-IPublicD.h
|-src
   |-main.cpp

https://github.com/ElDesalmado/cmake-scanner.git


UPDATE Strings' comparison by length yields different results, so I thought maybe there are some trailing characters in the output of execute_process. So I replaced all the newlines that actually might prevent cmake from finding source files. string(REGEX REPLACE "\n$" "" ...) So they compare equal, however still could not be located by cmake.

I had some luck with using OUTPUT_STRIP_TRAILING_WHITESPACE in execute_command and main.cpp has been finally located and project generated. But when there are 2 or more sources this doesn't help. I m going to try outputting sources' names in a single line and see what would occur...


I have solved the issue.

Cmake accepts lists of sources that must be formatted in a way, that sources' paths are separated with a semicolon. So the solution was to modifiy batch script to output a string line of semicolon-separated file names. Later I will update the repo and provide the batch code.

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • 1
    Please do not include solutions in the question, add an answer instead (following [answer]); you may even [accept](https://stackoverflow.com/help/accepted-answer) your own answer then... – aschipfl Aug 28 '19 at 16:00

1 Answers1

0

In order for CMake to recognize the output from the bathc script as a list of Source/Header files, it must not contain any trailing symbols like whitespaces or newlines and file paths must be separated with a semicolon:

path-to-headerA.h;path-to-headerB.h;path-to-headerC.h; (It is ok if there is a semiciolon at the end of the string line - CMake accepts that).

Working solution

powershell

@echo off
set arg1=%1
set arg2=%2

powershell -Command "$path = '%1'.Replace('\','/'); $headers = ''; get-childitem $path/*.%2 | select-object -expandProperty Name | foreach-object{ $headers += ($path + '/' + $_ + ';')}; Write-output $headers"

CollectSources.cmake

#Collect source files from a given folder

set(DIR_OF_CollectSources_CMAKE ${CMAKE_CURRENT_LIST_DIR})  
function(CollectSources path ext ret)
    message(STATUS "Collecting sources *.${ext} from ${path}")
    execute_process(
    COMMAND CMD /c ${DIR_OF_CollectSources_CMAKE}/CollectSources.bat ${path} ${ext}
    OUTPUT_VARIABLE res
    OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    message(STATUS "Sources collected:")
    foreach(src ${res})
        message(${src})
    endforeach()
    set(${ret} "${res}" PARENT_SCOPE)

endfunction()

usage in CMakeLists.txt:

include(CollectSources)

CollectSources(${CMAKE_CURRENT_SOURCE_DIR}/include h HEADERS)

Example: https://github.com/ElDesalmado/cmake-scanner.git

CMake output:

-- Collecting sources *.h from C:/Repos/cmake-scanner/include
-- Sources collected:
C:/Repos/cmake-scanner/include/IPublicA.h
C:/Repos/cmake-scanner/include/IPublicB.h
C:/Repos/cmake-scanner/include/IPublicC.h
C:/Repos/cmake-scanner/include/IPublicD.h
-- Collecting sources *.cpp from C:/Repos/cmake-scanner/src
-- Sources collected:
C:/Repos/cmake-scanner/src/lib.cpp
C:/Repos/cmake-scanner/src/main.cpp
Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28