File: TestProject.cmake

package info (click to toggle)
mongo-c-driver 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 47,088 kB
  • sloc: ansic: 193,670; python: 7,780; cpp: 1,493; sh: 659; makefile: 78
file content (266 lines) | stat: -rw-r--r-- 10,600 bytes parent folder | download
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#[[
Add a CMake test that configures, builds, and tests a CMake project.

    add_test_cmake_project(
        <name> <path>
        [INSTALL_PARENT]
        [BUILD_DIR <dir>]
        [GENERATOR <gen>]
        [CONFIG <config>]
        [PASSTHRU_VARS ...]
        [PASSTHRU_VARS_REGEX <regex>]
        [CONFIGURE_ARGS ...]
        [SETTINGS ...]
    )

The generated test will run CMake configure on the project at `<path>` (which
is resolved relative to the caller's source directory).

If INSTALL_PARENT is specified, then the host CMake project will be installed to a
temporary prefix, and that prefix will be passed along with CMAKE_PREFIX_PATH
when the test project is configured.

PASSTHRU_VARS is a list of CMake variables visible in the current scope that should
be made visible to the subproject with the same name and value. PASSTHRU_VARS_REGEX
takes a single regular expression. Any variables currently defined which match
the regex will be passed through as-if they were specified with PASSTHRU_VARS.

SETTINGS is list of a `[name]=[value]` strings for additional `-D` arguments to
pass to the sub-project. List arguments *are* supported.

If CONFIG is unspecified, then the generated test will configure+build the project
according to the configuration of CTest (passed with the `-C` argument).

The default for GENERATOR is to use the same generator as the host project.

The default BUILD_DIR will be a temporary directory that will be automatically
deleted at the start of each test to ensure a fresh configure+build cycle.

Additional Variables
####################

In addition to the variables specified explicitly in the call, all variables
with the suffix `_PATH` or `_DIR` will be passed to the sub-project with
`HOST_PROJECT_` prepended. For example, CMAKE_SOURCE_DIR will be passed to
the sub-project as HOST_PROJECT_CMAKE_SOURCE_DIR
]]
function(add_test_cmake_project name path)
    # Logging context
    list(APPEND CMAKE_MESSAGE_CONTEXT "${CMAKE_CURRENT_FUNCTION}")

    # Parse command arguments:
    set(options INSTALL_PARENT)
    set(args BUILD_DIR GENERATOR CONFIG PASSTHRU_VARS_REGEX)
    set(list_args SETTINGS PASSTHRU_VARS CONFIGURE_ARGS)
    cmake_parse_arguments(PARSE_ARGV 2 _tp_arg "${options}" "${args}" "${list_args}")
    foreach(unknown IN LISTS _tp_arg_UNPARSED_ARGUMENTS)
        message(SEND_ERROR "Unknown argument: “${unknown}”")
    endforeach()
    if(_tp_arg_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "Bad arguments (see above)")
    endif()

    # Default values:
    if(NOT DEFINED _tp_arg_BUILD_DIR)
        set(dirname "${name}")
        if(WIN32)
            # Escape specials
            string(REPLACE "%" "%25" dirname "${dirname}")
            string(REPLACE ":" "%3A" dirname "${dirname}")
            string(REPLACE "?" "%3F" dirname "${dirname}")
            string(REPLACE "*" "%2A" dirname "${dirname}")
            string(REPLACE "\"" "%22" dirname "${dirname}")
            string(REPLACE "\\" "%5C" dirname "${dirname}")
            string(REPLACE "<" "%3C" dirname "${dirname}")
            string(REPLACE ">" "%3E" dirname "${dirname}")
            string(REPLACE "|" "%7C" dirname "${dirname}")
        endif()
        set(_tp_arg_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/TestProject/${dirname}")
    endif()
    if(NOT DEFINED _tp_arg_GENERATOR)
        set(_tp_arg_GENERATOR "${CMAKE_GENERATOR}")
    endif()
    # Normalize paths
    if(NOT IS_ABSOLUTE "${_tp_arg_BUILD_DIR}")
        set(_tp_arg_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${_tp_arg_BUILD_DIR}")
    endif()
    get_filename_component(path "${path}" ABSOLUTE)
    message(VERBOSE "Add test project [${path}]")

    # Arguments that will be given during the CMake configure step:
    string(REPLACE ";" $<SEMICOLON> configure_args "${_tp_arg_CONFIGURE_ARGS}")

    # Build the argument lists that will be passed-through to project configuration -D flags:
    set(settings_passthru)

    # Pass through all _DIR and _PATH variables with a HOST_PROJECT_ prefix:
    get_directory_property(fwd_path_vars VARIABLES)
    list(FILTER fwd_path_vars INCLUDE REGEX "_DIR$|_PATH$")
    list(FILTER fwd_path_vars EXCLUDE REGEX "^_")
    list(SORT   fwd_path_vars CASE INSENSITIVE)
    set(dir_passthrough)
    foreach(var IN LISTS fwd_path_vars)
        string(REPLACE ";" $<SEMICOLON> value "${${var}}")
        list(APPEND settings_passthru "HOST_PROJECT_${var}=${value}")
    endforeach()

    # Pass through other variables without a prefix:
    set(passthru_vars "${_tp_arg_PASSTHRU_VARS}")
    # Some platform variables should always go through:
    list(APPEND passthru_vars
        CMAKE_C_COMPILER
        CMAKE_CXX_COMPILER
    )
    if(DEFINED _tp_arg_PASSTHRU_VARS_REGEX)
        # Pass through variables matching a certain pattern:
        get_directory_property(fwd_vars VARIABLES)
        list(FILTER fwd_vars INCLUDE REGEX "${_tp_arg_PASSTHRU_VARS_REGEX}")
        list(APPEND passthru_vars "${fwd_vars}")
    endif()

    # Pass through all variables that we've marked to be forwarded
    foreach(var IN LISTS passthru_vars)
        string(REPLACE ";" $<SEMICOLON> value "${${var}}")
        list(APPEND settings_passthru "${var}=${value}")
    endforeach()

    # Settings set with SETTINGS
    list(TRANSFORM _tp_arg_SETTINGS REPLACE ";" $<SEMICOLON> OUTPUT_VARIABLE settings_escaped)
    list(APPEND settings_passthru ${settings_escaped})

    # Add a prefix to each variable to mark it as a pass-thru variable:
    list(TRANSFORM settings_passthru PREPEND "-D;TEST_PROJECT_SETTINGS/")

    # Generate the test case:
    add_test(
        NAME "${name}"
        COMMAND ${CMAKE_COMMAND}
            --log-context
            -D "TEST_PROJECT_NAME=${name}"
            -D "TEST_PROJECT_PARENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
            -D "TEST_PROJECT_SOURCE_DIR=${path}"
            -D "TEST_PROJECT_BINARY_DIR=${_tp_arg_BUILD_DIR}"
            -D "TEST_PROJECT_GENERATOR=${_tp_arg_GENERATOR}"
            -D TEST_PROJECT_INSTALL_PARENT=${_tp_arg_INSTALL_PARENT}
            -D "TEST_PROJECT_CONFIGURE_ARGS=${_tp_arg_CONFIGURE_ARGS}"
            -D "TEST_PROJECT_CONFIG=$<CONFIG>"
            ${settings_passthru}
            -D __test_project_run=1
            -P "${CMAKE_CURRENT_FUNCTION_LIST_FILE}"
    )
endfunction()

# This function implements the actual test.
function(__do_test_project)
    list(APPEND CMAKE_MESSAGE_CONTEXT "TestProject Execution")
    cmake_path(ABSOLUTE_PATH TEST_PROJECT_SOURCE_DIR NORMALIZE OUTPUT_VARIABLE src_dir)
    cmake_path(ABSOLUTE_PATH TEST_PROJECT_BINARY_DIR NORMALIZE OUTPUT_VARIABLE bin_dir)

    string(MD5 test_name_hash "${TEST_PROJECT_NAME}")
    set(tmp_install_prefix "${TEST_PROJECT_PARENT_BINARY_DIR}/TestProject-install/${test_name_hash}")
    file(REMOVE_RECURSE "${tmp_install_prefix}")
    list(APPEND TEST_PROJECT_SETTINGS/CMAKE_INSTALL_PREFIX "${tmp_install_prefix}")
    list(APPEND TEST_PROJECT_SETTINGS/CMAKE_PREFIX_PATH "${tmp_install_prefix}")

    if(TEST_PROJECT_INSTALL_PARENT)
        cmake_path(ABSOLUTE_PATH tmp_install_prefix NORMALIZE)
        message(STATUS "Installing parent project into [${tmp_install_prefix}]")
        execute_process(
            COMMAND
                # Suppress DESTDIR
                ${CMAKE_COMMAND} -E env --unset=DESTDIR
                # Do the install:
                ${CMAKE_COMMAND}
                    --install "${TEST_PROJECT_PARENT_BINARY_DIR}"
                    --prefix "${tmp_install_prefix}"
                    --config "${TEST_PROJECT_CONFIG}"
            COMMAND_ERROR_IS_FATAL LAST
        )
    endif()
    message(STATUS "Project source dir: [${src_dir}]")
    message(STATUS "Project build dir: [${bin_dir}]")
    message(STATUS "Deleting build directory …")
    file(REMOVE_RECURSE "${bin_dir}")
    file(MAKE_DIRECTORY "${bin_dir}")

    # Grab settings passed-through from the parent project:
    get_directory_property(vars VARIABLES)
    set(fwd_settings)
    list(FILTER vars INCLUDE REGEX "^TEST_PROJECT_SETTINGS/")
    if(vars)
        message(STATUS "Configuration settings:")
    endif()
    foreach(var IN LISTS vars)
        set(value "${${var}}")
        # Remove our prefix
        string(REGEX REPLACE "^TEST_PROJECT_SETTINGS/" "" varname "${var}")
        # Print the value we received for debugging purposes
        message(STATUS "  • ${varname}=${value}")
        # Escape nested lists
        string(REPLACE ";" "\\;" value "${value}")
        list(APPEND fwd_settings -D "${varname}=${value}")
    endforeach()

    message(STATUS "Configuring project [${src_dir}] …")
    set(config_log "${bin_dir}/TestProject-configure.log")
    message(STATUS "CMake configure output will be written to [${config_log}]")
    execute_process(
        COMMAND_ECHO STDERR
        WORKING_DIRECTORY "${bin_dir}"
        RESULT_VARIABLE retc
        OUTPUT_VARIABLE output
        ERROR_VARIABLE output
        ECHO_OUTPUT_VARIABLE
        ECHO_ERROR_VARIABLE
        COMMAND ${CMAKE_COMMAND}
            -S "${src_dir}"
            -B "${bin_dir}"
            -G "${TEST_PROJECT_GENERATOR}"
            -D "CMAKE_BUILD_TYPE=${TEST_PROJECT_BUILD_TYPE}"
            --no-warn-unused-cli
            --log-context
            --log-level=debug
            ${fwd_settings}
            ${TEST_PROJECT_CONFIGURE_ARGS}
    )
    file(WRITE "${config_log}" "${output}")
    if(retc)
        message(FATAL_ERROR "Configure subcommand failed [${retc}]")
    endif()
    message(STATUS "CMake configure completed")

    set(build_log "${bin_dir}/TestProject-build.log")
    message(STATUS "Build output will be written to [${build_log}]")
    message(STATUS "Building configured project [${bin_dir}] …")
    execute_process(
        COMMAND_ECHO STDERR
        WORKING_DIRECTORY "${bin_dir}"
        RESULT_VARIABLE retc
        OUTPUT_VARIABLE out
        ERROR_VARIABLE out
        ECHO_OUTPUT_VARIABLE
        ECHO_ERROR_VARIABLE
        COMMAND ${CMAKE_COMMAND}
            --build "${bin_dir}"
            --config "${TEST_PROJECT_CONFIG}"
    )
    file(WRITE "${build_log}" "${output}")
    if(retc)
        message(FATAL_ERROR "Project build failed [${retc}]")
    endif()
    message(STATUS "Project build completed")

    execute_process(
        COMMAND ${CMAKE_CTEST_COMMAND}
            -T Start -T Test
            -C "${TEST_PROJECT_CONFIG}"
            --output-on-failure
        WORKING_DIRECTORY "${bin_dir}"
        COMMAND_ERROR_IS_FATAL LAST
    )
endfunction()

if(__test_project_run)
    cmake_minimum_required(VERSION 3.20)  # cmake_path/execute_process
    __do_test_project()
endif()