1##============================================================================
2##  Copyright (c) Kitware, Inc.
3##  All rights reserved.
4##  See LICENSE.txt for details.
5##  This software is distributed WITHOUT ANY WARRANTY; without even
6##  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
7##  PURPOSE.  See the above copyright notice for more information.
8##
9##  Copyright 2014 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
10##  Copyright 2014 UT-Battelle, LLC.
11##  Copyright 2014 Los Alamos National Security.
12##
13##  Under the terms of Contract DE-NA0003525 with NTESS,
14##  the U.S. Government retains certain rights in this software.
15##
16##  Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
17##  Laboratory (LANL), the U.S. Government retains certain rights in
18##  this software.
19##============================================================================
20
21include(CMakeParseArguments)
22
23include(VTKmDeviceAdapters)
24include(VTKmCPUVectorization)
25
26#-----------------------------------------------------------------------------
27# Utility to build a kit name from the current directory.
28function(vtkm_get_kit_name kitvar)
29  # Will this always work?  It should if ${CMAKE_CURRENT_SOURCE_DIR} is
30  # built from ${VTKm_SOURCE_DIR}.
31  string(REPLACE "${VTKm_SOURCE_DIR}/" "" dir_prefix ${CMAKE_CURRENT_SOURCE_DIR})
32  string(REPLACE "/" "_" kit "${dir_prefix}")
33  set(${kitvar} "${kit}" PARENT_SCOPE)
34  # Optional second argument to get dir_prefix.
35  if (${ARGC} GREATER 1)
36    set(${ARGV1} "${dir_prefix}" PARENT_SCOPE)
37  endif (${ARGC} GREATER 1)
38endfunction(vtkm_get_kit_name)
39
40#-----------------------------------------------------------------------------
41function(vtkm_pyexpander_generated_file generated_file_name)
42  # If pyexpander is available, add targets to build and check
43  if(PYEXPANDER_FOUND AND PYTHONINTERP_FOUND)
44    add_custom_command(
45      OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${generated_file_name}.checked
46      COMMAND ${CMAKE_COMMAND}
47        -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}
48        -DPYEXPANDER_COMMAND=${PYEXPANDER_COMMAND}
49        -DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/${generated_file_name}
50        -DGENERATED_FILE=${CMAKE_CURRENT_BINARY_DIR}/${generated_file_name}
51        -P ${VTKm_CMAKE_MODULE_PATH}/VTKmCheckPyexpander.cmake
52      MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${generated_file_name}.in
53      DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${generated_file_name}
54      COMMENT "Checking validity of ${generated_file_name}"
55      )
56    add_custom_target(check_${generated_file_name} ALL
57      DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${generated_file_name}.checked
58      )
59  endif()
60endfunction(vtkm_pyexpander_generated_file)
61
62#-----------------------------------------------------------------------------
63function(vtkm_compile_as_cuda output)
64  # We can't use set_source_files_properties(<> PROPERTIES LANGUAGE "CUDA")
65  # for the following reasons:
66  #
67  # 1. As of CMake 3.10 MSBuild cuda language support has a bug where files
68  #    aren't passed to nvcc with the explicit '-x cu' flag which will cause
69  #    them to be compiled without CUDA actually enabled.
70  # 2. If the source file is used by multiple targets(libraries/executable)
71  #    they will all see the source file marked as being CUDA. This will cause
72  #    tests / examples that reuse sources with different backends to use CUDA
73  #    by mistake
74  #
75  # The result of this is that instead we will use file(GENERATE ) to construct
76  # a proxy cu file
77  set(_cuda_srcs )
78  foreach(_to_be_cuda_file ${ARGN})
79    get_filename_component(_fname_ext "${_to_be_cuda_file}" EXT)
80    if(_fname_ext STREQUAL ".cu")
81      list(APPEND _cuda_srcs "${_to_be_cuda_file}")
82    else()
83      get_filename_component(_cuda_fname "${_to_be_cuda_file}" NAME_WE)
84      get_filename_component(_not_cuda_fullpath "${_to_be_cuda_file}" ABSOLUTE)
85      list(APPEND _cuda_srcs "${CMAKE_CURRENT_BINARY_DIR}/${_cuda_fname}.cu")
86      file(GENERATE
87            OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_cuda_fname}.cu
88            CONTENT "#include \"${_not_cuda_fullpath}\"")
89    endif()
90  endforeach()
91  set(${output} ${_cuda_srcs} PARENT_SCOPE)
92endfunction()
93
94#-----------------------------------------------------------------------------
95function(vtkm_add_header_build_test name dir_prefix use_cuda)
96  set(hfiles ${ARGN})
97
98  set(ext "cxx")
99  if(use_cuda)
100    set(ext "cu")
101  endif()
102
103  set(srcs)
104  foreach (header ${hfiles})
105    get_source_file_property(cant_be_tested ${header} VTKm_CANT_BE_HEADER_TESTED)
106    if( NOT cant_be_tested )
107      get_filename_component(headername ${header} NAME_WE)
108      get_filename_component(headerextension ${header} EXT)
109      string(SUBSTRING ${headerextension} 1 -1 headerextension)
110      set(src ${CMAKE_CURRENT_BINARY_DIR}/TB_${headername}_${headerextension}.${ext})
111
112      #By using file generate we will not trigger CMake execution when
113      #a header gets touched
114      file(GENERATE
115        OUTPUT ${src}
116        CONTENT "
117//mark that we are including headers as test for completeness.
118//This is used by headers that include thrust to properly define a proper
119//device backend / system
120#define VTKM_TEST_HEADER_BUILD
121#include <${dir_prefix}/${headername}.${headerextension}>
122int ${headername}_${headerextension}_testbuild_symbol;"
123        )
124      list(APPEND srcs ${src})
125    endif()
126  endforeach()
127
128  set_source_files_properties(${hfiles}
129    PROPERTIES HEADER_FILE_ONLY TRUE
130    )
131
132  #only attempt to add a test build executable if we have any headers to
133  #test. this might not happen when everything depends on thrust.
134  list(LENGTH srcs num_srcs)
135  if (${num_srcs} EQUAL 0)
136    return()
137  endif()
138
139  if(TARGET TestBuild_${name})
140    #If the target already exists just add more sources to it
141    target_sources(TestBuild_${name} PRIVATE ${srcs})
142  else()
143    add_library(TestBuild_${name} STATIC ${srcs} ${hfiles})
144    # Send the libraries created for test builds to their own directory so as to
145    # not pollute the directory with useful libraries.
146    set_property(TARGET TestBuild_${name} PROPERTY ARCHIVE_OUTPUT_DIRECTORY ${VTKm_LIBRARY_OUTPUT_PATH}/testbuilds)
147    set_property(TARGET TestBuild_${name} PROPERTY LIBRARY_OUTPUT_DIRECTORY ${VTKm_LIBRARY_OUTPUT_PATH}/testbuilds)
148
149    target_link_libraries(TestBuild_${name} PRIVATE vtkm_compiler_flags vtkm_taotuple)
150
151    if(TARGET vtkm::tbb)
152      #make sure that we have the tbb include paths when tbb is enabled.
153      target_link_libraries(TestBuild_${name} PRIVATE vtkm::tbb)
154    endif()
155
156    if(TARGET vtkm_diy)
157      target_link_libraries(TestBuild_${name} PRIVATE vtkm_diy)
158    endif()
159
160    if(TARGET vtkm_rendering_gl_context)
161      target_link_libraries(TestBuild_${name} PRIVATE vtkm_rendering_gl_context)
162    endif()
163
164
165  endif()
166
167endfunction()
168
169#-----------------------------------------------------------------------------
170function(vtkm_generate_export_header lib_name)
171  # Get the location of this library in the directory structure
172  # export headers work on the directory structure more than the lib_name
173  vtkm_get_kit_name(kit_name dir_prefix)
174
175  # Now generate a header that holds the macros needed to easily export
176  # template classes. This
177  string(TOUPPER ${kit_name} BASE_NAME_UPPER)
178  set(EXPORT_MACRO_NAME "${BASE_NAME_UPPER}")
179
180  set(EXPORT_IS_BUILT_STATIC 0)
181  get_target_property(is_static ${lib_name} TYPE)
182  if(${is_static} STREQUAL "STATIC_LIBRARY")
183    #If we are building statically set the define symbol
184    set(EXPORT_IS_BUILT_STATIC 1)
185  endif()
186  unset(is_static)
187
188  get_target_property(EXPORT_IMPORT_CONDITION ${lib_name} DEFINE_SYMBOL)
189  if(NOT EXPORT_IMPORT_CONDITION)
190    #set EXPORT_IMPORT_CONDITION to what the DEFINE_SYMBOL would be when
191    #building shared
192    set(EXPORT_IMPORT_CONDITION ${kit_name}_EXPORTS)
193  endif()
194
195
196  configure_file(
197      ${VTKm_SOURCE_DIR}/CMake/VTKmExportHeaderTemplate.h.in
198      ${VTKm_BINARY_DIR}/include/${dir_prefix}/${kit_name}_export.h
199    @ONLY)
200
201  if(NOT VTKm_INSTALL_ONLY_LIBRARIES)
202    install(FILES ${VTKm_BINARY_DIR}/include/${dir_prefix}/${kit_name}_export.h
203      DESTINATION ${VTKm_INSTALL_INCLUDE_DIR}/${dir_prefix}
204      )
205  endif()
206
207endfunction(vtkm_generate_export_header)
208
209function(vtkm_install_headers dir_prefix)
210  if(NOT VTKm_INSTALL_ONLY_LIBRARIES)
211    set(hfiles ${ARGN})
212    install(FILES ${hfiles}
213      DESTINATION ${VTKm_INSTALL_INCLUDE_DIR}/${dir_prefix}
214      )
215  endif()
216endfunction(vtkm_install_headers)
217
218
219#-----------------------------------------------------------------------------
220function(vtkm_declare_headers)
221  #TODO: look at the testable and cuda options
222  set(options CUDA)
223  set(oneValueArgs TESTABLE)
224  set(multiValueArgs EXCLUDE_FROM_TESTING)
225  cmake_parse_arguments(VTKm_DH "${options}"
226    "${oneValueArgs}" "${multiValueArgs}"
227    ${ARGN}
228    )
229
230  #The testable keyword allows the caller to turn off the header testing,
231  #mainly used so that backends can be installed even when they can't be
232  #built on the machine.
233  #Since this is an optional property not setting it means you do want testing
234  if(NOT DEFINED VTKm_DH_TESTABLE)
235      set(VTKm_DH_TESTABLE ON)
236  endif()
237
238  set(hfiles ${VTKm_DH_UNPARSED_ARGUMENTS} ${VTKm_DH_EXCLUDE_FROM_TESTING})
239  vtkm_get_kit_name(name dir_prefix)
240
241  #only do header testing if enable testing is turned on
242  if (VTKm_ENABLE_TESTING AND VTKm_DH_TESTABLE)
243    set_source_files_properties(${VTKm_DH_EXCLUDE_FROM_TESTING}
244      PROPERTIES VTKm_CANT_BE_HEADER_TESTED TRUE
245      )
246
247    vtkm_add_header_build_test(
248      "${name}" "${dir_prefix}" "${VTKm_DH_CUDA}" ${hfiles})
249  endif()
250
251  vtkm_install_headers("${dir_prefix}" ${hfiles})
252endfunction(vtkm_declare_headers)
253
254#-----------------------------------------------------------------------------
255# Add a VTK-m library. The name of the library will match the "kit" name
256# (e.g. vtkm_rendering) unless the NAME argument is given.
257#
258# vtkm_library(
259#   [NAME <name>]
260#   SOURCES <source_list>
261#   TEMPLATE_SOURCES <.hxx >
262#   HEADERS <header list>
263#   [WRAP_FOR_CUDA <source_list>]
264#   )
265function(vtkm_library)
266  set(oneValueArgs NAME)
267  set(multiValueArgs SOURCES HEADERS TEMPLATE_SOURCES WRAP_FOR_CUDA)
268  cmake_parse_arguments(VTKm_LIB
269    "${options}" "${oneValueArgs}" "${multiValueArgs}"
270    ${ARGN}
271    )
272
273  if(NOT VTKm_LIB_NAME)
274    message(FATAL_ERROR "vtkm library must have an explicit name")
275  endif()
276  set(lib_name ${VTKm_LIB_NAME})
277
278  if(TARGET vtkm::cuda)
279    vtkm_compile_as_cuda(cu_srcs ${VTKm_LIB_WRAP_FOR_CUDA})
280    set(VTKm_LIB_WRAP_FOR_CUDA ${cu_srcs})
281  endif()
282
283
284  add_library(${lib_name}
285              ${VTKm_LIB_SOURCES}
286              ${VTKm_LIB_HEADERS}
287              ${VTKm_LIB_TEMPLATE_SOURCES}
288              ${VTKm_LIB_WRAP_FOR_CUDA}
289              )
290
291  #specify where to place the built library
292  set_property(TARGET ${lib_name} PROPERTY ARCHIVE_OUTPUT_DIRECTORY ${VTKm_LIBRARY_OUTPUT_PATH})
293  set_property(TARGET ${lib_name} PROPERTY LIBRARY_OUTPUT_DIRECTORY ${VTKm_LIBRARY_OUTPUT_PATH})
294  set_property(TARGET ${lib_name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${VTKm_EXECUTABLE_OUTPUT_PATH})
295
296  if(NOT VTKm_USE_DEFAULT_SYMBOL_VISIBILITY)
297    set_property(TARGET ${lib_name} PROPERTY CUDA_VISIBILITY_PRESET "hidden")
298    set_property(TARGET ${lib_name} PROPERTY CXX_VISIBILITY_PRESET "hidden")
299  endif()
300
301  # allow the static cuda runtime find the driver (libcuda.dyllib) at runtime.
302  if(APPLE)
303    set_property(TARGET ${lib_name} PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
304  endif()
305
306  # Setup the SOVERSION and VERSION information for this vtkm library
307  set_property(TARGET ${lib_name} PROPERTY VERSION 1)
308  set_property(TARGET ${lib_name} PROPERTY SOVERSION 1)
309
310  # Support custom library suffix names, for other projects wanting to inject
311  # their own version numbers etc.
312  if(DEFINED VTKm_CUSTOM_LIBRARY_SUFFIX)
313    set(_lib_suffix "${VTKm_CUSTOM_LIBRARY_SUFFIX}")
314  else()
315    set(_lib_suffix "-${VTKm_VERSION_MAJOR}.${VTKm_VERSION_MINOR}")
316  endif()
317  set_property(TARGET ${lib_name} PROPERTY OUTPUT_NAME ${lib_name}${_lib_suffix})
318
319  #generate the export header and install it
320  vtkm_generate_export_header(${lib_name})
321
322  #test and install the headers
323  vtkm_declare_headers(${VTKm_LIB_HEADERS}
324                       EXCLUDE_FROM_TESTING ${VTKm_LIB_TEMPLATE_SOURCES}
325                       )
326
327  #install the library itself
328  install(TARGETS ${lib_name}
329    EXPORT ${VTKm_EXPORT_NAME}
330    ARCHIVE DESTINATION ${VTKm_INSTALL_LIB_DIR}
331    LIBRARY DESTINATION ${VTKm_INSTALL_LIB_DIR}
332    RUNTIME DESTINATION ${VTKm_INSTALL_BIN_DIR}
333    )
334
335endfunction(vtkm_library)
336
337#-----------------------------------------------------------------------------
338# Declare unit tests, which should be in the same directory as a kit
339# (package, module, whatever you call it).  Usage:
340#
341# vtkm_unit_tests(
342#   NAME
343#   SOURCES <source_list>
344#   BACKEND <type>
345#   LIBRARIES <dependent_library_list>
346#   TEST_ARGS <argument_list>
347#   MPI
348#   <options>
349#   )
350#
351# [BACKEND]: mark all source files as being compiled with the proper defines
352#            to make this backend the default backend
353#            If the backend is specified as CUDA it will also imply all
354#            sources should be treated as CUDA sources
355#            The backend name will also be added to the executable name
356#            so you can test multiple backends easily
357#
358# [LIBRARIES] : extra libraries that this set of tests need to link too
359#
360# [TEST_ARGS] : arguments that should be passed on the command line to the
361#               test executable
362#
363# [MPI]       : when specified, the tests should be run in parallel if
364#               MPI is enabled.
365#
366function(vtkm_unit_tests)
367  if (NOT VTKm_ENABLE_TESTING)
368    return()
369  endif()
370
371  set(options)
372  set(global_options ${options} MPI)
373  set(oneValueArgs BACKEND NAME)
374  set(multiValueArgs SOURCES LIBRARIES TEST_ARGS)
375  cmake_parse_arguments(VTKm_UT
376    "${global_options}" "${oneValueArgs}" "${multiValueArgs}"
377    ${ARGN}
378    )
379  vtkm_parse_test_options(VTKm_UT_SOURCES "${options}" ${VTKm_UT_SOURCES})
380
381  set(test_prog )
382  set(backend ${VTKm_UT_BACKEND})
383
384  if(VTKm_UT_NAME)
385    set(test_prog "${VTKm_UT_NAME}")
386  else()
387    vtkm_get_kit_name(kit)
388    set(test_prog "UnitTests_${kit}")
389  endif()
390  if(backend)
391    set(test_prog "${test_prog}_${backend}")
392  endif()
393
394  if(VTKm_UT_MPI)
395    # for MPI tests, suffix test name and add MPI_Init/MPI_Finalize calls.
396    set(test_prog "${test_prog}_mpi")
397    set(extraArgs EXTRA_INCLUDE "vtkm/cont/testing/Testing.h"
398                  FUNCTION "vtkm::cont::testing::Environment env")
399  else()
400    set(extraArgs)
401  endif()
402
403  #the creation of the test source list needs to occur before the labeling as
404  #cuda. This is so that we get the correctly named entry points generated
405  create_test_sourcelist(test_sources ${test_prog}.cxx ${VTKm_UT_SOURCES} ${extraArgs})
406  if(backend STREQUAL "CUDA")
407    vtkm_compile_as_cuda(cu_srcs ${VTKm_UT_SOURCES})
408    set(VTKm_UT_SOURCES ${cu_srcs})
409  endif()
410
411  add_executable(${test_prog} ${test_prog}.cxx ${VTKm_UT_SOURCES})
412  set_property(TARGET ${test_prog} PROPERTY ARCHIVE_OUTPUT_DIRECTORY ${VTKm_LIBRARY_OUTPUT_PATH})
413  set_property(TARGET ${test_prog} PROPERTY LIBRARY_OUTPUT_DIRECTORY ${VTKm_LIBRARY_OUTPUT_PATH})
414  set_property(TARGET ${test_prog} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${VTKm_EXECUTABLE_OUTPUT_PATH})
415
416  target_link_libraries(${test_prog} PRIVATE vtkm_cont ${VTKm_UT_LIBRARIES})
417  if(backend)
418    target_compile_definitions(${test_prog} PRIVATE "VTKM_DEVICE_ADAPTER=VTKM_DEVICE_ADAPTER_${backend}")
419  endif()
420
421  #determine the timeout for all the tests based on the backend. CUDA tests
422  #generally require more time because of kernel generation.
423  set(timeout 180)
424  set(run_serial False)
425  if(backend STREQUAL "CUDA")
426    set(timeout 1500)
427  elseif(backend STREQUAL "OPENMP")
428    #We need to have all OpenMP tests run serially as they
429    #will uses all the system cores, and we will cause a N*N thread
430    #explosion which causes the tests to run slower than when run
431    #serially
432    set(run_serial True)
433  endif()
434
435
436  foreach (test ${VTKm_UT_SOURCES})
437    get_filename_component(tname ${test} NAME_WE)
438    if(VTKm_UT_MPI AND VTKm_ENABLE_MPI)
439      add_test(NAME ${tname}${backend}
440        COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} 3 ${MPIEXEC_PREFLAGS}
441                $<TARGET_FILE:${test_prog}> ${tname} ${VTKm_UT_TEST_ARGS}
442                ${MPIEXEC_POSTFLAGS}
443        )
444    else()
445      add_test(NAME ${tname}${backend}
446        COMMAND ${test_prog} ${tname} ${VTKm_UT_TEST_ARGS}
447        )
448    endif()
449    set_tests_properties("${tname}${backend}" PROPERTIES
450      TIMEOUT ${timeout}
451      RUN_SERIAL ${run_serial}
452    )
453  endforeach (test)
454
455endfunction(vtkm_unit_tests)
456
457# -----------------------------------------------------------------------------
458# vtkm_parse_test_options(varname options)
459#   INTERNAL: Parse options specified for individual tests.
460#
461#   Parses the arguments to separate out options specified after the test name
462#   separated by a comma e.g.
463#
464#   TestName,Option1,Option2
465#
466#   For every option in options, this will set _TestName_Option1,
467#   _TestName_Option2, etc in the parent scope.
468#
469function(vtkm_parse_test_options varname options)
470  set(names)
471  foreach(arg IN LISTS ARGN)
472    set(test_name ${arg})
473    set(test_options)
474    if(test_name AND "x${test_name}" MATCHES "^x([^,]*),(.*)$")
475      set(test_name "${CMAKE_MATCH_1}")
476      string(REPLACE "," ";" test_options "${CMAKE_MATCH_2}")
477    endif()
478    foreach(opt IN LISTS test_options)
479      list(FIND options "${opt}" index)
480      if(index EQUAL -1)
481        message(WARNING "Unknown option '${opt}' specified for test '${test_name}'")
482      else()
483        set(_${test_name}_${opt} TRUE PARENT_SCOPE)
484      endif()
485    endforeach()
486    list(APPEND names ${test_name})
487  endforeach()
488  set(${varname} ${names} PARENT_SCOPE)
489endfunction()
490