[CMake] Unit tests, but not CTest

Michael Hertling mhertling at online.de
Thu May 13 20:14:44 EDT 2010


On 05/13/2010 10:43 AM, Magnus Therning wrote:
> On Thu, May 13, 2010 at 07:46, Michael Hertling <mhertling at online.de> wrote:
>> On 05/13/2010 07:36 AM, Magnus Therning wrote:
>>> On 13/05/10 02:31, Michael Hertling wrote:
>>>> On 05/11/2010 10:51 AM, Magnus Therning wrote:
>>>>> I'm still having problems with this.
>>>>>
>>>>> I put together this:
>>>>>
>>>>>     project( test-post-build NONE )
>>>>>     cmake_minimum_required( VERSION 2.8 )
>>>>>
>>>>>     set( output ${CMAKE_CURRENT_BINARY_DIR}/foo )
>>>>>     add_custom_command( OUTPUT ${output}
>>>>>         COMMAND touch ${output}
>>>>>         COMMENT "Touching foo"
>>>>>         )
>>>>>     add_custom_target( foo.build ALL DEPENDS ${output} )
>>>>>
>>>>>     add_custom_command( TARGET ${output}
>>>>>         POST_BUILD
>>>>>         COMMAND echo "POST_BUILD ${output}"
>>>>>         )
>>>>>
>>>>>     add_custom_command( TARGET foo.build
>>>>>         POST_BUILD
>>>>>         COMMAND echo "POST_BUILD foo.build"
>>>>>         )
>>>>>
>>>>> Based on your description above I expected this behaviour:
>>>>>
>>>>>     % cmake ..
>>>>>     -- Configuring done
>>>>>     -- Generating done
>>>>>     -- Build files have been written to:
>>>>> /home/magnus/Play/test/cmake/post_build/_build
>>>>>     % make
>>>>>     Scanning dependencies of target foo.build
>>>>>     [  0%] Touching foo
>>>>>     POST_BUILD /home/magnus/Play/test/cmake/post_build/_build/foo
>>>>>     POST_BUILD foo.build
>>>>>     [100%] Built target foo.build
>>>>>     % make
>>>>>     POST_BUILD foo.build
>>>>>     [100%] Built target foo.build
>>>>>
>>>>> However, that's not the case.  I only ever see the 'POST_BUILD foo.build'
>>>>> printed. [...]
>>>>
>>>> AFAIK, this is because your "add_custom_command(TARGET ${output} ...)"
>>>> is not associated with an actual target but with just a file whereas
>>>> "add_custom_command(TARGET foo.build ...)" actually refers to a target
>>>> "foo.build" defined by ADD_CUSTOM_TARGET(). Indeed, you may issue any
>>>> ADD_CUSTOM_COMMAND(TARGET ...) for undefined targets without causing
>>>> an error, but their commands will never be executed, of course.
>>>>
>>>>> [...] So, what target can I use to get the desired behaviour of the
>>>>> POST_BUILD only being run after an actual build?
>>>>
>>>> Just use a target - but really a target - after whose rebuild you like
>>>> to have your post-build custom commands being run.
>>>>
>>>> Hope this helps.
>>>
>>> I'm sorry, but it doesn't :-(  I have two POST_BUILD commands above, one
>>> attached to the creation of a file and one to a target.  The former I thought
>>> was attached to the creation of a file (the OUTPUT of a command) the latter to
>>> a custom target (which depends on the OUTPUT of the former).  The former is
>>> *never* run, the latter is *always* run.  The explanations in this thread so
>>> far has explained why CMake behaves this way, but how do I make the creation
>>> of the file into "a target - but really a target" so I can attach the
>>> POST_BUILD command to it?
>>>
>>> Would you mind correcting my CMake script from above as an example of how it
>>> can be done?
>>>
>>> I have the following:
>>>
>>>      project( test-post-build NONE )
>>>      cmake_minimum_required( VERSION 2.8 )
>>>
>>>      set( output ${CMAKE_CURRENT_BINARY_DIR}/foo )
>>>      add_custom_command( OUTPUT ${output}
>>>          COMMAND touch ${output}
>>>          COMMENT "Touching foo"
>>>          )
>>>
>>> I now want the text "POST_BUILD" echoed to stdout iff the command to create
>>> ${output} is run.  (With the explanations I've gotten in this thread so far I
>>> don't even see how that can be done.)
>>
>> OK, I see. Perhaps, the solution for your concern is pretty easy: Try
>>
>>     add_custom_command( OUTPUT ${output}
>>         COMMAND touch ${output}
>>         COMMAND echo "POST_BUILD ${output}"
>>         COMMENT "Touching foo"
>>         )
>>
>> and forget the "add_custom_command( TARGET ${output} ... )". This just
>> means that the latter's command is directly tied to the generation of
>> ${output}, and so, you even get exactly what you expected at first:
>>
>> % cmake ..
>> -- Configuring done
>> -- Generating done
>> -- Build files have been written to: [...]
>> % make
>> Scanning dependencies of target foo.build
>> [  0%] Touching foo
>> POST_BUILD [...]/foo
>> POST_BUILD foo.build
>> [100%] Built target foo.build
>> % make
>> POST_BUILD foo.build
>> [100%] Built target foo.build
>>
>> In other words, there would be no need to attach additional commands to
>> ADD_CUSTOM_COMMAND(OUTPUT ...) via ADD_CUSTOM_COMMAND(TARGET ...), but
>> one can specify them directly within the former, and for more complex
>> operations, one can use ADD_CUSTOM_COMMAND()'s DEPENDS option to
>> trigger arbitrary targets when the OUTPUT is regenerated.
>>
>> Is this the desired behaviour?
> 
> It's not ideal.
> 
> I'm writing some CMake scripts to ease the use of OCaml with CMake.  There are
> basically two macros at this point:
> 
> • add_ocaml_library()
> • add_ocaml_executable()
> 
> Both of them can somewhat simplistically be thought of as just wrappers around
> add_custom_command().  In some cases the OCaml executable is a unit test,
> which means I'd like the executable to be run after being built. [...]

OK, as far as I understand your concern, you have two separate tasks:

(1) (Re)build the executable using ADD_CUSTOM_COMMAND() and,
(2) in some cases, run it as a unit test after its (re)build.

IMO, these two can't be adequately expressed as Make-time dependencies:
Who depends on who? Of course, (1) on (2) does not, but vice versa does
not succeed, also, since you want *(1)* to trigger (2) after (1) itself
has been rebuilt, and this is nothing you can explain to Make as usual
dependencies between two targets unless you are willing to trigger (2)
explicitly when you mean (1). Thus, you can't use ADD_CUSTOM_TARGET()
to achieve the desired workflow via pure target interdependencies, and
since ADD_CUSTOM_COMMAND(TARGET ... POST_BUILD) which would probably do
the job isn't applicable in your case it turns out that (2) - even if
optional - should be integrated in (1) manually.

> [...] Ideally I'd
> like to write macros that would be used this way:
> 
>     add_ocaml_executable( myexe ... )
>     add_ocaml_unittest( myexe )
> 
> However, if I understand you correctly that wouldn't be possible. [...]

Hardly, indeed. If you insist on using two separate macros/functions to
mark an executable as to be unit-tested you could possibly (mis)use
variables or properties for this purpose as, say, external flags:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)

PROJECT(unittest NONE)

SET(output ${CMAKE_CURRENT_BINARY_DIR}/foo)

FUNCTION(ADD_OCAML_EXECUTABLE executable)
    GET_SOURCE_FILE_PROPERTY(unittest ${executable} UNITTEST)
    IF(unittest)
        ADD_CUSTOM_COMMAND(
            OUTPUT ${executable}
            COMMAND touch ${executable}
            COMMAND echo "Testing ${executable}"
            COMMENT "Touching ${executable}"
        )
    ELSE()
        ADD_CUSTOM_COMMAND(
            OUTPUT ${executable}
            COMMAND touch ${executable}
            COMMENT "Touching ${executable}"
        )
    ENDIF()
ENDFUNCTION()

FUNCTION(ADD_OCAML_UNITTEST executable)
    SET_SOURCE_FILES_PROPERTIES(${executable} PROPERTIES UNITTEST TRUE)
ENDFUNCTION()

# No unit test:
ADD_OCAML_EXECUTABLE(executable1)

# Mark for unit test:
# ATTENTION: Order matters!
ADD_OCAML_UNITTEST(executable2)
ADD_OCAML_EXECUTABLE(executable2)

ADD_CUSTOM_TARGET(target1 ALL DEPENDS executable1)
ADD_CUSTOM_TARGET(target2 ALL DEPENDS executable2)

Of course, this is close to a macro/function with an additional flag or
a separate macro/function for executables to be unit-tested. Moreover,
you need to mark an executable for unit-testing *before* you add it.
Finally, using source files properties on executables is dubious.

> [...] Instead
> I'll have to either create a specific macro for an executable that is a unit
> test, or pass a flag to add_ocaml_executable().

The latter would be my recommendation as being most convenient.

Regards,

Michael


More information about the CMake mailing list