From 7ade5c26732d14483b355a157f9e4d32d57ed171 Mon Sep 17 00:00:00 2001
From: Sam Yates <yates@cscs.ch>
Date: Wed, 10 Oct 2018 09:51:26 +0200
Subject: [PATCH] Add installable CMake config for arbor (#616)

Fixes #612.

* Fix issues with permissions on directories created at install time (at least for CMake 3.11+).
* Add CMake export guff to various targets and install an `arbor-config.cmake` for consumption by other CMake-based projects.
---
 CMakeLists.txt              | 92 +++++++++++++++++++++++++++++++++++--
 arbor/CMakeLists.txt        | 11 ++++-
 cmake/arbor-config.cmake.in |  9 ++++
 include/CMakeLists.txt      |  2 +
 4 files changed, 108 insertions(+), 6 deletions(-)
 create mode 100644 cmake/arbor-config.cmake.in

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5ba0e78d..948430d6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@ set(ARB_VALIDATION_DATA_DIR "${PROJECT_SOURCE_DIR}/validation/data" CACHE PATH
 #----------------------------------------------------------
 # Configure-time features for Arbor:
 #----------------------------------------------------------
+
 option(ARB_WITH_GPU "build with GPU support" OFF)
 
 option(ARB_WITH_MPI "build with MPI support" OFF)
@@ -44,9 +45,6 @@ option(ARB_WITH_ASSERTIONS "enable arb_assert() assertions in code" OFF)
 # Global CMake configuration
 #----------------------------------------------------------
 
-# Use GNU standard installation path conventions.
-include(GNUInstallDirs)
-
 # Include own CMake modules in search path, load common modules.
 
 set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
@@ -98,16 +96,22 @@ set(CMAKE_CXX_STANDARD 14)
 # Set up flags and dependencies:
 #----------------------------------------------------------
 
+# Note: any target dependency of arbor needs to be explicitly added
+# to the 'export set', even the private ones, and this must be done
+# in the same CMakeLists.txt in which the target is defined.
+
 # Interface library `arbor-private-deps` collects dependencies, options etc.
 # for the arbor library.
 
 add_library(arbor-private-deps INTERFACE)
+install(TARGETS arbor-private-deps EXPORT arbor-targets)
 
 # Interface library `arbor-public-deps` collects requirements for the
 # users of the arbor library (e.g. mpi) that will become part
 # of arbor's PUBLIC interface.
 
 add_library(arbor-public-deps INTERFACE)
+install(TARGETS arbor-public-deps EXPORT arbor-targets)
 
 # External libraries in `ext` sub-directory: json and tclap.
 # Creates interface libraries `ext-json` and `ext-tclap`.
@@ -119,6 +123,11 @@ add_subdirectory(ext)
 
 add_subdirectory(aux)
 
+# Keep track of packages we need to add to the generated CMake config
+# file for arbor.
+
+set(arbor_export_dependencies)
+
 # Target microarchitecture for building arbor libraries, tests and examples
 #---------------------------------------------------------------------------
 if(ARB_ARCH)
@@ -143,6 +152,8 @@ find_package(Threads REQUIRED)
 find_threads_cuda_fix()
 target_link_libraries(arbor-private-deps INTERFACE Threads::Threads)
 
+list(APPEND arbor_export_dependencies "Threads\;REQUIRED")
+
 # MPI support
 #-------------------
 
@@ -160,6 +171,9 @@ if(ARB_WITH_MPI)
     target_link_libraries(mpi-wrap INTERFACE MPI::MPI_CXX)
     target_compile_definitions(mpi-wrap INTERFACE MPICH_SKIP_MPICXX=1 OMPI_SKIP_MPICXX=1)
     target_link_libraries(arbor-public-deps INTERFACE mpi-wrap)
+    install(TARGETS mpi-wrap EXPORT arbor-targets)
+
+    list(APPEND arbor_export_dependencies "MPI\;REQUIRED\;CXX")
 endif()
 
 # CUDA support
@@ -187,6 +201,8 @@ find_package(Unwind)
 if(Unwind_FOUND)
     target_link_libraries(arbor-private-deps INTERFACE Unwind::unwind)
     target_compile_definitions(arbor-private-deps ARB_WITH_UNWIND)
+
+    list(APPEND arbor_export_dependencies "Unwind\;REQUIRED")
 endif()
 
 # Build and use modcc unless explicit path given
@@ -212,9 +228,47 @@ if(ARB_WITH_PROFILING)
 endif()
 
 #----------------------------------------------------------
-# Configure targets in sub-directories.
+# Set up install paths, permissions.
 #----------------------------------------------------------
 
+# Set up install paths according to GNU conventions.
+#
+# GNUInstallDirs picks (e.g.) `lib64` for the library install path on some
+# systems where this is definitely not correct (e.g. Arch Linux). If there
+# are cases where `lib` is inappropriate, we will have to incorporate special
+# case behaviour here.
+
+if(NOT CMAKE_INSTALL_LIBDIR)
+    set(CMAKE_INSTALL_LIBDIR lib)
+endif()
+include(GNUInstallDirs)
+
+# Implicitly created directories require permissions to be set explicitly
+# via this CMake variable.
+#
+# Note that this has no effect until CMake version 3.11.
+
+set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
+    OWNER_READ
+    OWNER_WRITE
+    OWNER_EXECUTE
+    GROUP_READ
+    GROUP_EXECUTE
+    WORLD_READ
+    WORLD_EXECUTE)
+
+# CMake versions 3.11 and 3.12 ignore this variable for directories
+# implicitly created by install(DIRECTORY ...), which for us corresponds
+# to our doc and include directories. Work-around by trying to install
+# a non-existant file to these locations.
+
+foreach(directory "${CMAKE_INSTALL_DOCDIR}" "${CMAKE_INSTALL_INCLUDEDIR}")
+    install(FILES _no_such_file_ OPTIONAL DESTINATION "${directory}")
+endforeach()
+
+#----------------------------------------------------------
+# Configure targets in sub-directories.
+#----------------------------------------------------------
 
 # arbor-public-headers:
 add_subdirectory(include)
@@ -242,3 +296,33 @@ if(ARB_BUILD_VALIDATION_DATA)
     add_subdirectory(validation) # validation-data
 endif()
 
+#----------------------------------------------------------
+# Generate CMake config/version files for install.
+#----------------------------------------------------------
+
+# Note: each dependency for the arbor library target, private or otherwise,
+# needs to add itself to the arbor-exports EXPORT target in the subdirectory
+# in which they are defined, or none of this will work.
+
+set(cmake_config_dir "${CMAKE_INSTALL_LIBDIR}/cmake/arbor")
+install(EXPORT arbor-targets NAMESPACE arbor:: DESTINATION "${cmake_config_dir}")
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+    "${CMAKE_CURRENT_BINARY_DIR}/arbor-config-version.cmake"
+    COMPATIBILITY SameMajorVersion)
+
+# Template file will use contents of arbor_export_dependencies to include the
+# required `find_dependency` statements. 
+
+configure_file(
+    cmake/arbor-config.cmake.in
+    "${CMAKE_CURRENT_BINARY_DIR}/arbor-config.cmake"
+    @ONLY)
+
+install(
+    FILES
+        "${CMAKE_CURRENT_BINARY_DIR}/arbor-config.cmake"
+        "${CMAKE_CURRENT_BINARY_DIR}/arbor-config-version.cmake"
+    DESTINATION "${cmake_config_dir}")
+
diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt
index 74d6a3cd..dd885eac 100644
--- a/arbor/CMakeLists.txt
+++ b/arbor/CMakeLists.txt
@@ -80,8 +80,15 @@ endif()
 # and arbor unit tests. Private headers are also used for the other binaries
 # until the process of splitting our private and public headers is complete.
 
