From 80218041ccfa1de3f85a3a4944f95aa72720e8d0 Mon Sep 17 00:00:00 2001
From: Sebastian Schmitt <sebastian.schmitt@kip.uni-heidelberg.de>
Date: Wed, 6 Jan 2021 14:29:15 +0100
Subject: [PATCH] Option to toggle between external and bundled versions of C++
 dependencies (#1198)

* Add the ability to use json and pybind11 libraries installed on the system instead of the
  vesrsions of those libraries bundled as submodules in the Arbor repository
  * Turned on by default
  * Toggled using the new `ARB_USE_BUNDLED_LIBS` CMake flag
* Update documentation for installation
* Update CI and pip workflows to opt in to the bundled libraries
---
 .github/workflows/basic.yml          |  2 +-
 CMakeLists.txt                       | 30 ++++++++++++++++++++++------
 doc/CMakeLists.txt                   |  4 ++--
 doc/install/build_install.rst        | 30 ++++++++++++++++++++++------
 example/bench/CMakeLists.txt         |  2 +-
 example/dryrun/CMakeLists.txt        |  2 +-
 example/gap_junctions/CMakeLists.txt |  2 +-
 example/generators/CMakeLists.txt    |  2 +-
 example/ring/CMakeLists.txt          |  2 +-
 ext/CMakeLists.txt                   |  7 +++++--
 python/CMakeLists.txt                | 27 +++++++++++++++----------
 scripts/travis/build.sh              |  2 +-
 setup.py                             | 10 ++++++++--
 sup/CMakeLists.txt                   |  2 +-
 14 files changed, 87 insertions(+), 37 deletions(-)

diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml
index cc862780..884634f2 100644
--- a/.github/workflows/basic.yml
+++ b/.github/workflows/basic.yml
@@ -74,7 +74,7 @@ jobs:
         run: |
           mkdir build
           cd build
-          cmake .. -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.mpi }}
+          cmake .. -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.mpi }} -DARB_USE_BUNDLED_LIBS=ON
           make -j4 tests examples pyarb html
           cd -
       - name: Run unit tests
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e8c755ad..8155f906 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,5 @@
 cmake_minimum_required(VERSION 3.12)
+include(CMakeDependentOption)
 
 file(READ VERSION FULL_VERSION_STRING)
 string(STRIP "${FULL_VERSION_STRING}" FULL_VERSION_STRING)
@@ -37,6 +38,10 @@ option(ARB_UNWIND "Use libunwind for stack trace printing if available" OFF)
 set(ARB_GPU "none" CACHE STRING "GPU backend and compiler configuration")
 set_property(CACHE PROPERTY STRINGS "none" "cuda" "cuda-clang" "hip")
 
+# Use bundled 3rd party libraries
+
+option(ARB_USE_BUNDLED_LIBS "Use bundled 3rd party libraries" OFF)
+
 #----------------------------------------------------------
 # Configure-time features for Arbor:
 #----------------------------------------------------------
@@ -198,6 +203,13 @@ install(TARGETS arbornml-public-deps EXPORT arbornml-targets)
 # External libraries in `ext` sub-directory: json, tinyopt and randon123.
 # Creates interface libraries `ext-json`, `ext-tinyopt` and `ext-random123`
 
+cmake_dependent_option(ARB_USE_BUNDLED_JSON "Use bundled Niels Lohmann's json library." ON "ARB_USE_BUNDLED_LIBS" OFF)
+if(NOT ARB_USE_BUNDLED_JSON)
+  find_package(nlohmann_json)
+  set(json_library_name nlohmann_json::nlohmann_json)
+else()
+  unset(nlohmann_json_DIR CACHE)
+endif()
 add_subdirectory(ext)
 
 # Keep track of packages we need to add to the generated CMake config
@@ -234,10 +246,15 @@ endif()
 
 # The minimum version of Python supported by Arbor.
 set(arb_py_version 3.6.0)
+
+if(DEFINED PYTHON_EXECUTABLE)
+    set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
+endif()
+
 if(ARB_WITH_PYTHON)
+    cmake_dependent_option(ARB_USE_BUNDLED_PYBIND11 "Use bundled pybind11" ON "ARB_WITH_PYTHON;ARB_USE_BUNDLED_LIBS" OFF)
+
     find_package(Python3 ${arb_py_version} COMPONENTS Interpreter Development REQUIRED)
-    set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}")
-    message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}")
 
     # Required to link the dynamic libraries for python modules.
     # Effectively adds '-fpic' flag to CXX_FLAGS.
@@ -246,10 +263,11 @@ else()
     # If not building the Python module, the interpreter is still required
     # to build some targets, e.g. when building the documentation.
     find_package(Python3 ${arb_py_version} COMPONENTS Interpreter)
