1include(RunCMake)
2
3set(RunCMake_GENERATOR "Ninja")
4set(RunCMake_GENERATOR_IS_MULTI_CONFIG 0)
5
6# Detect ninja version so we know what tests can be supported.
7execute_process(
8  COMMAND "${RunCMake_MAKE_PROGRAM}" --version
9  OUTPUT_VARIABLE ninja_out
10  ERROR_VARIABLE ninja_out
11  RESULT_VARIABLE ninja_res
12  OUTPUT_STRIP_TRAILING_WHITESPACE
13  )
14if(ninja_res EQUAL 0 AND "x${ninja_out}" MATCHES "^x[0-9]+\\.[0-9]+")
15  set(ninja_version "${ninja_out}")
16  message(STATUS "ninja version: ${ninja_version}")
17else()
18  message(FATAL_ERROR "'ninja --version' reported:\n${ninja_out}")
19endif()
20
21# Sanitize NINJA_STATUS since we expect default behavior.
22unset(ENV{NINJA_STATUS})
23
24if(CMAKE_HOST_WIN32)
25  run_cmake(SelectCompilerWindows)
26else()
27  run_cmake(SelectCompilerUNIX)
28endif()
29
30function(run_NinjaToolMissing)
31  set(RunCMake_MAKE_PROGRAM ninja-tool-missing)
32  run_cmake(NinjaToolMissing)
33endfunction()
34run_NinjaToolMissing()
35
36function(run_NoWorkToDo)
37  run_cmake(NoWorkToDo)
38  set(RunCMake_TEST_NO_CLEAN 1)
39  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/NoWorkToDo-build)
40  set(RunCMake_TEST_OUTPUT_MERGE 1)
41  run_cmake_command(NoWorkToDo-build ${CMAKE_COMMAND} --build .)
42  run_cmake_command(NoWorkToDo-nowork ${CMAKE_COMMAND} --build . -- -d explain)
43endfunction()
44run_NoWorkToDo()
45
46function(run_VerboseBuild)
47  run_cmake(VerboseBuild)
48  set(RunCMake_TEST_NO_CLEAN 1)
49  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/VerboseBuild-build)
50  set(RunCMake_TEST_OUTPUT_MERGE 1)
51  run_cmake_command(VerboseBuild-build ${CMAKE_COMMAND} --build . -v --clean-first)
52  run_cmake_command(VerboseBuild-nowork ${CMAKE_COMMAND} --build . --verbose)
53endfunction()
54run_VerboseBuild()
55
56function(run_CMP0058 case)
57  # Use a single build tree for a few tests without cleaning.
58  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0058-${case}-build)
59  set(RunCMake_TEST_NO_CLEAN 1)
60  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
61  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
62  run_cmake(CMP0058-${case})
63  run_cmake_command(CMP0058-${case}-build ${CMAKE_COMMAND} --build .)
64endfunction()
65
66run_CMP0058(OLD-no)
67run_CMP0058(OLD-by)
68run_CMP0058(WARN-no)
69run_CMP0058(WARN-by)
70run_CMP0058(NEW-no)
71run_CMP0058(NEW-by)
72
73run_cmake_with_options(CustomCommandDepfile -DCMAKE_BUILD_TYPE=Debug)
74run_cmake(CustomCommandJobPool)
75run_cmake(JobPoolUsesTerminal)
76
77run_cmake(RspFileC)
78run_cmake(RspFileCXX)
79if(TEST_Fortran)
80  run_cmake(RspFileFortran)
81endif()
82
83function(run_CommandConcat)
84  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CommandConcat-build)
85  set(RunCMake_TEST_NO_CLEAN 1)
86  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
87  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
88  run_cmake(CommandConcat)
89  run_cmake_command(CommandConcat-build ${CMAKE_COMMAND} --build .)
90endfunction()
91run_CommandConcat()
92
93function(run_SubDir)
94  # Use a single build tree for a few tests without cleaning.
95  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/SubDir-build)
96  set(RunCMake_TEST_NO_CLEAN 1)
97  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
98  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
99  run_cmake(SubDir)
100  if(WIN32)
101    set(SubDir_all [[SubDir\all]])
102    set(SubDir_test [[SubDir\test]])
103    set(SubDir_install [[SubDir\install]])
104    set(SubDirBinary_test [[SubDirBinary\test]])
105    set(SubDirBinary_all [[SubDirBinary\all]])
106    set(SubDirBinary_install [[SubDirBinary\install]])
107  else()
108    set(SubDir_all [[SubDir/all]])
109    set(SubDir_test [[SubDir/test]])
110    set(SubDir_install [[SubDir/install]])
111    set(SubDirBinary_all [[SubDirBinary/all]])
112    set(SubDirBinary_test [[SubDirBinary/test]])
113    set(SubDirBinary_install [[SubDirBinary/install]])
114  endif()
115  run_cmake_command(SubDir-build ${CMAKE_COMMAND} --build . --target ${SubDir_all})
116  run_cmake_command(SubDir-test ${CMAKE_COMMAND} --build . --target ${SubDir_test})
117  run_cmake_command(SubDir-install ${CMAKE_COMMAND} --build . --target ${SubDir_install})
118  run_cmake_command(SubDirBinary-build ${CMAKE_COMMAND} --build . --target ${SubDirBinary_all})
119  run_cmake_command(SubDirBinary-test ${CMAKE_COMMAND} --build . --target ${SubDirBinary_test})
120  run_cmake_command(SubDirBinary-install ${CMAKE_COMMAND} --build . --target ${SubDirBinary_install})
121endfunction()
122run_SubDir()
123
124function(run_ninja dir)
125  execute_process(
126    COMMAND "${RunCMake_MAKE_PROGRAM}" ${ARGN}
127    WORKING_DIRECTORY "${dir}"
128    OUTPUT_VARIABLE ninja_stdout
129    ERROR_VARIABLE ninja_stderr
130    RESULT_VARIABLE ninja_result
131    )
132  if(NOT ninja_result EQUAL 0)
133    message(STATUS "
134============ beginning of ninja's stdout ============
135${ninja_stdout}
136=============== end of ninja's stdout ===============
137")
138    message(STATUS "
139============ beginning of ninja's stderr ============
140${ninja_stderr}
141=============== end of ninja's stderr ===============
142")
143    message(FATAL_ERROR
144      "top ninja build failed exited with status ${ninja_result}")
145  endif()
146  set(ninja_stdout "${ninja_stdout}" PARENT_SCOPE)
147endfunction(run_ninja)
148
149function (run_LooseObjectDepends)
150  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/LooseObjectDepends-build)
151  run_cmake(LooseObjectDepends)
152  run_ninja("${RunCMake_TEST_BINARY_DIR}" "CMakeFiles/top.dir/top.c${CMAKE_C_OUTPUT_EXTENSION}")
153  if (EXISTS "${RunCMake_TEST_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}dep${CMAKE_SHARED_LIBRARY_SUFFIX}")
154    message(FATAL_ERROR
155      "The `dep` library was created when requesting an object file to be "
156      "built; this should no longer be necessary.")
157  endif ()
158  if (EXISTS "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/dep.dir/dep.c${CMAKE_C_OUTPUT_EXTENSION}")
159    message(FATAL_ERROR
160      "The `dep.c` object file was created when requesting an object file to "
161      "be built; this should no longer be necessary.")
162  endif ()
163endfunction ()
164run_LooseObjectDepends()
165
166function (run_AssumedSources)
167  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/AssumedSources-build)
168  run_cmake(AssumedSources)
169  run_ninja("${RunCMake_TEST_BINARY_DIR}" "${RunCMake_TEST_BINARY_DIR}/target.c")
170  if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/target.c")
171    message(FATAL_ERROR
172      "Dependencies for an assumed source did not hook up properly for 'target.c'.")
173  endif ()
174  run_ninja("${RunCMake_TEST_BINARY_DIR}" "${RunCMake_TEST_BINARY_DIR}/target-no-depends.c")
175  if (EXISTS "${RunCMake_TEST_BINARY_DIR}/target-no-depends.c")
176    message(FATAL_ERROR
177      "Dependencies for an assumed source were magically hooked up for 'target-no-depends.c'.")
178  endif ()
179endfunction ()
180run_AssumedSources()
181
182function(sleep delay)
183  execute_process(
184    COMMAND ${CMAKE_COMMAND} -E sleep ${delay}
185    RESULT_VARIABLE result
186    )
187  if(NOT result EQUAL 0)
188    message(FATAL_ERROR "failed to sleep for ${delay} second.")
189  endif()
190endfunction(sleep)
191
192macro(ninja_escape_path path out)
193  string(REPLACE "\$ " "\$\$" "${out}" "${path}")
194  string(REPLACE " " "\$ " "${out}" "${${out}}")
195  string(REPLACE ":" "\$:" "${out}" "${${out}}")
196endmacro(ninja_escape_path)
197
198macro(shell_escape string out)
199  string(REPLACE "\"" "\\\"" "${out}" "${string}")
200endmacro(shell_escape)
201
202function(run_sub_cmake test ninja_output_path_prefix)
203  set(top_build_dir "${RunCMake_BINARY_DIR}/${test}-build/")
204  file(REMOVE_RECURSE "${top_build_dir}")
205  file(MAKE_DIRECTORY "${top_build_dir}")
206
207  ninja_escape_path("${ninja_output_path_prefix}"
208    escaped_ninja_output_path_prefix)
209
210  # Generate top build ninja file.
211  set(top_build_ninja "${top_build_dir}/build.ninja")
212  shell_escape("${top_build_ninja}" escaped_top_build_ninja)
213  set(build_ninja_dep "${top_build_dir}/build_ninja_dep")
214  ninja_escape_path("${build_ninja_dep}" escaped_build_ninja_dep)
215  shell_escape("${CMAKE_COMMAND}" escaped_CMAKE_COMMAND)
216  file(WRITE "${build_ninja_dep}" "fake dependency of top build.ninja file\n")
217  if(WIN32)
218    set(cmd_prefix "cmd.exe /C \"")
219    set(cmd_suffix "\"")
220  else()
221    set(cmd_prefix "")
222    set(cmd_suffix "")
223  endif()
224  set(fs_delay 3) # We assume the system as 1 sec timestamp resolution.
225  file(WRITE "${top_build_ninja}" "\
226subninja ${escaped_ninja_output_path_prefix}/build.ninja
227default ${escaped_ninja_output_path_prefix}/all
228
229# Sleep for long enough before regenerating to make sure the timestamp of
230# the top build.ninja will be strictly greater than the timestamp of the
231# sub/build.ninja file.
232rule RERUN
233  command = ${cmd_prefix}\"${escaped_CMAKE_COMMAND}\" -E sleep ${fs_delay} && \"${escaped_CMAKE_COMMAND}\" -E touch \"${escaped_top_build_ninja}\"${cmd_suffix}
234  description = Testing regeneration
235  generator = 1
236
237build build.ninja: RERUN ${escaped_build_ninja_dep} || ${escaped_ninja_output_path_prefix}/build.ninja
238  pool = console
239")
240
241  # Run sub cmake project.
242  set(RunCMake_TEST_OPTIONS "-DCMAKE_NINJA_OUTPUT_PATH_PREFIX=${ninja_output_path_prefix}")
243  set(RunCMake_TEST_BINARY_DIR "${top_build_dir}/${ninja_output_path_prefix}")
244  run_cmake(${test})
245
246  # Check there is no 'default' statement in Ninja file generated by CMake.
247  set(sub_build_ninja "${RunCMake_TEST_BINARY_DIR}/build.ninja")
248  file(READ "${sub_build_ninja}" sub_build_ninja_file)
249  if(sub_build_ninja_file MATCHES "\ndefault [^\n][^\n]*all\n")
250    message(FATAL_ERROR
251      "unexpected 'default' statement found in '${sub_build_ninja}'")
252  endif()
253
254  # Run ninja from the top build directory.
255  run_ninja("${top_build_dir}")
256
257  # Test regeneration rules run in order.
258  set(main_cmakelists "${RunCMake_SOURCE_DIR}/CMakeLists.txt")
259  sleep(${fs_delay})
260  file(TOUCH "${main_cmakelists}")
261  file(TOUCH "${build_ninja_dep}")
262  run_ninja("${top_build_dir}")
263  file(TIMESTAMP "${main_cmakelists}" mtime_main_cmakelists UTC)
264  file(TIMESTAMP "${sub_build_ninja}" mtime_sub_build_ninja UTC)
265  file(TIMESTAMP "${top_build_ninja}" mtime_top_build_ninja UTC)
266
267  # Check sub build.ninja is regenerated.
268  if(mtime_main_cmakelists STRGREATER mtime_sub_build_ninja)
269    message(FATAL_ERROR
270      "sub build.ninja not regenerated:
271  CMakeLists.txt  = ${mtime_main_cmakelists}
272  sub/build.ninja = ${mtime_sub_build_ninja}")
273  endif()
274
275  # Check top build.ninja is regenerated after sub build.ninja.
276  if(NOT mtime_top_build_ninja STRGREATER mtime_sub_build_ninja)
277    message(FATAL_ERROR
278      "top build.ninja not regenerated strictly after sub build.ninja:
279  sub/build.ninja = ${mtime_sub_build_ninja}
280  build.ninja     = ${mtime_top_build_ninja}")
281  endif()
282
283endfunction(run_sub_cmake)
284
285if("${ninja_version}" VERSION_LESS 1.6)
286  message(WARNING "Ninja is too old; skipping rest of test.")
287  return()
288endif()
289
290foreach(ninja_output_path_prefix "sub space" "sub")
291  run_sub_cmake(Executable "${ninja_output_path_prefix}")
292  run_sub_cmake(StaticLib  "${ninja_output_path_prefix}")
293  run_sub_cmake(SharedLib "${ninja_output_path_prefix}")
294  run_sub_cmake(TwoLibs "${ninja_output_path_prefix}")
295  run_sub_cmake(SubDirPrefix "${ninja_output_path_prefix}")
296  run_sub_cmake(CustomCommandWorkingDirectory "${ninja_output_path_prefix}")
297endforeach(ninja_output_path_prefix)
298
299function (run_PreventTargetAliasesDupBuildRule)
300  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/PreventTargetAliasesDupBuildRule-build)
301  run_cmake(PreventTargetAliasesDupBuildRule)
302  run_ninja("${RunCMake_TEST_BINARY_DIR}" -w dupbuild=err)
303endfunction ()
304run_PreventTargetAliasesDupBuildRule()
305
306function (run_PreventConfigureFileDupBuildRule)
307  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/PreventConfigureFileDupBuildRule-build)
308  run_cmake(PreventConfigureFileDupBuildRule)
309  run_ninja("${RunCMake_TEST_BINARY_DIR}" -w dupbuild=err)
310endfunction()
311run_PreventConfigureFileDupBuildRule()
312
313function (run_ChangeBuildType)
314  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ChangeBuildType-build)
315  set(RunCMake_TEST_OPTIONS "-DCMAKE_BUILD_TYPE:STRING=Debug")
316  run_cmake(ChangeBuildType)
317  unset(RunCMake_TEST_OPTIONS)
318  run_ninja("${RunCMake_TEST_BINARY_DIR}" -w dupbuild=err)
319endfunction()
320run_ChangeBuildType()
321
322function(run_QtAutoMocDeps)
323  set(QtX Qt${CMake_TEST_Qt_version})
324  if(CMake_TEST_${QtX}Core_Version VERSION_GREATER_EQUAL 5.15.0)
325    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/QtAutoMocDeps-build)
326    run_cmake_with_options(QtAutoMocDeps
327      "-Dwith_qt_version=${CMake_TEST_Qt_version}"
328      "-D${QtX}_DIR=${${QtX}_DIR}"
329      "-D${QtX}Core_DIR=${${QtX}Core_DIR}"
330      "-D${QtX}Widgets_DIR=${${QtX}Widgets_DIR}"
331      "-DCMAKE_PREFIX_PATH:STRING=${CMAKE_PREFIX_PATH}"
332    )
333    # Build the project.
334    run_ninja("${RunCMake_TEST_BINARY_DIR}")
335    # Touch just the library source file, which shouldn't cause a rerun of AUTOMOC
336    # for app_with_qt target.
337    file(TOUCH "${RunCMake_SOURCE_DIR}/simple_lib.cpp")
338    # Build and assert that AUTOMOC was not run for app_with_qt.
339    run_ninja("${RunCMake_TEST_BINARY_DIR}")
340    if(ninja_stdout MATCHES "Automatic MOC for target app_with_qt")
341      message(FATAL_ERROR
342        "AUTOMOC should not have executed for 'app_with_qt' target:\nstdout:\n${ninja_stdout}")
343    endif()
344    # Assert that the subdir executables were not rebuilt.
345    if(ninja_stdout MATCHES "Automatic MOC for target sub_exe_1")
346      message(FATAL_ERROR
347        "AUTOMOC should not have executed for 'sub_exe_1' target:\nstdout:\n${ninja_stdout}")
348    endif()
349    if(ninja_stdout MATCHES "Automatic MOC for target sub_exe_2")
350      message(FATAL_ERROR
351        "AUTOMOC should not have executed for 'sub_exe_2' target:\nstdout:\n${ninja_stdout}")
352    endif()
353    # Touch a header file to make sure an automoc dependency cycle is not introduced.
354    file(TOUCH "${RunCMake_SOURCE_DIR}/MyWindow.h")
355    run_ninja("${RunCMake_TEST_BINARY_DIR}")
356    # Need to run a second time to hit the dependency cycle.
357    run_ninja("${RunCMake_TEST_BINARY_DIR}")
358  endif()
359endfunction()
360if(CMake_TEST_Qt_version)
361  run_QtAutoMocDeps()
362endif()
363