+# Because we need to add this target to the EXPORT set, and it needs to be
+# installed (despite being private to arbor), we have to qualify the include
+# directory with a build-time only generator expression.
+
 add_library(arbor-private-headers INTERFACE)
-target_include_directories(arbor-private-headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
+target_include_directories(arbor-private-headers INTERFACE
+    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>")
+
+install(TARGETS arbor-private-headers EXPORT arbor-targets)
 
 # Mechanisms, generated from .mod files; sets arbor_mechanism_sources
 # variable, build_all_mods target. Note: CMake source file properties are
@@ -97,5 +104,5 @@ add_dependencies(arbor build_all_mods)
 target_link_libraries(arbor PRIVATE arbor-private-deps arbor-private-headers)
 target_link_libraries(arbor PUBLIC arbor-public-deps arbor-public-headers)
 
-install(TARGETS arbor ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+install(TARGETS arbor EXPORT arbor-targets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
diff --git a/cmake/arbor-config.cmake.in b/cmake/arbor-config.cmake.in
new file mode 100644
index 00000000..703e7005
--- /dev/null
+++ b/cmake/arbor-config.cmake.in
@@ -0,0 +1,9 @@
+include(CMakeFindDependencyMacro)
+
+foreach(dep @arbor_export_dependencies@)
+    find_dependency(${dep})
+endforeach()
+
+include("${CMAKE_CURRENT_LIST_DIR}/arbor-targets.cmake")
+
+
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 4006ec2a..815a3b6f 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -62,3 +62,5 @@ add_dependencies(arbor-public-headers generate_version_hpp)
 install(DIRECTORY arbor ${CMAKE_CURRENT_BINARY_DIR}/arbor
     DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
     FILES_MATCHING PATTERN "*.hpp")
+
+install(TARGETS arbor-public-headers EXPORT arbor-targets)
-- 
GitLab