-    if(Python3_FOUND)
-        set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}")
-        message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}")
-    endif()
+endif()
+
+if(${Python3_FOUND})
+    set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}")
+    message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}")
 endif()
 
 
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index 5889d51a..755550f5 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -20,8 +20,8 @@ string(REPLACE ";" "," theme_path "${theme_path}")
 
 add_custom_target(html
     COMMAND
-        PYTHONPATH=${CMAKE_BINARY_DIR}/python
-        ${PYTHON_EXECUTABLE}
+        PYTHONPATH="${CMAKE_BINARY_DIR}/python:$ENV{PYTHONPATH}"
+        "${PYTHON_EXECUTABLE}"
         -m sphinx
         -b html
         -d ${doctree_dir}
diff --git a/doc/install/build_install.rst b/doc/install/build_install.rst
index 2319d0cb..b2802297 100644
--- a/doc/install/build_install.rst
+++ b/doc/install/build_install.rst
@@ -141,6 +141,24 @@ install `Sphinx <http://www.sphinx-doc.org/en/master/>`_.
 
 .. _install-downloading:
 
+
+External dependencies
+~~~~~~~~~~~~~~~~~~~~~
+
+For the (optional) python bindings Arbor uses `pybind11 <https://github.com/pybind/pybind11>`_, and
+JSON parsing is faciliated through `nlohmann json <https://github.com/nlohmann/json>`_.
+
+There are two ways to obtain these libraries. The default way is to use them from the
+system, e.g., installed via ``apt install python3-pybind11`` and ``apt install nlohmann-json3-dev``
+for a Debian based distribution.
+
+The other possiblity is to use versions of these dependencies that are bundled with Arbor
+via the CMAKE option `ARB_USE_BUNDLED_LIBS`.
+If set, `pybind11 <https://github.com/pybind/pybind11>`_ is retrieved from a Git submodule (see below)
+and `nlohmann json <https://github.com/nlohmann/json>`_ from a copy in the checked out sources.
+
+It is also possible to select only one of the two libraries to be taken from the system or from Arbor.
+
 Getting the code
 ================
 
@@ -194,7 +212,7 @@ For more detailed build configuration options, see the `quick start <quickstart_
     # 2) Use CMake to configure the build.
     # By default Arbor builds in release mode, i.e. with optimizations on.
     # Release mode should be used for installing and benchmarking Arbor.
-    cmake ..
+    cmake .. # add -DARB_USE_BUNDLED_LIBS=ON to use bundled/git-submoduled libs
 
     # 3.1) Build Arbor library.
     make -j 4
@@ -414,16 +432,16 @@ CMake ``ARB_WITH_PYTHON`` option:
 By default ``ARB_WITH_PYTHON=OFF``. When this option is turned on, a Python module called :py:mod:`arbor` is built.
 
 A specific version of Python can be set when configuring with CMake using the
-``Python3_EXECUTABLE`` variable. For example, to use Python 3.8 installed on a Linux
+``PYTHON_EXECUTABLE`` variable. For example, to use Python 3.8 installed on a Linux
 system with the executable in ``/usr/bin/python3.8``:
 
 .. code-block:: bash
 
-    cmake .. -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=/usr/bin/python3.8
+    cmake .. -DARB_WITH_PYTHON=ON -DPYTHON_EXECUTABLE=/usr/bin/python3.8
 
 By default the Python module will be installed in the directory returned by
-``${Python3_EXECUTABLE} -c "import sysconfig; print(sysconfig.get_path('platlib'))"``.
-This returns the directory where the supplied or found ``Python3_EXECUTABLE`` looks for system packages.
+``${PYTHON_EXECUTABLE} -c "import sysconfig; print(sysconfig.get_path('platlib'))"``.
+This returns the directory where the supplied or found ``PYTHON_EXECUTABLE`` looks for system packages.
 `See Python's sysconfig documentation <https://docs.python.org/3/library/sysconfig.html#installation-paths>`_.
 If CMake is run in a `venv` or Conda environment, this should pick up on the appropriate package directory.
 To install the module in a different location, set ``ARB_PYTHON_LIB_PATH`` to a custom path.
@@ -434,7 +452,7 @@ user site package might look like the following:
 
     cmake .. -DARB_WITH_PYTHON=ON                                              \
              -DARB_PYTHON_LIB_PATH=${HOME}/.local/lib/python3.8/site-packages/ \
-             -DPython3_EXECUTABLE=/usr/bin/python3.8
+             -DPYTHON_EXECUTABLE=/usr/bin/python3.8
 
 On the target LINUX system, the Arbor package was installed in
 ``/home/$USER/.local/lib/python3.8/site-packages``.
diff --git a/example/bench/CMakeLists.txt b/example/bench/CMakeLists.txt
index 49fb7016..b9a7e120 100644
--- a/example/bench/CMakeLists.txt
+++ b/example/bench/CMakeLists.txt
@@ -1,4 +1,4 @@
 add_executable(bench EXCLUDE_FROM_ALL bench.cpp)
 add_dependencies(examples bench)
 
-target_link_libraries(bench PRIVATE arbor arborenv arbor-sup ext-json)
+target_link_libraries(bench PRIVATE arbor arborenv arbor-sup ${json_library_name})
diff --git a/example/dryrun/CMakeLists.txt b/example/dryrun/CMakeLists.txt
index 9d9cb559..4cd7a797 100644
--- a/example/dryrun/CMakeLists.txt
+++ b/example/dryrun/CMakeLists.txt
@@ -1,4 +1,4 @@
 add_executable(dryrun EXCLUDE_FROM_ALL dryrun.cpp)
 add_dependencies(examples dryrun)
 
-target_link_libraries(dryrun PRIVATE arbor arborenv arbor-sup ext-json)
+target_link_libraries(dryrun PRIVATE arbor arborenv arbor-sup ${json_library_name})
diff --git a/example/gap_junctions/CMakeLists.txt b/example/gap_junctions/CMakeLists.txt
index abd78ebe..9aee5fbe 100644
--- a/example/gap_junctions/CMakeLists.txt
+++ b/example/gap_junctions/CMakeLists.txt
@@ -1,4 +1,4 @@
 add_executable(gap_junctions EXCLUDE_FROM_ALL gap_junctions.cpp)
 add_dependencies(examples gap_junctions)
 
-target_link_libraries(gap_junctions PRIVATE arbor arborenv arbor-sup ext-json)
+target_link_libraries(gap_junctions PRIVATE arbor arborenv arbor-sup ${json_library_name})
diff --git a/example/generators/CMakeLists.txt b/example/generators/CMakeLists.txt
index 7bfaca9f..2cd0c230 100644
--- a/example/generators/CMakeLists.txt
+++ b/example/generators/CMakeLists.txt
@@ -1,4 +1,4 @@
 add_executable(generators EXCLUDE_FROM_ALL generators.cpp)
 add_dependencies(examples generators)
 
-target_link_libraries(generators PRIVATE arbor arbor-sup ext-json)
+target_link_libraries(generators PRIVATE arbor arbor-sup ${json_library_name})
diff --git a/example/ring/CMakeLists.txt b/example/ring/CMakeLists.txt
index 76bb5afb..953ca291 100644
--- a/example/ring/CMakeLists.txt
+++ b/example/ring/CMakeLists.txt
@@ -1,4 +1,4 @@
 add_executable(ring EXCLUDE_FROM_ALL ring.cpp)
 add_dependencies(examples ring)
 
-target_link_libraries(ring PRIVATE arbor arborenv arbor-sup ext-json)
+target_link_libraries(ring PRIVATE arbor arborenv arbor-sup ${json_library_name})
diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt
index b06c0cd2..f613d664 100644
--- a/ext/CMakeLists.txt
+++ b/ext/CMakeLists.txt
@@ -1,7 +1,10 @@
 # Niels Lohmann's json library (single-header version).
 
-add_library(ext-json INTERFACE)
-target_include_directories(ext-json INTERFACE json/single_include)
+if(ARB_USE_BUNDLED_JSON)
+  add_library(ext-json INTERFACE)
+  target_include_directories(ext-json INTERFACE json/single_include)
+  set(json_library_name ext-json PARENT_SCOPE)
+endif()
 
 # tinyopt command line parsing libary (header-only).
 
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index de34bf47..744a0d18 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -1,18 +1,23 @@
-include(FindPythonModule) # required for find_python_module
+set(PYBIND11_CPP_STANDARD -std=c++17)
+
+if(ARB_USE_BUNDLED_PYBIND11)
+  include(FindPythonModule) # required for find_python_module
 
-# Set up pybind11 as an external project.
-set(pb11_src_dir "${PROJECT_SOURCE_DIR}/python/pybind11")
-check_git_submodule(pybind11 "${pb11_src_dir}")
+  # Set up pybind11 as an external project.
+  set(pb11_src_dir "${PROJECT_SOURCE_DIR}/python/pybind11")
+  check_git_submodule(pybind11 "${pb11_src_dir}")
 
-if(NOT pybind11_avail)
+  if(NOT pybind11_avail)
     message(FATAL_ERROR "The git submodule for pybind11 is not available, required for python support")
-endif()
+  endif()
 
-# Set up pybind11, which is used to generate Python bindings.
-# Pybind11 has good cmake support, so just add the pybind11 directory,
-# instead of using find_package.
-set(PYBIND11_CPP_STANDARD -std=c++17)
-add_subdirectory(pybind11)
+  # Set up pybind11, which is used to generate Python bindings.
+  # Pybind11 has good cmake support, so just add the pybind11 directory,
+  # instead of using find_package.
+  add_subdirectory(pybind11)
+else()
+  find_package(pybind11 REQUIRED)
+endif()
 
 set(pyarb_source
     cable_probes.cpp
diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh
index f3e09600..60e6e95b 100755
--- a/scripts/travis/build.sh
+++ b/scripts/travis/build.sh
@@ -81,7 +81,7 @@ if which xcrun >/dev/null; then
     typeset -x CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}":$(xcrun --sdk macosx --show-sdk-path)/usr
 fi
 
-cmake_flags="-DARB_WITH_ASSERTIONS=ON -DARB_WITH_NEUROML=${WITH_NEUROML} -DARB_WITH_MPI=${WITH_MPI} -DARB_WITH_PYTHON=${ARB_WITH_PYTHON} -DARB_ARCH=${ARCH} ${CXX_FLAGS} ${PY_FLAGS}"
+cmake_flags="-DARB_WITH_ASSERTIONS=ON -DARB_USE_BUNDLED_LIBS=ON -DARB_WITH_NEUROML=${WITH_NEUROML} -DARB_WITH_MPI=${WITH_MPI} -DARB_WITH_PYTHON=${ARB_WITH_PYTHON} -DARB_ARCH=${ARCH} ${CXX_FLAGS} ${PY_FLAGS}"
 echo "cmake flags: ${cmake_flags}"
 cmake .. ${cmake_flags} || error "unable to configure cmake"
 
diff --git a/setup.py b/setup.py
index 02c16f0d..b1eda7ad 100644
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,8 @@ class install_command(install):
         ('gpu=',  None, 'enable nvidia cuda support (requires cudaruntime and nvcc) or amd hip support. Supported values: '
                         'none, cuda, cuda-clang, hip'),
         ('vec',   None, 'enable vectorization'),
-        ('arch=', None, 'cpu architecture, e.g. haswell, skylake, armv8-a'),
+        ('arch=', None, 'cpu architecture, e.g. haswell, skylake, armv8.2-a+sve, znver2 (default native).'),
+        ('sysdeps', None, 'don\'t use bundled 3rd party C++ dependencies (pybind11 and json). This flag forces use of dependencies installed on the system.')
     ]
 
     def initialize_options(self):
