[CMake] Getting the source file list for an executable fails

Michael Hertling mhertling at online.de
Sun Sep 18 22:50:33 EDT 2011


On 09/17/2011 03:01 PM, Campbell Barton wrote:
> On Sat, Sep 17, 2011 at 10:36 AM, Michael Hertling <mhertling at online.de> wrote:
>> On 09/16/2011 03:24 AM, Campbell Barton wrote:
>>> On Fri, Sep 16, 2011 at 11:11 AM, Michael Hertling <mhertling at online.de> wrote:
>>>> On 09/16/2011 02:59 AM, Campbell Barton wrote:
>>>>> Hi, I would expect this would work from reading the docs but it gives
>>>>>
>>>>>       add_executable(mytarget ${SRC})
>>>>>       get_property(TESTPROP_A TARGET mytarget PROPERTY SOURCE)
>>>>>         get_target_property(TESTPROP_B mytarget SOURCE)
>>>>>       message(FATAL_ERROR "Testing ${TESTPROP_A}, ${TESTPROP_B}")
>>>>>
>>>>> The output I get is:
>>>>>
>>>>> Testing '', 'TESTPROP_B-NOTFOUND'
>>>>>
>>>>> This is odd since properties "TYPE" and "LOCATION" are found
>>>>>
>>>>> Obviously in this case ${SRC} is already available, but I'm just
>>>>> trying to figure out why it fails.
>>>>>
>>>>> tested on cmake 2.8.5
>>>>
>>>> The property you probably refer to is named SOURCES, not SOURCE. The
>>>> different results returned by GET_PROPERTY() and GET_TARGET_PROPERTY()
>>>> are documented: The former returns an empty value if the property isn't
>>>> set, and the latter returns the NOTFOUND value, both evaluating to FALSE.
>>>>
>>>> Regards,
>>>>
>>>> Michael
>>>
>>> Ack!, sorry for the dumb mistake, but now I'm faced with a different problem.
>>>
>>> Getting the sources for a library defined in another directory, and it
>>> gives me relative paths.
>>>
>>> How do you get the path a library is defined in so I can make the
>>> absolute paths? - tried LOCATION but this gives the output location.
>>> PREFIX, SUFFIX are not set.
>>
>> AFAIK, CMake doesn't provide a mean to achieve this, so you must DIY:
>>
>> # CMakeLists.txt:
>> CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
>> PROJECT(TARGETSOURCES C)
>> SET(CMAKE_VERBOSE_MAKEFILE ON)
>>
>> FUNCTION(SET_TARGET_DIRECTORIES TARGET)
>>    SET_TARGET_PROPERTIES(
>>        ${TARGET}
>>        PROPERTIES
>>        TARGET_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
>>        TARGET_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
>> ENDFUNCTION()
>>
>> FUNCTION(GET_TARGET_SOURCES TARGET RESULT)
>>    GET_PROPERTY(HASSRCDIR
>>        TARGET ${TARGET}
>>        PROPERTY TARGET_SOURCE_DIR SET)
>>    GET_PROPERTY(HASBINDIR
>>        TARGET ${TARGET}
>>        PROPERTY TARGET_BINARY_DIR SET)
>>    IF(HASSRCDIR AND HASBINDIR)
>>        GET_TARGET_PROPERTY(SRCDIR ${TARGET} TARGET_SOURCE_DIR)
>>        GET_TARGET_PROPERTY(BINDIR ${TARGET} TARGET_BINARY_DIR)
>>        GET_TARGET_PROPERTY(SRCS ${TARGET} SOURCES)
>>        UNSET(SOURCES)
>>        FOREACH(i IN LISTS SRCS)
>>            IF(IS_ABSOLUTE ${i})
>>                LIST(APPEND SOURCES ${i})
>>            ELSEIF(EXISTS ${SRCDIR}/${i})
>>                LIST(APPEND SOURCES ${SRCDIR}/${i})
>>            ELSEIF(EXISTS ${BINDIR}/${i})
>>                LIST(APPEND SOURCES ${BINDIR}/${i})
>>            ENDIF()
>>        ENDFOREACH()
>>        SET(${RESULT} ${SOURCES} PARENT_SCOPE)
>>    ENDIF()
>> ENDFUNCTION()
>>
>> ADD_SUBDIRECTORY(xyz)
>> FILE(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
>> ADD_EXECUTABLE(main main.c)
>> TARGET_LINK_LIBRARIES(main xyz)
>> GET_TARGET_SOURCES(xyz SOURCES)
>> MESSAGE("SOURCES: ${SOURCES}")
>>
>> # xyz/CMakeLists.txt:
>> FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/x.c "void x(void){}\n")
>> FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/y.c "void y(void){}\n")
>> FILE(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/y.c "void y(void){}\n")
>> FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/z.c "void z(void){}\n")
>> FILE(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/z.c "void z(void){}\n")
>> FILE(WRITE /tmp/z.c "void z(void){}\n")
>> ADD_LIBRARY(xyz SHARED x.c y.c /tmp/z.c)
>> SET_TARGET_DIRECTORIES(xyz)
>>
>> The SET_TARGET_DIRECTORIES() function records a target's source/binary
>> directories as user-defined target properties and must be invoked from
>> within the concerned target's CMakeLists.txt. The GET_TARGET_SOURCES()
>> function reconstructs the list of a target's sources with full paths
>> based on the above-noted target properties. Compare the contents of
>> the SOURCES variable with Make's output to see that the precedence of
>> absolute paths over the source directory and the latter's precedence
>> over the binary directory in ADD_EXECUTABLE/LIBRARY() is respected.
>>
>> 'hope that helps.
>>
>> Regards,
>>
>> Michael
> 
> 
> Thanks for the example, but perhaps I should have explained my end
> goal better at first.
> 
> My aim is to run source code checkers with the correct defines and
> includes for each file - cppcheck, splint, sparse are the ones I'm
> using right now.
> 
> 
> I was trying to figure it out bit by bit but seems like its just not
> supported to get source from the target.
> 
> We're already using macro's to add source code and libraries so this
> could be made build a source list too, but I was hoping this could be
> done in a less intrusive, stand-alone way (just grab source files from
> target at the end and do operations on them).
> 
> 
> 
> So far I managed to get this working in a shoddy, makefile specific way.
> 
> $ gmake --always-make --dry-run --keep-going VERBOSE=1
> 
> ...  read the compilers in from CMakeCache.txt and parse the output of
> make for lines that contain the compiler strings.
> 
> for the script see:
>   https://svn.blender.org/svnroot/bf-blender/trunk/blender/build_files/cmake/project_source_info.py
> 
> example usage with cppcheck.
>   https://svn.blender.org/svnroot/bf-blender/trunk/blender/build_files/cmake/cmake_static_check_cppcheck.py
> 
> I have this setup so I can run "make check_splint / check_cppcheck /
> check_sparse" and it goes over all the files in the project and runs
> the checker.
> 
> Until there is a way to get this info from CMake I prefer scanning the
> makefile output, but think it would be useful to be able to have give
> a configuration and a target and be able to get a list of files, their
> defines and includes.

