summaryrefslogtreecommitdiff
path: root/cmake/AddCustomXXXTimestamp.cmake
blob: c8d134e081c1f42fba40e368cd8bb425d4d8359b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
macro(_parse_arguments_with_multi_hack ORIGINAL_COMMAND_LINE)
    # cmake_parse_arguments() put all the MULTIS in a single variable; you
    # lose the ability to see for example multiple COMMANDs. To be able to
    # passthrough multiple MULTIS, we add a marker after every MULTI. This
    # allows us to reassemble the correct amount again before giving it to
    # the wrapped command with _reassemble_command_line().

    set(COMMAND_LINE "${ORIGINAL_COMMAND_LINE}")

    foreach(MULTI IN LISTS MULTIS)
        string(REPLACE "${MULTI}" "${MULTI};:::" COMMAND_LINE "${COMMAND_LINE}")
    endforeach()

    cmake_parse_arguments(PARAM "${OPTIONS}" "${SINGLES}" "${MULTIS}" ${COMMAND_LINE})
endmacro()

macro(_reassemble_command_line)
    # Reassemble the command line as we original got it.
    set(NEW_COMMAND_LINE ${PARAM_UNPARSED_ARGUMENTS})

    foreach(OPTION IN LISTS OPTIONS)
        if(PARAM_${OPTION})
            list(APPEND NEW_COMMAND_LINE "${OPTION}")
        endif()
    endforeach()

    foreach(SINGLE IN LISTS SINGLES)
        if(PARAM_${SINGLE})
            list(APPEND NEW_COMMAND_LINE "${SINGLE}" "${PARAM_${SINGLE}}")
        endif()
    endforeach()

    foreach(MULTI IN LISTS MULTIS)
        if(PARAM_${MULTI})
            # Replace our special marker with the name of the MULTI again. This
            # restores for example multiple COMMANDs again.
            string(REPLACE ":::" "${MULTI}" PARAM_${MULTI} "${PARAM_${MULTI}}")
            list(APPEND NEW_COMMAND_LINE "${PARAM_${MULTI}}")
        endif()
    endforeach()
endmacro()

# Generated files can be older than their dependencies, causing useless
# regenerations. This function replaces each file in OUTPUT with a .timestamp
# file, adds a command to touch it and move the original file in BYPRODUCTS,
# before calling add_custom_command().
#
# Note: Any add_custom_target() depending on files in original OUTPUT must use
# add_custom_target_timestamp() instead to have the correct dependencies.
#
# add_custom_command_timestamp(OUTPUT output1 [output2 ...]
#                       COMMAND command1 [ARGS] [args1...]
#                       [COMMAND command2 [ARGS] [args2...] ...]
#                       [MAIN_DEPENDENCY depend]
#                       [DEPENDS [depends...]]
#                       [BYPRODUCTS [files...]]
#                       [IMPLICIT_DEPENDS <lang1> depend1
#                                         [<lang2> depend2] ...]
#                       [WORKING_DIRECTORY dir]
#                       [COMMENT comment]
#                       [VERBATIM] [APPEND] [USES_TERMINAL])
function(add_custom_command_timestamp)
    set(OPTIONS VERBATIM APPEND USES_TERMINAL)
    set(SINGLES MAIN_DEPENDENCY WORKING_DIRECTORY COMMENT)
    set(MULTIS OUTPUT COMMAND DEPENDS BYPRODUCTS IMPLICIT_DEPENDS)

    _parse_arguments_with_multi_hack("${ARGN}")

    # Create a list of all the OUTPUTs (by removing our magic marker)
    string(REPLACE ":::;" "" OUTPUTS "${PARAM_OUTPUT}")

    # Reset the OUTPUT and BYPRODUCTS as an empty list (if needed).
    # Because they are MULTIS, we need to add our special marker here.
    set(PARAM_OUTPUT ":::")
    if(NOT PARAM_BYPRODUCTS)
        set(PARAM_BYPRODUCTS ":::")
    endif()

    foreach(OUTPUT IN LISTS OUTPUTS)
        # For every output, we add a 'cmake -E touch' entry to update the
        # timestamp on each run.
        get_filename_component(OUTPUT_FILENAME ${OUTPUT} NAME)
        string(APPEND PARAM_COMMAND ";:::;${CMAKE_COMMAND};-E;touch;${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILENAME}.timestamp")

        # We change the OUTPUT to a '.timestamp' variant, and make the real
        # output a byproduct.
        list(APPEND PARAM_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILENAME}.timestamp)
        list(APPEND PARAM_BYPRODUCTS ${OUTPUT})

        # Mark this file as being a byproduct; we use this again with
        # add_custom_target_timestamp() to know if we should point to the
        # '.timestamp' variant or not.
        set_source_files_properties(${OUTPUT} PROPERTIES BYPRODUCT ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILENAME}.timestamp)
    endforeach()

    # Reassemble and call the wrapped command
    _reassemble_command_line()
    add_custom_command(${NEW_COMMAND_LINE})
endfunction()

# Generated files can be older than their dependencies, causing useless
# regenerations. This function adds a .timestamp file for each file in DEPENDS
# replaced by add_custom_command_timestamp(), before calling add_custom_target().
#
# add_custom_target_timestamp(Name [ALL] [command1 [args1...]]
#                      [COMMAND command2 [args2...] ...]
#                      [DEPENDS depend depend depend ... ]
#                      [BYPRODUCTS [files...]]
#                      [WORKING_DIRECTORY dir]
#                      [COMMENT comment]
#                      [VERBATIM] [USES_TERMINAL]
#                      [SOURCES src1 [src2...]])
function(add_custom_target_timestamp)
    set(OPTIONS VERBATIM USES_TERMINAL)
    set(SINGLES WORKING_DIRECTORY COMMENT)
    set(MULTIS COMMAND DEPENDS BYPRODUCTS SOURCES)
    # ALL is missing, as the order is important here. It will be picked up
    # by ${PARAM_UNPARSED_ARGUMENTS} when reassembling the command line.

    _parse_arguments_with_multi_hack("${ARGN}")

    # Create a list of all the DEPENDs (by removing our magic marker)
    string(REPLACE ":::;" "" DEPENDS "${PARAM_DEPENDS}")

    # Reset the DEPEND as an empty list.
    # Because it is a MULTI, we need to add our special marker here.
    set(PARAM_DEPENDS ":::")

    foreach(DEPEND IN LISTS DEPENDS)
        # Check if the output is produced by add_custom_command_timestamp()
        get_source_file_property(BYPRODUCT ${DEPEND} BYPRODUCT)

        if(BYPRODUCT STREQUAL "NOTFOUND")
            # If it is not, just keep it as DEPEND
            list(APPEND PARAM_DEPENDS "${DEPEND}")
        else()
            # If it is, the BYPRODUCT property points to the timestamp we want to depend on
            list(APPEND PARAM_DEPENDS "${BYPRODUCT}")
        endif()
    endforeach()

    # Reassemble and call the wrapped command
    _reassemble_command_line()
    add_custom_target(${NEW_COMMAND_LINE})
endfunction()