diff options
Diffstat (limited to 'src/arrow/cpp/cmake_modules/BuildUtils.cmake')
-rw-r--r-- | src/arrow/cpp/cmake_modules/BuildUtils.cmake | 936 |
1 files changed, 936 insertions, 0 deletions
diff --git a/src/arrow/cpp/cmake_modules/BuildUtils.cmake b/src/arrow/cpp/cmake_modules/BuildUtils.cmake new file mode 100644 index 000000000..cd8290d1b --- /dev/null +++ b/src/arrow/cpp/cmake_modules/BuildUtils.cmake @@ -0,0 +1,936 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Common path suffixes to be searched by find_library or find_path. +# Windows artifacts may be found under "<root>/Library", so +# search there as well. +set(ARROW_LIBRARY_PATH_SUFFIXES + "${CMAKE_LIBRARY_ARCHITECTURE}" + "lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "lib64" + "lib32" + "lib" + "bin" + "Library" + "Library/lib" + "Library/bin") +set(ARROW_INCLUDE_PATH_SUFFIXES "include" "Library" "Library/include") + +set(ARROW_BOOST_PROCESS_COMPILE_DEFINITIONS) +if(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # boost/process/detail/windows/handle_workaround.hpp doesn't work + # without BOOST_USE_WINDOWS_H with MinGW because MinGW doesn't + # provide __kernel_entry without winternl.h. + # + # See also: + # https://github.com/boostorg/process/blob/develop/include/boost/process/detail/windows/handle_workaround.hpp + # + # You can use this like the following: + # + # target_compile_definitions(target PRIVATE + # ${ARROW_BOOST_PROCESS_COMPILE_DEFINITIONS}) + list(APPEND ARROW_BOOST_PROCESS_COMPILE_DEFINITIONS "BOOST_USE_WINDOWS_H=1") +endif() + +function(ADD_THIRDPARTY_LIB LIB_NAME) + set(options) + set(one_value_args SHARED_LIB STATIC_LIB) + set(multi_value_args DEPS INCLUDE_DIRECTORIES) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(ARG_STATIC_LIB AND ARG_SHARED_LIB) + set(AUG_LIB_NAME "${LIB_NAME}_static") + add_library(${AUG_LIB_NAME} STATIC IMPORTED) + set_target_properties(${AUG_LIB_NAME} PROPERTIES IMPORTED_LOCATION + "${ARG_STATIC_LIB}") + if(ARG_DEPS) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_LINK_LIBRARIES + "${ARG_DEPS}") + endif() + message(STATUS "Added static library dependency ${AUG_LIB_NAME}: ${ARG_STATIC_LIB}") + if(ARG_INCLUDE_DIRECTORIES) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${ARG_INCLUDE_DIRECTORIES}") + endif() + + set(AUG_LIB_NAME "${LIB_NAME}_shared") + add_library(${AUG_LIB_NAME} SHARED IMPORTED) + + if(WIN32) + # Mark the ".lib" location as part of a Windows DLL + set_target_properties(${AUG_LIB_NAME} PROPERTIES IMPORTED_IMPLIB + "${ARG_SHARED_LIB}") + else() + set_target_properties(${AUG_LIB_NAME} PROPERTIES IMPORTED_LOCATION + "${ARG_SHARED_LIB}") + endif() + if(ARG_DEPS) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_LINK_LIBRARIES + "${ARG_DEPS}") + endif() + message(STATUS "Added shared library dependency ${AUG_LIB_NAME}: ${ARG_SHARED_LIB}") + if(ARG_INCLUDE_DIRECTORIES) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${ARG_INCLUDE_DIRECTORIES}") + endif() + elseif(ARG_STATIC_LIB) + set(AUG_LIB_NAME "${LIB_NAME}_static") + add_library(${AUG_LIB_NAME} STATIC IMPORTED) + set_target_properties(${AUG_LIB_NAME} PROPERTIES IMPORTED_LOCATION + "${ARG_STATIC_LIB}") + if(ARG_DEPS) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_LINK_LIBRARIES + "${ARG_DEPS}") + endif() + message(STATUS "Added static library dependency ${AUG_LIB_NAME}: ${ARG_STATIC_LIB}") + if(ARG_INCLUDE_DIRECTORIES) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${ARG_INCLUDE_DIRECTORIES}") + endif() + elseif(ARG_SHARED_LIB) + set(AUG_LIB_NAME "${LIB_NAME}_shared") + add_library(${AUG_LIB_NAME} SHARED IMPORTED) + + if(WIN32) + # Mark the ".lib" location as part of a Windows DLL + set_target_properties(${AUG_LIB_NAME} PROPERTIES IMPORTED_IMPLIB + "${ARG_SHARED_LIB}") + else() + set_target_properties(${AUG_LIB_NAME} PROPERTIES IMPORTED_LOCATION + "${ARG_SHARED_LIB}") + endif() + message(STATUS "Added shared library dependency ${AUG_LIB_NAME}: ${ARG_SHARED_LIB}") + if(ARG_DEPS) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_LINK_LIBRARIES + "${ARG_DEPS}") + endif() + if(ARG_INCLUDE_DIRECTORIES) + set_target_properties(${AUG_LIB_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${ARG_INCLUDE_DIRECTORIES}") + endif() + else() + message(FATAL_ERROR "No static or shared library provided for ${LIB_NAME}") + endif() +endfunction() + +function(REUSE_PRECOMPILED_HEADER_LIB TARGET_NAME LIB_NAME) + if(ARROW_USE_PRECOMPILED_HEADERS) + target_precompile_headers(${TARGET_NAME} REUSE_FROM ${LIB_NAME}) + endif() +endfunction() + +# Based on MIT-licensed +# https://gist.github.com/cristianadam/ef920342939a89fae3e8a85ca9459b49 +function(create_merged_static_lib output_target) + set(options) + set(one_value_args NAME ROOT) + set(multi_value_args TO_MERGE) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + set(output_lib_path + ${BUILD_OUTPUT_ROOT_DIRECTORY}${CMAKE_STATIC_LIBRARY_PREFIX}${ARG_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX} + ) + + set(all_library_paths $<TARGET_FILE:${ARG_ROOT}>) + foreach(lib ${ARG_TO_MERGE}) + list(APPEND all_library_paths $<TARGET_FILE:${lib}>) + endforeach() + + if(APPLE) + set(BUNDLE_COMMAND "libtool" "-no_warning_for_no_symbols" "-static" "-o" + ${output_lib_path} ${all_library_paths}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|GNU|Intel)$") + set(ar_script_path ${CMAKE_BINARY_DIR}/${ARG_NAME}.ar) + + file(WRITE ${ar_script_path}.in "CREATE ${output_lib_path}\n") + file(APPEND ${ar_script_path}.in "ADDLIB $<TARGET_FILE:${ARG_ROOT}>\n") + + foreach(lib ${ARG_TO_MERGE}) + file(APPEND ${ar_script_path}.in "ADDLIB $<TARGET_FILE:${lib}>\n") + endforeach() + + file(APPEND ${ar_script_path}.in "SAVE\nEND\n") + file(GENERATE + OUTPUT ${ar_script_path} + INPUT ${ar_script_path}.in) + set(ar_tool ${CMAKE_AR}) + + if(CMAKE_INTERPROCEDURAL_OPTIMIZATION) + set(ar_tool ${CMAKE_CXX_COMPILER_AR}) + endif() + + set(BUNDLE_COMMAND ${ar_tool} -M < ${ar_script_path}) + + elseif(MSVC) + if(NOT CMAKE_LIBTOOL) + find_program(lib_tool lib HINTS "${CMAKE_CXX_COMPILER}/..") + if("${lib_tool}" STREQUAL "lib_tool-NOTFOUND") + message(FATAL_ERROR "Cannot locate libtool to bundle libraries") + endif() + else() + set(${lib_tool} ${CMAKE_LIBTOOL}) + endif() + set(BUNDLE_TOOL ${lib_tool}) + set(BUNDLE_COMMAND ${BUNDLE_TOOL} /NOLOGO /OUT:${output_lib_path} + ${all_library_paths}) + else() + message(FATAL_ERROR "Unknown bundle scenario!") + endif() + + add_custom_command(COMMAND ${BUNDLE_COMMAND} + OUTPUT ${output_lib_path} + COMMENT "Bundling ${output_lib_path}" + VERBATIM) + + message(STATUS "Creating bundled static library target ${output_target} at ${output_lib_path}" + ) + + add_custom_target(${output_target} ALL DEPENDS ${output_lib_path}) + add_dependencies(${output_target} ${ARG_ROOT} ${ARG_TO_MERGE}) + install(FILES ${output_lib_path} DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endfunction() + +# \arg OUTPUTS list to append built targets to +function(ADD_ARROW_LIB LIB_NAME) + set(options) + set(one_value_args + BUILD_SHARED + BUILD_STATIC + CMAKE_PACKAGE_NAME + PKG_CONFIG_NAME + SHARED_LINK_FLAGS + PRECOMPILED_HEADER_LIB) + set(multi_value_args + SOURCES + PRECOMPILED_HEADERS + OUTPUTS + STATIC_LINK_LIBS + SHARED_LINK_LIBS + SHARED_PRIVATE_LINK_LIBS + EXTRA_INCLUDES + PRIVATE_INCLUDES + DEPENDENCIES + SHARED_INSTALL_INTERFACE_LIBS + STATIC_INSTALL_INTERFACE_LIBS + OUTPUT_PATH) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(ARG_OUTPUTS) + set(${ARG_OUTPUTS}) + endif() + + # Allow overriding ARROW_BUILD_SHARED and ARROW_BUILD_STATIC + if(DEFINED ARG_BUILD_SHARED) + set(BUILD_SHARED ${ARG_BUILD_SHARED}) + else() + set(BUILD_SHARED ${ARROW_BUILD_SHARED}) + endif() + if(DEFINED ARG_BUILD_STATIC) + set(BUILD_STATIC ${ARG_BUILD_STATIC}) + else() + set(BUILD_STATIC ${ARROW_BUILD_STATIC}) + endif() + if(ARG_OUTPUT_PATH) + set(OUTPUT_PATH ${ARG_OUTPUT_PATH}) + else() + set(OUTPUT_PATH ${BUILD_OUTPUT_ROOT_DIRECTORY}) + endif() + + if(WIN32 OR (CMAKE_GENERATOR STREQUAL Xcode)) + # We need to compile C++ separately for each library kind (shared and static) + # because of dllexport declarations on Windows. + # The Xcode generator doesn't reliably work with Xcode as target names are not + # guessed correctly. + set(USE_OBJLIB OFF) + else() + set(USE_OBJLIB ON) + endif() + + if(USE_OBJLIB) + # Generate a single "objlib" from all C++ modules and link + # that "objlib" into each library kind, to avoid compiling twice + add_library(${LIB_NAME}_objlib OBJECT ${ARG_SOURCES}) + # Necessary to make static linking into other shared libraries work properly + set_property(TARGET ${LIB_NAME}_objlib PROPERTY POSITION_INDEPENDENT_CODE 1) + if(ARG_DEPENDENCIES) + add_dependencies(${LIB_NAME}_objlib ${ARG_DEPENDENCIES}) + endif() + if(ARG_PRECOMPILED_HEADER_LIB) + reuse_precompiled_header_lib(${LIB_NAME}_objlib ${ARG_PRECOMPILED_HEADER_LIB}) + endif() + if(ARG_PRECOMPILED_HEADERS AND ARROW_USE_PRECOMPILED_HEADERS) + target_precompile_headers(${LIB_NAME}_objlib PRIVATE ${ARG_PRECOMPILED_HEADERS}) + endif() + set(LIB_DEPS $<TARGET_OBJECTS:${LIB_NAME}_objlib>) + set(LIB_INCLUDES) + set(EXTRA_DEPS) + + if(ARG_OUTPUTS) + list(APPEND ${ARG_OUTPUTS} ${LIB_NAME}_objlib) + endif() + + if(ARG_EXTRA_INCLUDES) + target_include_directories(${LIB_NAME}_objlib SYSTEM PUBLIC ${ARG_EXTRA_INCLUDES}) + endif() + if(ARG_PRIVATE_INCLUDES) + target_include_directories(${LIB_NAME}_objlib PRIVATE ${ARG_PRIVATE_INCLUDES}) + endif() + else() + # Prepare arguments for separate compilation of static and shared libs below + # TODO: add PCH directives + set(LIB_DEPS ${ARG_SOURCES}) + set(EXTRA_DEPS ${ARG_DEPENDENCIES}) + + if(ARG_EXTRA_INCLUDES) + set(LIB_INCLUDES ${ARG_EXTRA_INCLUDES}) + endif() + endif() + + set(RUNTIME_INSTALL_DIR bin) + + if(BUILD_SHARED) + add_library(${LIB_NAME}_shared SHARED ${LIB_DEPS}) + if(EXTRA_DEPS) + add_dependencies(${LIB_NAME}_shared ${EXTRA_DEPS}) + endif() + + if(ARG_PRECOMPILED_HEADER_LIB) + reuse_precompiled_header_lib(${LIB_NAME}_shared ${ARG_PRECOMPILED_HEADER_LIB}) + endif() + + if(ARG_OUTPUTS) + list(APPEND ${ARG_OUTPUTS} ${LIB_NAME}_shared) + endif() + + if(LIB_INCLUDES) + target_include_directories(${LIB_NAME}_shared SYSTEM PUBLIC ${ARG_EXTRA_INCLUDES}) + endif() + + if(ARG_PRIVATE_INCLUDES) + target_include_directories(${LIB_NAME}_shared PRIVATE ${ARG_PRIVATE_INCLUDES}) + endif() + + # On iOS, specifying -undefined conflicts with enabling bitcode + if(APPLE + AND NOT IOS + AND NOT DEFINED ENV{EMSCRIPTEN}) + # On OS X, you can avoid linking at library load time and instead + # expecting that the symbols have been loaded separately. This happens + # with libpython* where there can be conflicts between system Python and + # the Python from a thirdparty distribution + # + # When running with the Emscripten Compiler, we need not worry about + # python, and the Emscripten Compiler does not support this option. + set(ARG_SHARED_LINK_FLAGS "-undefined dynamic_lookup ${ARG_SHARED_LINK_FLAGS}") + endif() + + set_target_properties(${LIB_NAME}_shared + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_PATH}" + RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_PATH}" + PDB_OUTPUT_DIRECTORY "${OUTPUT_PATH}" + LINK_FLAGS "${ARG_SHARED_LINK_FLAGS}" + OUTPUT_NAME ${LIB_NAME} + VERSION "${ARROW_FULL_SO_VERSION}" + SOVERSION "${ARROW_SO_VERSION}") + + target_link_libraries(${LIB_NAME}_shared + LINK_PUBLIC + "$<BUILD_INTERFACE:${ARG_SHARED_LINK_LIBS}>" + "$<INSTALL_INTERFACE:${ARG_SHARED_INSTALL_INTERFACE_LIBS}>" + LINK_PRIVATE + ${ARG_SHARED_PRIVATE_LINK_LIBS}) + + if(ARROW_RPATH_ORIGIN) + if(APPLE) + set(_lib_install_rpath "@loader_path") + else() + set(_lib_install_rpath "\$ORIGIN") + endif() + set_target_properties(${LIB_NAME}_shared PROPERTIES INSTALL_RPATH + ${_lib_install_rpath}) + endif() + + if(APPLE) + if(ARROW_INSTALL_NAME_RPATH) + set(_lib_install_name "@rpath") + else() + set(_lib_install_name "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + endif() + set_target_properties(${LIB_NAME}_shared + PROPERTIES BUILD_WITH_INSTALL_RPATH ON INSTALL_NAME_DIR + "${_lib_install_name}") + endif() + + install(TARGETS ${LIB_NAME}_shared ${INSTALL_IS_OPTIONAL} + EXPORT ${LIB_NAME}_targets + RUNTIME DESTINATION ${RUNTIME_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + endif() + + if(BUILD_STATIC) + add_library(${LIB_NAME}_static STATIC ${LIB_DEPS}) + if(EXTRA_DEPS) + add_dependencies(${LIB_NAME}_static ${EXTRA_DEPS}) + endif() + + if(ARG_PRECOMPILED_HEADER_LIB) + reuse_precompiled_header_lib(${LIB_NAME}_static ${ARG_PRECOMPILED_HEADER_LIB}) + endif() + + if(ARG_OUTPUTS) + list(APPEND ${ARG_OUTPUTS} ${LIB_NAME}_static) + endif() + + if(LIB_INCLUDES) + target_include_directories(${LIB_NAME}_static SYSTEM PUBLIC ${ARG_EXTRA_INCLUDES}) + endif() + + if(ARG_PRIVATE_INCLUDES) + target_include_directories(${LIB_NAME}_static PRIVATE ${ARG_PRIVATE_INCLUDES}) + endif() + + if(MSVC_TOOLCHAIN) + set(LIB_NAME_STATIC ${LIB_NAME}_static) + else() + set(LIB_NAME_STATIC ${LIB_NAME}) + endif() + + if(ARROW_BUILD_STATIC AND WIN32) + target_compile_definitions(${LIB_NAME}_static PUBLIC ARROW_STATIC) + endif() + + set_target_properties(${LIB_NAME}_static + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_PATH}" + OUTPUT_NAME ${LIB_NAME_STATIC}) + + if(ARG_STATIC_INSTALL_INTERFACE_LIBS) + target_link_libraries(${LIB_NAME}_static LINK_PUBLIC + "$<INSTALL_INTERFACE:${ARG_STATIC_INSTALL_INTERFACE_LIBS}>") + endif() + + if(ARG_STATIC_LINK_LIBS) + target_link_libraries(${LIB_NAME}_static LINK_PRIVATE + "$<BUILD_INTERFACE:${ARG_STATIC_LINK_LIBS}>") + endif() + + install(TARGETS ${LIB_NAME}_static ${INSTALL_IS_OPTIONAL} + EXPORT ${LIB_NAME}_targets + RUNTIME DESTINATION ${RUNTIME_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + endif() + + if(ARG_CMAKE_PACKAGE_NAME) + arrow_install_cmake_find_module("${ARG_CMAKE_PACKAGE_NAME}") + + set(TARGETS_CMAKE "${ARG_CMAKE_PACKAGE_NAME}Targets.cmake") + install(EXPORT ${LIB_NAME}_targets + FILE "${TARGETS_CMAKE}" + DESTINATION "${ARROW_CMAKE_INSTALL_DIR}") + + set(CONFIG_CMAKE "${ARG_CMAKE_PACKAGE_NAME}Config.cmake") + set(BUILT_CONFIG_CMAKE "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_CMAKE}") + configure_package_config_file("${CONFIG_CMAKE}.in" "${BUILT_CONFIG_CMAKE}" + INSTALL_DESTINATION "${ARROW_CMAKE_INSTALL_DIR}") + install(FILES "${BUILT_CONFIG_CMAKE}" DESTINATION "${ARROW_CMAKE_INSTALL_DIR}") + + set(CONFIG_VERSION_CMAKE "${ARG_CMAKE_PACKAGE_NAME}ConfigVersion.cmake") + set(BUILT_CONFIG_VERSION_CMAKE "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_VERSION_CMAKE}") + write_basic_package_version_file( + "${BUILT_CONFIG_VERSION_CMAKE}" + VERSION ${${PROJECT_NAME}_VERSION} + COMPATIBILITY AnyNewerVersion) + install(FILES "${BUILT_CONFIG_VERSION_CMAKE}" + DESTINATION "${ARROW_CMAKE_INSTALL_DIR}") + endif() + + if(ARG_PKG_CONFIG_NAME) + arrow_add_pkg_config("${ARG_PKG_CONFIG_NAME}") + endif() + + # Modify variable in calling scope + if(ARG_OUTPUTS) + set(${ARG_OUTPUTS} + ${${ARG_OUTPUTS}} + PARENT_SCOPE) + endif() +endfunction() + +# +# Benchmarking +# +# Add a new micro benchmark, with or without an executable that should be built. +# If benchmarks are enabled then they will be run along side unit tests with ctest. +# 'make benchmark' and 'make unittest' to build/run only benchmark or unittests, +# respectively. +# +# REL_BENCHMARK_NAME is the name of the benchmark app. It may be a single component +# (e.g. monotime-benchmark) or contain additional components (e.g. +# net/net_util-benchmark). Either way, the last component must be a globally +# unique name. + +# The benchmark will registered as unit test with ctest with a label +# of 'benchmark'. +# +# Arguments after the test name will be passed to set_tests_properties(). +# +# \arg PREFIX a string to append to the name of the benchmark executable. For +# example, if you have src/arrow/foo/bar-benchmark.cc, then PREFIX "foo" will +# create test executable foo-bar-benchmark +# \arg LABELS the benchmark label or labels to assign the unit tests to. By +# default, benchmarks will go in the "benchmark" group. Custom targets for the +# group names must exist +function(ADD_BENCHMARK REL_BENCHMARK_NAME) + set(options) + set(one_value_args) + set(multi_value_args + EXTRA_LINK_LIBS + STATIC_LINK_LIBS + DEPENDENCIES + PREFIX + LABELS) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(NO_BENCHMARKS) + return() + endif() + get_filename_component(BENCHMARK_NAME ${REL_BENCHMARK_NAME} NAME_WE) + + if(ARG_PREFIX) + set(BENCHMARK_NAME "${ARG_PREFIX}-${BENCHMARK_NAME}") + endif() + + # Make sure the executable name contains only hyphens, not underscores + string(REPLACE "_" "-" BENCHMARK_NAME ${BENCHMARK_NAME}) + + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${REL_BENCHMARK_NAME}.cc) + # This benchmark has a corresponding .cc file, set it up as an executable. + set(BENCHMARK_PATH "${EXECUTABLE_OUTPUT_PATH}/${BENCHMARK_NAME}") + add_executable(${BENCHMARK_NAME} "${REL_BENCHMARK_NAME}.cc") + + if(ARG_STATIC_LINK_LIBS) + # Customize link libraries + target_link_libraries(${BENCHMARK_NAME} PRIVATE ${ARG_STATIC_LINK_LIBS}) + else() + target_link_libraries(${BENCHMARK_NAME} PRIVATE ${ARROW_BENCHMARK_LINK_LIBS}) + endif() + add_dependencies(benchmark ${BENCHMARK_NAME}) + set(NO_COLOR "--color_print=false") + + if(ARG_EXTRA_LINK_LIBS) + target_link_libraries(${BENCHMARK_NAME} PRIVATE ${ARG_EXTRA_LINK_LIBS}) + endif() + else() + # No executable, just invoke the benchmark (probably a script) directly. + set(BENCHMARK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${REL_BENCHMARK_NAME}) + set(NO_COLOR "") + endif() + + # With OSX and conda, we need to set the correct RPATH so that dependencies + # are found. The installed libraries with conda have an RPATH that matches + # for executables and libraries lying in $ENV{CONDA_PREFIX}/bin or + # $ENV{CONDA_PREFIX}/lib but our test libraries and executables are not + # installed there. + if(NOT "$ENV{CONDA_PREFIX}" STREQUAL "" AND APPLE) + set_target_properties(${BENCHMARK_NAME} + PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH_USE_LINK_PATH TRUE + INSTALL_RPATH + "$ENV{CONDA_PREFIX}/lib;${EXECUTABLE_OUTPUT_PATH}") + endif() + + # Add test as dependency of relevant label targets + add_dependencies(all-benchmarks ${BENCHMARK_NAME}) + foreach(TARGET ${ARG_LABELS}) + add_dependencies(${TARGET} ${BENCHMARK_NAME}) + endforeach() + + if(ARG_DEPENDENCIES) + add_dependencies(${BENCHMARK_NAME} ${ARG_DEPENDENCIES}) + endif() + + if(ARG_LABELS) + set(ARG_LABELS "benchmark;${ARG_LABELS}") + else() + set(ARG_LABELS benchmark) + endif() + + add_test(${BENCHMARK_NAME} + ${BUILD_SUPPORT_DIR}/run-test.sh + ${CMAKE_BINARY_DIR} + benchmark + ${BENCHMARK_PATH} + ${NO_COLOR}) + set_property(TEST ${BENCHMARK_NAME} + APPEND + PROPERTY LABELS ${ARG_LABELS}) +endfunction() + +# +# Testing +# +# Add a new test case, with or without an executable that should be built. +# +# REL_TEST_NAME is the name of the test. It may be a single component +# (e.g. monotime-test) or contain additional components (e.g. +# net/net_util-test). Either way, the last component must be a globally +# unique name. +# +# If given, SOURCES is the list of C++ source files to compile into the test +# executable. Otherwise, "REL_TEST_NAME.cc" is used. +# +# The unit test is added with a label of "unittest" to support filtering with +# ctest. +# +# Arguments after the test name will be passed to set_tests_properties(). +# +# \arg ENABLED if passed, add this unit test even if ARROW_BUILD_TESTS is off +# \arg PREFIX a string to append to the name of the test executable. For +# example, if you have src/arrow/foo/bar-test.cc, then PREFIX "foo" will create +# test executable foo-bar-test +# \arg LABELS the unit test label or labels to assign the unit tests +# to. By default, unit tests will go in the "unittest" group, but if we have +# multiple unit tests in some subgroup, you can assign a test to multiple +# groups use the syntax unittest;GROUP2;GROUP3. Custom targets for the group +# names must exist +function(ADD_TEST_CASE REL_TEST_NAME) + set(options NO_VALGRIND ENABLED) + set(one_value_args PRECOMPILED_HEADER_LIB) + set(multi_value_args + SOURCES + PRECOMPILED_HEADERS + STATIC_LINK_LIBS + EXTRA_LINK_LIBS + EXTRA_INCLUDES + EXTRA_DEPENDENCIES + LABELS + EXTRA_LABELS + PREFIX) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(NO_TESTS AND NOT ARG_ENABLED) + return() + endif() + get_filename_component(TEST_NAME ${REL_TEST_NAME} NAME_WE) + + if(ARG_PREFIX) + set(TEST_NAME "${ARG_PREFIX}-${TEST_NAME}") + endif() + + if(ARG_SOURCES) + set(SOURCES ${ARG_SOURCES}) + else() + set(SOURCES "${REL_TEST_NAME}.cc") + endif() + + # Make sure the executable name contains only hyphens, not underscores + string(REPLACE "_" "-" TEST_NAME ${TEST_NAME}) + + set(TEST_PATH "${EXECUTABLE_OUTPUT_PATH}/${TEST_NAME}") + add_executable(${TEST_NAME} ${SOURCES}) + + # With OSX and conda, we need to set the correct RPATH so that dependencies + # are found. The installed libraries with conda have an RPATH that matches + # for executables and libraries lying in $ENV{CONDA_PREFIX}/bin or + # $ENV{CONDA_PREFIX}/lib but our test libraries and executables are not + # installed there. + if(NOT "$ENV{CONDA_PREFIX}" STREQUAL "" AND APPLE) + set_target_properties(${TEST_NAME} + PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH_USE_LINK_PATH TRUE + INSTALL_RPATH + "${EXECUTABLE_OUTPUT_PATH};$ENV{CONDA_PREFIX}/lib") + endif() + + if(ARG_STATIC_LINK_LIBS) + # Customize link libraries + target_link_libraries(${TEST_NAME} PRIVATE ${ARG_STATIC_LINK_LIBS}) + else() + target_link_libraries(${TEST_NAME} PRIVATE ${ARROW_TEST_LINK_LIBS}) + endif() + + if(ARG_PRECOMPILED_HEADER_LIB) + reuse_precompiled_header_lib(${TEST_NAME} ${ARG_PRECOMPILED_HEADER_LIB}) + endif() + + if(ARG_PRECOMPILED_HEADERS AND ARROW_USE_PRECOMPILED_HEADERS) + target_precompile_headers(${TEST_NAME} PRIVATE ${ARG_PRECOMPILED_HEADERS}) + endif() + + if(ARG_EXTRA_LINK_LIBS) + target_link_libraries(${TEST_NAME} PRIVATE ${ARG_EXTRA_LINK_LIBS}) + endif() + + if(ARG_EXTRA_INCLUDES) + target_include_directories(${TEST_NAME} SYSTEM PUBLIC ${ARG_EXTRA_INCLUDES}) + endif() + + if(ARG_EXTRA_DEPENDENCIES) + add_dependencies(${TEST_NAME} ${ARG_EXTRA_DEPENDENCIES}) + endif() + + if(ARROW_TEST_MEMCHECK AND NOT ARG_NO_VALGRIND) + add_test(${TEST_NAME} + bash + -c + "cd '${CMAKE_SOURCE_DIR}'; \ + valgrind --suppressions=valgrind.supp --tool=memcheck --gen-suppressions=all \ + --num-callers=500 --leak-check=full --leak-check-heuristics=stdstring \ + --error-exitcode=1 ${TEST_PATH}") + elseif(WIN32) + add_test(${TEST_NAME} ${TEST_PATH}) + else() + add_test(${TEST_NAME} + ${BUILD_SUPPORT_DIR}/run-test.sh + ${CMAKE_BINARY_DIR} + test + ${TEST_PATH}) + endif() + + # Add test as dependency of relevant targets + add_dependencies(all-tests ${TEST_NAME}) + foreach(TARGET ${ARG_LABELS}) + add_dependencies(${TARGET} ${TEST_NAME}) + endforeach() + + set(LABELS) + list(APPEND LABELS "unittest") + if(ARG_LABELS) + list(APPEND LABELS ${ARG_LABELS}) + endif() + # EXTRA_LABELS don't create their own dependencies, they are only used + # to ease running certain test categories. + if(ARG_EXTRA_LABELS) + list(APPEND LABELS ${ARG_EXTRA_LABELS}) + endif() + + foreach(LABEL ${ARG_LABELS}) + # ensure there is a cmake target which exercises tests with this LABEL + set(LABEL_TEST_NAME "test-${LABEL}") + if(NOT TARGET ${LABEL_TEST_NAME}) + add_custom_target(${LABEL_TEST_NAME} + ctest -L "${LABEL}" --output-on-failure + USES_TERMINAL) + endif() + # ensure the test is (re)built before the LABEL test runs + add_dependencies(${LABEL_TEST_NAME} ${TEST_NAME}) + endforeach() + + set_property(TEST ${TEST_NAME} + APPEND + PROPERTY LABELS ${LABELS}) +endfunction() + +# +# Examples +# +# Add a new example, with or without an executable that should be built. +# If examples are enabled then they will be run along side unit tests with ctest. +# 'make runexample' to build/run only examples. +# +# REL_EXAMPLE_NAME is the name of the example app. It may be a single component +# (e.g. monotime-example) or contain additional components (e.g. +# net/net_util-example). Either way, the last component must be a globally +# unique name. + +# The example will registered as unit test with ctest with a label +# of 'example'. +# +# Arguments after the test name will be passed to set_tests_properties(). +# +# \arg PREFIX a string to append to the name of the example executable. For +# example, if you have src/arrow/foo/bar-example.cc, then PREFIX "foo" will +# create test executable foo-bar-example +function(ADD_ARROW_EXAMPLE REL_EXAMPLE_NAME) + set(options) + set(one_value_args) + set(multi_value_args EXTRA_LINK_LIBS DEPENDENCIES PREFIX) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(NO_EXAMPLES) + return() + endif() + get_filename_component(EXAMPLE_NAME ${REL_EXAMPLE_NAME} NAME_WE) + + if(ARG_PREFIX) + set(EXAMPLE_NAME "${ARG_PREFIX}-${EXAMPLE_NAME}") + endif() + + if(EXISTS ${CMAKE_SOURCE_DIR}/examples/arrow/${REL_EXAMPLE_NAME}.cc) + # This example has a corresponding .cc file, set it up as an executable. + set(EXAMPLE_PATH "${EXECUTABLE_OUTPUT_PATH}/${EXAMPLE_NAME}") + add_executable(${EXAMPLE_NAME} "${REL_EXAMPLE_NAME}.cc") + target_link_libraries(${EXAMPLE_NAME} ${ARROW_EXAMPLE_LINK_LIBS}) + add_dependencies(runexample ${EXAMPLE_NAME}) + set(NO_COLOR "--color_print=false") + + if(ARG_EXTRA_LINK_LIBS) + target_link_libraries(${EXAMPLE_NAME} ${ARG_EXTRA_LINK_LIBS}) + endif() + endif() + + if(ARG_DEPENDENCIES) + add_dependencies(${EXAMPLE_NAME} ${ARG_DEPENDENCIES}) + endif() + + add_test(${EXAMPLE_NAME} ${EXAMPLE_PATH}) + set_tests_properties(${EXAMPLE_NAME} PROPERTIES LABELS "example") +endfunction() + +# +# Fuzzing +# +# Add new fuzz target executable. +# +# The single source file must define a function: +# extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +# +# No main function must be present within the source file! +# +function(ADD_FUZZ_TARGET REL_FUZZING_NAME) + set(options) + set(one_value_args PREFIX) + set(multi_value_args LINK_LIBS) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(ARG_UNPARSED_ARGUMENTS) + message(SEND_ERROR "Error: unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(NO_FUZZING) + return() + endif() + + get_filename_component(FUZZING_NAME ${REL_FUZZING_NAME} NAME_WE) + + # Make sure the executable name contains only hyphens, not underscores + string(REPLACE "_" "-" FUZZING_NAME ${FUZZING_NAME}) + + if(ARG_PREFIX) + set(FUZZING_NAME "${ARG_PREFIX}-${FUZZING_NAME}") + endif() + + # For OSS-Fuzz + # (https://google.github.io/oss-fuzz/advanced-topics/ideal-integration/) + if(DEFINED ENV{LIB_FUZZING_ENGINE}) + set(FUZZ_LDFLAGS $ENV{LIB_FUZZING_ENGINE}) + else() + set(FUZZ_LDFLAGS "-fsanitize=fuzzer") + endif() + + add_executable(${FUZZING_NAME} "${REL_FUZZING_NAME}.cc") + target_link_libraries(${FUZZING_NAME} ${LINK_LIBS}) + target_compile_options(${FUZZING_NAME} PRIVATE ${FUZZ_LDFLAGS}) + set_target_properties(${FUZZING_NAME} PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS} LABELS + "fuzzing") +endfunction() + +function(ARROW_INSTALL_ALL_HEADERS PATH) + set(options) + set(one_value_args) + set(multi_value_args PATTERN) + cmake_parse_arguments(ARG + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN}) + if(NOT ARG_PATTERN) + # The .hpp extension is used by some vendored libraries + set(ARG_PATTERN "*.h" "*.hpp") + endif() + file(GLOB CURRENT_DIRECTORY_HEADERS ${ARG_PATTERN}) + + set(PUBLIC_HEADERS) + foreach(HEADER ${CURRENT_DIRECTORY_HEADERS}) + get_filename_component(HEADER_BASENAME ${HEADER} NAME) + if(HEADER_BASENAME MATCHES "internal") + continue() + endif() + list(APPEND PUBLIC_HEADERS ${HEADER}) + endforeach() + install(FILES ${PUBLIC_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PATH}") +endfunction() + +function(ARROW_ADD_PKG_CONFIG MODULE) + configure_file(${MODULE}.pc.in "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/") +endfunction() + +function(ARROW_INSTALL_CMAKE_FIND_MODULE MODULE) + install(FILES "${ARROW_SOURCE_DIR}/cmake_modules/Find${MODULE}.cmake" + DESTINATION "${ARROW_CMAKE_INSTALL_DIR}") +endfunction() + +# Implementations of lisp "car" and "cdr" functions +macro(ARROW_CAR var) + set(${var} ${ARGV1}) +endmacro() + +macro(ARROW_CDR var rest) + set(${var} ${ARGN}) +endmacro() |