1# This is a helper function and not a build rule. It is to be used by the
2# various test rules to generate the full list of object files
3# recursively produced by "add_entrypoint_object" and "add_object_library"
4# targets.
5# Usage:
6#   get_object_files_for_test(<result var>
7#                             <skipped_entrypoints_var>
8#                             <target0> [<target1> ...])
9#
10#   The list of object files is collected in <result_var>.
11#   If skipped entrypoints were found, then <skipped_entrypoints_var> is
12#   set to a true value.
13#   targetN is either an "add_entrypoint_target" target or an
14#   "add_object_library" target.
15function(get_object_files_for_test result skipped_entrypoints_list)
16  set(object_files "")
17  set(skipped_list "")
18  foreach(dep IN LISTS ARGN)
19    get_target_property(dep_type ${dep} "TARGET_TYPE")
20    if(NOT dep_type)
21      # Target for which TARGET_TYPE property is not set do not
22      # provide any object files.
23      continue()
24    endif()
25
26    if(${dep_type} STREQUAL ${OBJECT_LIBRARY_TARGET_TYPE})
27      get_target_property(dep_object_files ${dep} "OBJECT_FILES")
28      if(dep_object_files)
29        list(APPEND object_files ${dep_object_files})
30      endif()
31    elseif(${dep_type} STREQUAL ${ENTRYPOINT_OBJ_TARGET_TYPE})
32      get_target_property(is_skipped ${dep} "SKIPPED")
33      if(is_skipped)
34        list(APPEND skipped_list ${dep})
35        continue()
36      endif()
37      get_target_property(object_file_raw ${dep} "OBJECT_FILE_RAW")
38      if(object_file_raw)
39        list(APPEND object_files ${object_file_raw})
40      endif()
41    endif()
42
43    get_target_property(indirect_deps ${dep} "DEPS")
44    get_object_files_for_test(
45        indirect_objfiles indirect_skipped_list ${indirect_deps})
46    list(APPEND object_files ${indirect_objfiles})
47    if(indirect_skipped_list)
48      list(APPEND skipped_list ${indirect_skipped_list})
49    endif()
50  endforeach(dep)
51  list(REMOVE_DUPLICATES object_files)
52  set(${result} ${object_files} PARENT_SCOPE)
53  list(REMOVE_DUPLICATES skipped_list)
54  set(${skipped_entrypoints_list} ${skipped_list} PARENT_SCOPE)
55endfunction(get_object_files_for_test)
56
57# Rule to add a libc unittest.
58# Usage
59#    add_libc_unittest(
60#      <target name>
61#      SUITE <name of the suite this test belongs to>
62#      SRCS  <list of .cpp files for the test>
63#      HDRS  <list of .h files for the test>
64#      DEPENDS <list of dependencies>
65#      COMPILE_OPTIONS <list of special compile options for this target>
66#      LINK_LIBRARIES <list of linking libraries for this target>
67#    )
68function(add_libc_unittest target_name)
69  if(NOT LLVM_INCLUDE_TESTS)
70    return()
71  endif()
72
73  cmake_parse_arguments(
74    "LIBC_UNITTEST"
75    "NO_RUN_POSTBUILD;NO_LIBC_UNITTEST_TEST_MAIN" # Optional arguments
76    "SUITE;CXX_STANDARD" # Single value arguments
77    "SRCS;HDRS;DEPENDS;COMPILE_OPTIONS;LINK_LIBRARIES" # Multi-value arguments
78    ${ARGN}
79  )
80  if(NOT LIBC_UNITTEST_SRCS)
81    message(FATAL_ERROR "'add_libc_unittest' target requires a SRCS list of .cpp "
82                        "files.")
83  endif()
84  if(NOT LIBC_UNITTEST_DEPENDS)
85    message(FATAL_ERROR "'add_libc_unittest' target requires a DEPENDS list of "
86                        "'add_entrypoint_object' targets.")
87  endif()
88
89  get_fq_target_name(${target_name} fq_target_name)
90  get_fq_deps_list(fq_deps_list ${LIBC_UNITTEST_DEPENDS})
91  get_object_files_for_test(
92      link_object_files skipped_entrypoints_list ${fq_deps_list})
93  if(skipped_entrypoints_list)
94    # If a test is OS/target machine independent, it has to be skipped if the
95    # OS/target machine combination does not provide any dependent entrypoints.
96    # If a test is OS/target machine specific, then such a test will live is a
97    # OS/target machine specific directory and will be skipped at the directory
98    # level if required.
99    #
100    # There can potentially be a setup like this: A unittest is setup for a
101    # OS/target machine independent object library, which in turn depends on a
102    # machine specific object library. Such a test would be testing internals of
103    # the libc and it is assumed that they will be rare in practice. So, they
104    # can be skipped in the corresponding CMake files using platform specific
105    # logic. This pattern is followed in the loader tests for example.
106    #
107    # Another pattern that is present currently is to detect machine
108    # capabilities and add entrypoints and tests accordingly. That approach is
109    # much lower level approach and is independent of the kind of skipping that
110    # is happening here at the entrypoint level.
111
112    set(msg "Skipping unittest ${fq_target_name} as it has missing deps: "
113            "${skipped_entrypoints_list}.")
114    message(STATUS ${msg})
115    return()
116  endif()
117
118  add_executable(
119    ${fq_target_name}
120    EXCLUDE_FROM_ALL
121    ${LIBC_UNITTEST_SRCS}
122    ${LIBC_UNITTEST_HDRS}
123  )
124  target_include_directories(
125    ${fq_target_name}
126    PRIVATE
127      ${LIBC_SOURCE_DIR}
128      ${LIBC_BUILD_DIR}
129      ${LIBC_BUILD_DIR}/include
130  )
131  target_compile_options(
132    ${fq_target_name}
133    PRIVATE ${LIBC_COMPILE_OPTIONS_DEFAULT}
134  )
135  if(LIBC_UNITTEST_COMPILE_OPTIONS)
136    target_compile_options(
137      ${fq_target_name}
138      PRIVATE ${LIBC_UNITTEST_COMPILE_OPTIONS}
139    )
140  endif()
141  if(LIBC_UNITTEST_CXX_STANDARD)
142    set_target_properties(
143      ${fq_target_name}
144      PROPERTIES
145        CXX_STANDARD ${LIBC_UNITTEST_CXX_STANDARD}
146    )
147  endif()
148
149  # Test object files will depend on LINK_LIBRARIES passed down from `add_fp_unittest`
150  set(link_libraries ${link_object_files} ${LIBC_UNITTEST_LINK_LIBRARIES})
151
152  set_target_properties(${fq_target_name}
153    PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
154
155  add_dependencies(
156    ${fq_target_name}
157    ${fq_deps_list}
158  )
159
160  # LibcUnitTest and libc_test_utils should not depend on anything in LINK_LIBRARIES.
161  if(NO_LIBC_UNITTEST_TEST_MAIN)
162    list(APPEND link_libraries LibcUnitTest libc_test_utils)
163  else()
164    list(APPEND link_libraries LibcUnitTest LibcUnitTestMain libc_test_utils)
165  endif()
166
167  target_link_libraries(${fq_target_name} PRIVATE ${link_libraries})
168
169  if(NOT LIBC_UNITTEST_NO_RUN_POSTBUILD)
170    add_custom_command(
171      TARGET ${fq_target_name}
172      POST_BUILD
173      COMMAND $<TARGET_FILE:${fq_target_name}>
174    )
175  endif()
176
177  if(LIBC_UNITTEST_SUITE)
178    add_dependencies(
179      ${LIBC_UNITTEST_SUITE}
180      ${fq_target_name}
181    )
182  endif()
183endfunction(add_libc_unittest)
184
185function(add_libc_testsuite suite_name)
186  add_custom_target(${suite_name})
187  add_dependencies(check-llvmlibc ${suite_name})
188endfunction(add_libc_testsuite)
189
190function(add_libc_exhaustive_testsuite suite_name)
191  add_custom_target(${suite_name})
192  add_dependencies(exhaustive-check-libc ${suite_name})
193endfunction(add_libc_exhaustive_testsuite)
194
195function(add_libc_long_running_testsuite suite_name)
196  add_custom_target(${suite_name})
197  add_dependencies(libc-long-running-tests ${suite_name})
198endfunction(add_libc_long_running_testsuite)
199
200# Rule to add a fuzzer test.
201# Usage
202#    add_libc_fuzzer(
203#      <target name>
204#      SRCS  <list of .cpp files for the test>
205#      HDRS  <list of .h files for the test>
206#      DEPENDS <list of dependencies>
207#    )
208function(add_libc_fuzzer target_name)
209  cmake_parse_arguments(
210    "LIBC_FUZZER"
211    "" # No optional arguments
212    "" # Single value arguments
213    "SRCS;HDRS;DEPENDS" # Multi-value arguments
214    ${ARGN}
215  )
216  if(NOT LIBC_FUZZER_SRCS)
217    message(FATAL_ERROR "'add_libc_fuzzer' target requires a SRCS list of .cpp "
218                        "files.")
219  endif()
220  if(NOT LIBC_FUZZER_DEPENDS)
221    message(FATAL_ERROR "'add_libc_fuzzer' target requires a DEPENDS list of "
222                        "'add_entrypoint_object' targets.")
223  endif()
224
225  get_fq_target_name(${target_name} fq_target_name)
226  get_fq_deps_list(fq_deps_list ${LIBC_FUZZER_DEPENDS})
227  get_object_files_for_test(
228      link_object_files skipped_entrypoints_list ${fq_deps_list})
229  if(skipped_entrypoints_list)
230    set(msg "Skipping fuzzer target ${fq_target_name} as it has missing deps: "
231            "${skipped_entrypoints_list}.")
232    message(STATUS ${msg})
233    add_custom_target(${fq_target_name})
234
235    # A post build custom command is used to avoid running the command always.
236    add_custom_command(
237      TARGET ${fq_target_name}
238      POST_BUILD
239      COMMAND ${CMAKE_COMMAND} -E echo ${msg}
240    )
241    return()
242  endif()
243
244  add_executable(
245    ${fq_target_name}
246    EXCLUDE_FROM_ALL
247    ${LIBC_FUZZER_SRCS}
248    ${LIBC_FUZZER_HDRS}
249  )
250  target_include_directories(
251    ${fq_target_name}
252    PRIVATE
253      ${LIBC_SOURCE_DIR}
254      ${LIBC_BUILD_DIR}
255      ${LIBC_BUILD_DIR}/include
256  )
257
258  target_link_libraries(${fq_target_name} PRIVATE ${link_object_files})
259
260  set_target_properties(${fq_target_name}
261      PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
262
263  add_dependencies(
264    ${fq_target_name}
265    ${fq_deps_list}
266  )
267  add_dependencies(libc-fuzzer ${fq_target_name})
268endfunction(add_libc_fuzzer)
269
270# Rule to add an integration test. An integration test is like a unit test
271# but does not use the system libc. Not even the loader from the system libc
272# is linked to the final executable. The final exe is fully statically linked.
273# The libc that the final exe links to consists of only the object files of
274# the DEPENDS targets.
275#
276# Usage:
277#   add_integration_test(
278#     <target name>
279#     SUITE <the suite to which the test should belong>
280#     SRCS <src1.cpp> [src2.cpp ...]
281#     HDRS [hdr1.cpp ...]
282#     LOADER <fully qualified loader target name>
283#     DEPENDS <list of entrypoint or other object targets>
284#     ARGS <list of command line arguments to be passed to the test>
285#     ENV <list of environment variables to set before running the test>
286#   )
287#
288# The loader target should provide a property named LOADER_OBJECT which is
289# the full path to the object file produces when the loader is built.
290#
291# The DEPENDS list can be empty. If not empty, it should be a list of
292# targets added with add_entrypoint_object or add_object_library.
293function(add_integration_test test_name)
294  get_fq_target_name(${test_name} fq_target_name)
295  if(NOT (${LIBC_TARGET_OS} STREQUAL "linux"))
296    message(STATUS "Skipping ${fq_target_name} as it is not available on ${LIBC_TARGET_OS}.")
297    return()
298  endif()
299  cmake_parse_arguments(
300    "INTEGRATION_TEST"
301    "" # No optional arguments
302    "SUITE;LOADER" # Single value arguments
303    "SRCS;HDRS;DEPENDS;ARGS;ENV" # Multi-value arguments
304    ${ARGN}
305  )
306
307  if(NOT INTEGRATION_TEST_SUITE)
308    message(FATAL_ERROR "SUITE not specified for ${fq_target_name}")
309  endif()
310  if(NOT INTEGRATION_TEST_LOADER)
311    message(FATAL_ERROR "The LOADER to link to the integration test is missing.")
312  endif()
313  if(NOT INTEGRATION_TEST_SRCS)
314    message(FATAL_ERROR "The SRCS list for add_integration_test is missing.")
315  endif()
316
317  get_fq_target_name(${test_name}.libc fq_libc_target_name)
318
319  get_fq_deps_list(fq_deps_list ${INTEGRATION_TEST_DEPENDS})
320  # Add memory functions to which compilers can emit calls.
321  list(APPEND fq_deps_list
322          libc.src.string.bcmp
323          libc.src.string.bzero
324          libc.src.string.memcmp
325          libc.src.string.memcpy
326          libc.src.string.memset)
327  list(REMOVE_DUPLICATES fq_deps_list)
328  # TODO: Instead of gathering internal object files from entrypoints,
329  # collect the object files with public names of entrypoints.
330  get_object_files_for_test(
331      link_object_files skipped_entrypoints_list ${fq_deps_list})
332  if(skipped_entrypoints_list)
333    message(STATUS "Skipping ${fq_target_name} as it has skipped deps.")
334    return()
335  endif()
336
337  # Create a sysroot structure
338  set(sysroot ${CMAKE_CURRENT_BINARY_DIR}/${test_name}/sysroot)
339  file(MAKE_DIRECTORY ${sysroot})
340  file(MAKE_DIRECTORY ${sysroot}/include)
341  set(sysroot_lib ${sysroot}/lib)
342  file(MAKE_DIRECTORY ${sysroot_lib})
343  get_target_property(loader_object_file ${INTEGRATION_TEST_LOADER} LOADER_OBJECT)
344  get_target_property(crti_object_file libc.loader.linux.crti LOADER_OBJECT)
345  get_target_property(crtn_object_file libc.loader.linux.crtn LOADER_OBJECT)
346  set(dummy_archive $<TARGET_PROPERTY:libc_integration_test_dummy,ARCHIVE_OUTPUT_DIRECTORY>/lib$<TARGET_PROPERTY:libc_integration_test_dummy,ARCHIVE_OUTPUT_NAME>.a)
347  if(NOT loader_object_file)
348    message(FATAL_ERROR "Missing LOADER_OBJECT property of ${INTEGRATION_TEST_LOADER}.")
349  endif()
350  set(loader_dst ${sysroot_lib}/${LIBC_TARGET_ARCHITECTURE}-linux-gnu/crt1.o)
351  add_custom_command(
352    OUTPUT ${loader_dst} ${sysroot}/lib/crti.o ${sysroot}/lib/crtn.o ${sysroot}/lib/libm.a ${sysroot}/lib/libc++.a
353    COMMAND cmake -E copy ${loader_object_file} ${loader_dst}
354    COMMAND cmake -E copy ${crti_object_file} ${sysroot}/lib
355    COMMAND cmake -E copy ${crtn_object_file} ${sysroot}/lib
356    # We copy the dummy archive as libm.a and libc++.a as the compiler drivers expect them.
357    COMMAND cmake -E copy ${dummy_archive} ${sysroot}/lib/libm.a
358    COMMAND cmake -E copy ${dummy_archive} ${sysroot}/lib/libc++.a
359    DEPENDS ${INTEGRATION_TEST_LOADER} libc.loader.linux.crti libc.loader.linux.crtn libc_integration_test_dummy
360  )
361  add_custom_target(
362    ${fq_target_name}.__copy_loader__
363    DEPENDS ${loader_dst}
364  )
365
366  add_library(
367    ${fq_libc_target_name}
368    STATIC
369    ${link_object_files}
370  )
371  set_target_properties(${fq_libc_target_name} PROPERTIES ARCHIVE_OUTPUT_NAME c)
372  set_target_properties(${fq_libc_target_name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${sysroot_lib})
373
374  add_executable(
375    ${fq_target_name}
376    EXCLUDE_FROM_ALL
377    ${INTEGRATION_TEST_SRCS}
378    ${INTEGRATION_TEST_HDRS}
379  )
380  set_target_properties(${fq_target_name}
381      PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
382  target_include_directories(
383    ${fq_target_name}
384    PRIVATE
385      ${LIBC_SOURCE_DIR}
386      ${LIBC_BUILD_DIR}
387      ${LIBC_BUILD_DIR}/include
388  )
389  # We set a number of link options to prevent picking up system libc binaries.
390  # Also, we restrict the integration tests to fully static executables. The
391  # rtlib is set to compiler-rt to make the compiler drivers pick up the compiler
392  # runtime binaries using full paths. Otherwise, files like crtbegin.o are passed
393  # as is (and not as paths like /usr/lib/.../crtbegin.o).
394  target_link_options(${fq_target_name} PRIVATE --sysroot=${sysroot} -static -stdlib=libc++ --rtlib=compiler-rt)
395  add_dependencies(${fq_target_name}
396                   ${fq_target_name}.__copy_loader__
397                   ${fq_libc_target_name}
398                   libc.utils.IntegrationTest.test)
399
400  add_custom_command(
401    TARGET ${fq_target_name}
402    POST_BUILD
403    COMMAND ${INTEGRATION_TEST_ENV} $<TARGET_FILE:${fq_target_name}> ${INTEGRATION_TEST_ARGS}
404  )
405
406  add_dependencies(${INTEGRATION_TEST_SUITE} ${fq_target_name})
407endfunction(add_integration_test)
408