diff --git a/.gitignore b/.gitignore index 3721346946..24d5bba596 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ compare_git_to_perforce.bash .clangd/ .cache compile_commands.json +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f52226041..116c85db9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,33 @@ cmake_minimum_required(VERSION 3.8) project(libcudacxx CXX) +# Determine whether libcudacxx is the top-level project or included into +# another project via add_subdirectory(). +if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_LIST_DIR}") + set(libcudacxx_TOPLEVEL_PROJECT ON) +else() + set(libcudacxx_TOPLEVEL_PROJECT OFF) +endif() + +include(cmake/libcudacxxInstallRules.cmake) + +if (NOT libcudacxx_TOPLEVEL_PROJECT) + include(cmake/libcudacxxAddSubdir.cmake) + return() +endif() + +# Note that this currently returns and skips the rest of the build +# system. +option(libcudacxx_ENABLE_CMAKE_TESTS "Enable ctest-based testing." OFF) +if (libcudacxx_ENABLE_CMAKE_TESTS) + # Might be able to lower this, but would need to do some testing: + cmake_minimum_required(VERSION 3.20.1) + include(CTest) + enable_testing() # Must be called in root CMakeLists.txt + add_subdirectory(cmake/test/) + return() +endif() + set(PACKAGE_NAME libcudacxx) set(PACKAGE_VERSION 11.0) set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") diff --git a/cmake/libcudacxxAddSubdir.cmake b/cmake/libcudacxxAddSubdir.cmake new file mode 100644 index 0000000000..20c4674c9b --- /dev/null +++ b/cmake/libcudacxxAddSubdir.cmake @@ -0,0 +1,4 @@ +find_package(libcudacxx REQUIRED CONFIG + NO_DEFAULT_PATH # Only check the explicit path in HINTS: + HINTS "${CMAKE_CURRENT_LIST_DIR}/.." +) diff --git a/cmake/libcudacxxInstallRules.cmake b/cmake/libcudacxxInstallRules.cmake new file mode 100644 index 0000000000..cf140024b5 --- /dev/null +++ b/cmake/libcudacxxInstallRules.cmake @@ -0,0 +1,38 @@ +option(libcudacxx_ENABLE_INSTALL_RULES + "Enable installation of libcudacxx" ${libcudacxx_TOPLEVEL_PROJECT} +) + +if (NOT libcudacxx_ENABLE_INSTALL_RULES) + return() +endif() + +# Bring in CMAKE_INSTALL_LIBDIR +include(GNUInstallDirs) + +# libcudacxx is a header library; no need to build anything before installing: +set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY TRUE) + +# Libcudacxx headers +install(DIRECTORY "${libcudacxx_SOURCE_DIR}/include/cuda" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) +install(DIRECTORY "${libcudacxx_SOURCE_DIR}/include/nv" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +# Libcudacxx cmake package +install(DIRECTORY "${libcudacxx_SOURCE_DIR}/lib/cmake/libcudacxx" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake" + PATTERN libcudacxx-header-search EXCLUDE +) + +# Need to configure a file to store CMAKE_INSTALL_INCLUDEDIR +# since it can be defined by the user. This is common to work around collisions +# with the CTK installed headers. +configure_file("${libcudacxx_SOURCE_DIR}/lib/cmake/libcudacxx/libcudacxx-header-search.cmake.in" + "${libcudacxx_BINARY_DIR}/lib/cmake/libcudacxx/libcudacxx-header-search.cmake" + @ONLY +) +install(FILES "${libcudacxx_BINARY_DIR}/lib/cmake/libcudacxx/libcudacxx-header-search.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libcudacxx" +) diff --git a/cmake/test/CMakeLists.txt b/cmake/test/CMakeLists.txt new file mode 100644 index 0000000000..c2a13bb898 --- /dev/null +++ b/cmake/test/CMakeLists.txt @@ -0,0 +1,75 @@ +if ("MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}") + # There's a bug that prevents build-and-test from working on MSVC. + # See NVIDIA/nvbench#43. + return() +endif() + +set(cmake_opts + -D "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" + -D "CMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}" + -D "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" +) + +# Temporary installation prefix for tests against installed project: +set(tmp_install_prefix "${CMAKE_CURRENT_BINARY_DIR}/test_install") + +# Add a build-and-test CTest. +# - full_test_name_var will be set to the full name of the test. +# - subdir is the relative path to the test project directory. +# - test_id is used to generate a unique name for this test, allowing the +# subdir to be reused. +# - Any additional args will be passed to the project configure step. +function(libcudacxx_add_compile_test full_test_name_var subdir test_id) + set(test_name libcudacxx.test.cmake.${subdir}.${test_id}) + set(src_dir "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}") + set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/${subdir}/${test_id}") + add_test(NAME ${test_name} + COMMAND "${CMAKE_CTEST_COMMAND}" + --build-and-test "${src_dir}" "${build_dir}" + --build-generator "${CMAKE_GENERATOR}" + --build-options + ${cmake_opts} + ${ARGN} + --test-command "${CMAKE_CTEST_COMMAND}" --output-on-failure + ) + set(${full_test_name_var} ${test_name} PARENT_SCOPE) +endfunction() + +################################################################################ +# Test against source dir + +libcudacxx_add_compile_test(test_name + test_export + source_tree + -D "libcudacxx_DIR=${libcudacxx_SOURCE_DIR}/lib/cmake/libcudacxx/" + -D TEST_TYPE=SOURCE_TREE +) + +################################################################################ +# Test against install tree + +libcudacxx_add_compile_test(test_name + test_export + install_tree + -D "libcudacxx_DIR=${tmp_install_prefix}/lib/cmake/libcudacxx/" + -D TEST_TYPE=INSTALL_TREE +) +set_tests_properties(${test_name} PROPERTIES FIXTURES_REQUIRED install_tree) + +################################################################################ +# Install tree fixtures +add_test(NAME libcudacxx.test.cmake.install_tree.install + COMMAND "${CMAKE_COMMAND}" + --install "${libcudacxx_BINARY_DIR}" + --prefix "${tmp_install_prefix}" +) +set_tests_properties(libcudacxx.test.cmake.install_tree.install PROPERTIES + FIXTURES_SETUP install_tree +) + +add_test(NAME libcudacxx.test.cmake.install_tree.cleanup + COMMAND "${CMAKE_COMMAND}" -E rm -rf "${tmp_install_prefix}" +) +set_tests_properties(libcudacxx.test.cmake.install_tree.cleanup PROPERTIES + FIXTURES_CLEANUP install_tree +) diff --git a/cmake/test/test_export/CMakeLists.txt b/cmake/test/test_export/CMakeLists.txt new file mode 100644 index 0000000000..f8f7b4fe8e --- /dev/null +++ b/cmake/test/test_export/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.20.1) +project(libcudacxxTestExport CXX) + +message(STATUS "libcudacxx_DIR=${libcudacxx_DIR}") +find_package(libcudacxx) + +add_executable(version_check version_check.cxx) +target_link_libraries(version_check PRIVATE libcudacxx::libcudacxx) +enable_testing() +add_test(NAME version_check COMMAND "$") +set_property(TEST version_check PROPERTY + PASS_REGULAR_EXPRESSION + "${libcudacxx_VERSION_MAJOR}\.${libcudacxx_VERSION_MINOR}\.${libcudacxx_VERSION_PATCH}" +) diff --git a/cmake/test/test_export/version_check.cxx b/cmake/test/test_export/version_check.cxx new file mode 100644 index 0000000000..0fa0df0df8 --- /dev/null +++ b/cmake/test/test_export/version_check.cxx @@ -0,0 +1,18 @@ +#include + +#include + +int main() +{ + cuda::std::atomic x{0}; + + printf("Built with libcudacxx version %d.%d.%d.\n", + _LIBCUDACXX_CUDA_API_VERSION_MAJOR, + _LIBCUDACXX_CUDA_API_VERSION_MINOR, + _LIBCUDACXX_CUDA_API_VERSION_PATCH); + + return x; +} + + + diff --git a/lib/cmake/libcudacxx/libcudacxx-config-version.cmake b/lib/cmake/libcudacxx/libcudacxx-config-version.cmake new file mode 100644 index 0000000000..a29d076fd6 --- /dev/null +++ b/lib/cmake/libcudacxx/libcudacxx-config-version.cmake @@ -0,0 +1,36 @@ +# Parse version information from version header: +include("${CMAKE_CURRENT_LIST_DIR}/libcudacxx-header-search.cmake") + +file(READ "${_libcudacxx_VERSION_INCLUDE_DIR}/cuda/std/detail/__config" + libcudacxx_VERSION_HEADER +) + +string(REGEX MATCH + "#define[ \t]+_LIBCUDACXX_CUDA_API_VERSION[ \t]+([0-9]+)" unused_var + "${libcudacxx_VERSION_HEADER}" +) + +set(libcudacxx_VERSION_FLAT ${CMAKE_MATCH_1}) +math(EXPR libcudacxx_VERSION_MAJOR "${libcudacxx_VERSION_FLAT} / 1000000") +math(EXPR libcudacxx_VERSION_MINOR "(${libcudacxx_VERSION_FLAT} / 1000) % 1000") +math(EXPR libcudacxx_VERSION_PATCH "${libcudacxx_VERSION_FLAT} % 1000") +set(libcudacxx_VERSION_TWEAK 0) + +set(libcudacxx_VERSION + "${libcudacxx_VERSION_MAJOR}.${libcudacxx_VERSION_MINOR}.${libcudacxx_VERSION_PATCH}.${libcudacxx_VERSION_TWEAK}" +) + +set(PACKAGE_VERSION ${libcudacxx_VERSION}) +set(PACKAGE_VERSION_COMPATIBLE FALSE) +set(PACKAGE_VERSION_EXACT FALSE) +set(PACKAGE_VERSION_UNSUITABLE FALSE) + +if(PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION) + if(PACKAGE_FIND_VERSION_MAJOR VERSION_EQUAL libcudacxx_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + endif() + + if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/lib/cmake/libcudacxx/libcudacxx-config.cmake b/lib/cmake/libcudacxx/libcudacxx-config.cmake new file mode 100644 index 0000000000..259543b3a5 --- /dev/null +++ b/lib/cmake/libcudacxx/libcudacxx-config.cmake @@ -0,0 +1,56 @@ +# +# find_package(libcudacxx) config file. +# +# Defines a libcudacxx::libcudacxx target that may be linked from user projects to include +# libcudacxx. + +if (TARGET libcudacxx::libcudacxx) + return() +endif() + +function(_libcudacxx_declare_interface_alias alias_name ugly_name) + # 1) Only IMPORTED and ALIAS targets can be placed in a namespace. + # 2) When an IMPORTED library is linked to another target, its include + # directories are treated as SYSTEM includes. + # 3) nvcc will automatically check the CUDA Toolkit include path *before* the + # system includes. This means that the Toolkit libcudacxx will *always* be used + # during compilation, and the include paths of an IMPORTED libcudacxx::libcudacxx + # target will never have any effect. + # 4) This behavior can be fixed by setting the property NO_SYSTEM_FROM_IMPORTED + # on EVERY target that links to libcudacxx::libcudacxx. This would be a burden and a + # footgun for our users. Forgetting this would silently pull in the wrong libcudacxx! + # 5) A workaround is to make a non-IMPORTED library outside of the namespace, + # configure it, and then ALIAS it into the namespace (or ALIAS and then + # configure, that seems to work too). + add_library(${ugly_name} INTERFACE) + add_library(${alias_name} ALIAS ${ugly_name}) +endfunction() + +# +# Setup targets +# + +_libcudacxx_declare_interface_alias(libcudacxx::libcudacxx _libcudacxx_libcudacxx) +# Pull in the include dir detected by libcudacxx-config-version.cmake +set(_libcudacxx_INCLUDE_DIR "${_libcudacxx_VERSION_INCLUDE_DIR}" + CACHE INTERNAL "Location of libcudacxx headers." +) +unset(_libcudacxx_VERSION_INCLUDE_DIR CACHE) # Clear tmp variable from cache +target_include_directories(_libcudacxx_libcudacxx INTERFACE "${_libcudacxx_INCLUDE_DIR}") + +# +# Standardize version info +# + +set(LIBCUDACXX_VERSION ${${CMAKE_FIND_PACKAGE_NAME}_VERSION} CACHE INTERNAL "") +set(LIBCUDACXX_VERSION_MAJOR ${${CMAKE_FIND_PACKAGE_NAME}_VERSION_MAJOR} CACHE INTERNAL "") +set(LIBCUDACXX_VERSION_MINOR ${${CMAKE_FIND_PACKAGE_NAME}_VERSION_MINOR} CACHE INTERNAL "") +set(LIBCUDACXX_VERSION_PATCH ${${CMAKE_FIND_PACKAGE_NAME}_VERSION_PATCH} CACHE INTERNAL "") +set(LIBCUDACXX_VERSION_TWEAK ${${CMAKE_FIND_PACKAGE_NAME}_VERSION_TWEAK} CACHE INTERNAL "") +set(LIBCUDACXX_VERSION_COUNT ${${CMAKE_FIND_PACKAGE_NAME}_VERSION_COUNT} CACHE INTERNAL "") + +include(FindPackageHandleStandardArgs) +if (NOT libcudacxx_CONFIG) + set(libcudacxx_CONFIG "${CMAKE_CURRENT_LIST_FILE}") +endif() +find_package_handle_standard_args(libcudacxx CONFIG_MODE) diff --git a/lib/cmake/libcudacxx/libcudacxx-header-search.cmake b/lib/cmake/libcudacxx/libcudacxx-header-search.cmake new file mode 100644 index 0000000000..1e9fba2626 --- /dev/null +++ b/lib/cmake/libcudacxx/libcudacxx-header-search.cmake @@ -0,0 +1,8 @@ +# Parse version information from version header: +unset(_libcudacxx_VERSION_INCLUDE_DIR CACHE) # Clear old result to force search +find_path(_libcudacxx_VERSION_INCLUDE_DIR cuda/std/detail/__config + NO_DEFAULT_PATH # Only search explicit paths below: + PATHS + "${CMAKE_CURRENT_LIST_DIR}/../../../include" # Source tree +) +set_property(CACHE _libcudacxx_VERSION_INCLUDE_DIR PROPERTY TYPE INTERNAL) diff --git a/lib/cmake/libcudacxx/libcudacxx-header-search.cmake.in b/lib/cmake/libcudacxx/libcudacxx-header-search.cmake.in new file mode 100644 index 0000000000..9e7e187ca3 --- /dev/null +++ b/lib/cmake/libcudacxx/libcudacxx-header-search.cmake.in @@ -0,0 +1,8 @@ +# Parse version information from version header: +unset(_libcudacxx_VERSION_INCLUDE_DIR CACHE) # Clear old result to force search +find_path(_libcudacxx_VERSION_INCLUDE_DIR cuda/std/detail/__config + NO_DEFAULT_PATH # Only search explicit paths below: + PATHS + "${CMAKE_CURRENT_LIST_DIR}/../../../@CMAKE_INSTALL_INCLUDEDIR@" # Install tree +) +set_property(CACHE _libcudacxx_VERSION_INCLUDE_DIR PROPERTY TYPE INTERNAL)