Instead of parsing Make's output, you might intercept the compilation
commands with one of the RULE_LAUNCH_COMPILE properties, collect the
"-[IDU...]" options and invoke the analyzer tool rather than the
compiler. Look here for a rudimentary example using splint:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(SPLINT C)
SET(CMAKE_VERBOSE_MAKEFILE ON)
FILE(WRITE ${CMAKE_BINARY_DIR}/x.c "void x(void){}\n")
FILE(WRITE ${CMAKE_BINARY_DIR}/y.c "void y(void){}\n")
FILE(WRITE ${CMAKE_BINARY_DIR}/z.c "void z(void){}\n")
ADD_DEFINITIONS(-DDEFS)
INCLUDE_DIRECTORIES($ENV{HOME}/include)
SET_SOURCE_FILES_PROPERTIES(${CMAKE_BINARY_DIR}/y.c PROPERTIES
COMPILE_DEFINITIONS YDEF)
SET_SOURCE_FILES_PROPERTIES(${CMAKE_BINARY_DIR}/z.c PROPERTIES
COMPILE_FLAGS -UZDEF)
ADD_LIBRARY(xyz SHARED x.c y.c z.c)
SET_TARGET_PROPERTIES(xyz PROPERTIES
    RULE_LAUNCH_COMPILE
    "bash ${CMAKE_SOURCE_DIR}/compile.sh ${CMAKE_BINARY_DIR}/xyz.lck
<SOURCE>"
    RULE_LAUNCH_LINK
    "bash ${CMAKE_SOURCE_DIR}/link.sh ${CMAKE_BINARY_DIR}/xyz.lck
${CMAKE_BINARY_DIR}/xyz.lst <OBJECTS> --"
)
ADD_CUSTOM_TARGET(xyz_splint
    COMMAND bash ${CMAKE_SOURCE_DIR}/remove.sh ${CMAKE_BINARY_DIR}/xyz.lst
    COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/xyz.lck
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target xyz
    COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_BINARY_DIR}/xyz.lck
    COMMAND bash ${CMAKE_SOURCE_DIR}/restore.sh ${CMAKE_BINARY_DIR}/xyz.lst
)
ADD_DEPENDENCIES(xyz_splint xyz)

# compile.sh:
lockfile="$1"; shift
source=$1; shift
if [ -e "$lockfile" ]; then
    cmd="splint"
    for i in "$@"; do
        [[ "$i" == -[IDU]* ]] && cmd="$cmd $i"
    done
    cmd="$cmd $source"
    echo "Executing: $cmd"
    exec $cmd
else
    exec "$@"
fi

# link.sh:
lockfile="$1"; shift
listfile="$1"; shift
if [ ! -e "$lockfile" ]; then
    echo -n "" > "$listfile"
    while [ "$1" != "--" ]; do echo "$1" >> "$listfile"; shift; done; shift
    exec "$@"
fi

# remove.sh:
for i in $(cat "$1"); do mv "$i"{,.tmp}; done

# restore.sh:
for i in $(cat "$1"); do mv "$i"{.tmp,}; done

The basic idea is to have two build modes, controlled by the presence
of a lock file: If the lock file is absent, there's business as usual
except for the link command that additionally generates a list of its
target's object files via the link.sh script for later usage. If the
lock file is present, link.sh is a no-op, and the compile.sh script
gathers "-[IDU]" options and invokes the splint analyzer with these
options and the source file instead of the compiler. Finally, this
analyzing mode is triggered by a custom target which

- sweeps the actual target's object files out of the way, so all of
  the source files will be recompiled, i.e. analyzed, the next time
- creates the lock file
- rebuilds the actual target; due to the presence of the lock file
  the launch scripts will analyze the sources instead of compiling
- removes the lock file
- restores the object files, so everything is as it was before.

IMO, this approach is easier and more straight forward than catching
and parsing Make's output, though the launch scripts are restricted
to CMake's Makefile generators, too. Of course, the scripts need to
be elaborated for a real-world deployment; the ones presented above
are just meant as a proof of concept.

Regards,

Michael


More information about the CMake mailing list