@@ -61,6 +62,7 @@ class install_command(install):
         self.gpu  = None
         self.arch = None
         self.vec  = None
+        self.sysdeps = None
 
     def finalize_options(self):
         install.finalize_options(self)
@@ -76,6 +78,9 @@ class install_command(install):
         opt['vec']  = self.vec is not None
         #   arch : target CPU micro-architecture (string).
         opt['arch'] = "native" if self.arch is None else self.arch
+        #   bundled : use bundled/git-submoduled 3rd party libraries.
+        #             By default use bundled libs.
+        opt['bundled'] = self.sysdeps is None
 
         install.run(self)
 
@@ -101,11 +106,12 @@ class cmake_build(build_ext):
         opt = cl_opt()
         cmake_args = [
             '-DARB_WITH_PYTHON=on',
-            '-DPython3_EXECUTABLE=' + sys.executable,
+            '-DPYTHON_EXECUTABLE=' + sys.executable,
             '-DARB_WITH_MPI={}'.format( 'on' if opt['mpi'] else 'off'),
             '-DARB_VECTORIZE={}'.format('on' if opt['vec'] else 'off'),
             '-DARB_ARCH={}'.format(opt['arch']),
             '-DARB_GPU={}'.format(opt['gpu']),
+            '-DARB_USE_BUNDLED_LIBS={}'.format('on' if opt['bundled'] else 'off']),
             '-DCMAKE_BUILD_TYPE=Release' # we compile with debug symbols in release mode.
         ]
 
diff --git a/sup/CMakeLists.txt b/sup/CMakeLists.txt
index 0a9e7306..79627717 100644
--- a/sup/CMakeLists.txt
+++ b/sup/CMakeLists.txt
@@ -10,7 +10,7 @@ add_library(arbor-sup ${sup-sources})
 target_compile_options(arbor-sup PRIVATE ${ARB_CXXOPT_ARCH})
 
 # The sup library uses both the json library and libarbor
-target_link_libraries(arbor-sup PUBLIC ext-json arbor)
+target_link_libraries(arbor-sup PUBLIC ${json_library_name} arbor)
 
 target_include_directories(arbor-sup PUBLIC include)
 
-- 
GitLab