From a292557f6b76b41e9446528d53921ceeffdb9804 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 10 Sep 2024 21:56:51 +0200 Subject: [PATCH 1/3] Make const element proxy. (#2388) As discussed in #2208, use a specialized proxy to avoid some allocations. --- arbor/util/piecewise.hpp | 30 ++++++++++++++++++++++-------- arbor/util/pw_over_cable.hpp | 12 +++++++++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/arbor/util/piecewise.hpp b/arbor/util/piecewise.hpp index ff83d6ea01..f047b2bbdd 100644 --- a/arbor/util/piecewise.hpp +++ b/arbor/util/piecewise.hpp @@ -103,7 +103,6 @@ #include "util/iterutil.hpp" #include "util/transform.hpp" -#include "util/meta.hpp" #include "util/partition.hpp" namespace arb { @@ -160,8 +159,8 @@ struct pw_element_proxy { extent(pw.extent(i)), value(pw.value(i)) {} operator pw_element() const { return pw_element{extent, value}; } - operator X() const { return value; } - pw_element_proxy& operator=(X x) {value = std::move(x); return *this; }; + operator X&() const { return value; } + pw_element_proxy& operator=(X x) { value = std::move(x); return *this; }; double lower_bound() const { return extent.first; } double upper_bound() const { return extent.second; } @@ -170,6 +169,21 @@ struct pw_element_proxy { X& value; }; +template +struct pw_element_proxy { + pw_element_proxy(const pw_elements& pw, pw_size_type i): + extent(pw.extent(i)), value(pw.value(i)) {} + + operator pw_element() const { return pw_element{extent, value}; } + operator const X&() const { return value; } + + double lower_bound() const { return extent.first; } + double upper_bound() const { return extent.second; } + + const std::pair extent; + const X& value; +}; + // Compute indices into vertex set corresponding to elements that cover a point x: namespace { @@ -239,12 +253,12 @@ struct pw_elements { const_iterator(): pw_(nullptr) {} using value_type = pw_element; - using pointer = const pointer_proxy>; - using reference = pw_element; + using pointer = const pointer_proxy>; + using reference = pw_element_proxy; - reference operator[](difference_type j) const { return (*pw_)[j+*c_]; } - reference operator*() const { return (*pw_)[*c_]; } - pointer operator->() const { return pointer{(*pw_)[*c_]}; } + reference operator[](difference_type j) const { return {*pw_, j + *c_}; } + reference operator*() const { return {*pw_, *c_}; } + pointer operator->() const { return pointer{reference{*pw_, *c_}}; } // (required for iterator_adaptor) counter& inner() { return c_; } diff --git a/arbor/util/pw_over_cable.hpp b/arbor/util/pw_over_cable.hpp index ee699a9fcb..73ce40e0a9 100644 --- a/arbor/util/pw_over_cable.hpp +++ b/arbor/util/pw_over_cable.hpp @@ -16,7 +16,10 @@ struct get_value { // Convert mcable_map values to a piecewise function over an mcable. // The projection gives the map from the values in the mcable_map to the values in the piecewise function. template -util::pw_elements pw_over_cable(const mcable_map& mm, mcable cable, U dflt_value, Proj projection = Proj{}) { +util::pw_elements pw_over_cable(const mcable_map& mm, + mcable cable, + U dflt_value, + Proj projection = Proj{}) { using value_type = typename mcable_map::value_type; msize_t bid = cable.branch; @@ -26,8 +29,11 @@ util::pw_elements pw_over_cable(const mcable_map& mm, mcable cable, U dflt as_branch(msize_t x): value(x) {} }; - auto map_on_branch = util::make_range( - std::equal_range(mm.begin(), mm.end(), bid, [](as_branch a, as_branch b) { return a.value({cable.prox_pos, cable.dist_pos}, {dflt_value}); From 660804e2d5cdf17217394df40b4decb5e2e37ec9 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Wed, 11 Sep 2024 07:43:14 +0200 Subject: [PATCH 2/3] Use CPM and simplify CMake. (#2267) There. I finally cracked. While trying to get CMake to play ball with Tracy I had to devise the next bespoke solution for adding a dependency. Now, we are just using CPM https://github.com/cpm-cmake/CPM.cmake which makes things significantly less painful, if not painless, it's still CMake after all. This means: - No more submodules. Everything is pulled in via CPM. - No more toggling between system packages and submodules. System is preferred, if compatible. - No more ten ways of adding a dependency. It's two now. (I'd prefer one, but what can we do?) - Significantly less code in our build system. --- .github/workflows/benchmarks.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/sanitize.yml | 2 +- .github/workflows/test-docs.yaml | 2 +- .github/workflows/test-matrix.yml | 4 +- .github/workflows/test-pip.yml | 2 +- .gitmodules | 31 - .gitpod.yml | 2 +- CMakeLists.txt | 205 ++-- arbor/CMakeLists.txt | 3 + arbor/include/CMakeLists.txt | 4 - arborio/CMakeLists.txt | 9 +- cmake/CPM.cmake | 24 + cmake/arbor-config.cmake.in | 7 + doc/CMakeLists.txt | 2 + doc/install/build_install.rst | 20 +- doc/python/hardware.rst | 1 - example/ornstein_uhlenbeck/CMakeLists.txt | 8 +- ext/CMakeLists.txt | 136 --- ext/fmt | 1 - ext/google-benchmark | 1 - ext/googletest | 1 - ext/json | 1 - ext/pugixml | 1 - ext/pybind11 | 1 - ext/random123 | 1 - ext/sup/include/sup/export.hpp | 41 - ext/tinyopt/LICENSE | 29 - ext/tinyopt/README.md | 576 ---------- ext/tinyopt/include/tinyopt/tinyopt.h | 1156 --------------------- ext/units | 1 - modcc/CMakeLists.txt | 49 +- pyproject.toml | 1 - python/CMakeLists.txt | 24 +- python/test/cpp/CMakeLists.txt | 2 +- scripts/check-all-tags.sh | 2 +- spack/package.py | 3 - sup/CMakeLists.txt | 18 +- test/ubench/CMakeLists.txt | 2 +- test/unit-distributed/CMakeLists.txt | 6 +- test/unit-modcc/CMakeLists.txt | 2 +- test/unit/CMakeLists.txt | 4 +- test/unit/test_version.cpp | 5 +- 43 files changed, 218 insertions(+), 2176 deletions(-) create mode 100644 cmake/CPM.cmake delete mode 100644 ext/CMakeLists.txt delete mode 160000 ext/fmt delete mode 160000 ext/google-benchmark delete mode 160000 ext/googletest delete mode 160000 ext/json delete mode 160000 ext/pugixml delete mode 160000 ext/pybind11 delete mode 160000 ext/random123 delete mode 100644 ext/sup/include/sup/export.hpp delete mode 100644 ext/tinyopt/LICENSE delete mode 100644 ext/tinyopt/README.md delete mode 100644 ext/tinyopt/include/tinyopt/tinyopt.h delete mode 160000 ext/units diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c9ee464569..d306774691 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -44,7 +44,7 @@ jobs: run: | mkdir build cd build - cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_MPI=OFF -DARB_USE_BUNDLED_LIBS=ON + cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_MPI=OFF ninja -j4 ubenches cd - - name: Run benchmarks diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8c9a2eaf0f..4974cd45e6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,7 +66,7 @@ jobs: run: | mkdir build cd build - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_COMPILER=gcc -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_USE_BUNDLED_LIBS=ON -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON + cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_COMPILER=gcc -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON make -j4 tests examples pyarb cd - diff --git a/.github/workflows/sanitize.yml b/.github/workflows/sanitize.yml index bb17283317..4684d758e1 100644 --- a/.github/workflows/sanitize.yml +++ b/.github/workflows/sanitize.yml @@ -61,7 +61,7 @@ jobs: mkdir build cd build export SAN="-fsanitize=${{ matrix.sanitizer }} -fno-omit-frame-pointer" - cmake .. -GNinja -DCMAKE_BUILD_TYPE=debug -DCMAKE_CXX_FLAGS="$SAN" -DCMAKE_C_FLAGS="$SAN" -DCMAKE_EXE_LINKER_FLAGS="$SAN" -DCMAKE_MODULE_LINKER_FLAGS="$SAN" -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_VECTORIZE=${{ matrix.simd }} -DARB_WITH_MPI=OFF -DARB_USE_BUNDLED_LIBS=ON -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` + cmake .. -GNinja -DCMAKE_BUILD_TYPE=debug -DCMAKE_CXX_FLAGS="$SAN" -DCMAKE_C_FLAGS="$SAN" -DCMAKE_EXE_LINKER_FLAGS="$SAN" -DCMAKE_MODULE_LINKER_FLAGS="$SAN" -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_VECTORIZE=${{ matrix.simd }} -DARB_WITH_MPI=OFF -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` ninja -j4 -v tests examples pyarb cd - - name: Run unit tests diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml index 21281e6600..21a5468458 100644 --- a/.github/workflows/test-docs.yaml +++ b/.github/workflows/test-docs.yaml @@ -37,5 +37,5 @@ jobs: run: | mkdir build cd build - cmake .. -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` -DARB_USE_BUNDLED_LIBS=ON + cmake .. -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` make html diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index 3e91b9d90f..7e66c8467b 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -153,7 +153,7 @@ jobs: export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" mkdir build cd build - cmake .. -GNinja -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=${{ matrix.config.simd }} -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.config.mpi }} -DARB_USE_BUNDLED_LIBS=ON -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON + cmake .. -GNinja -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=${{ matrix.config.simd }} -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.config.mpi }} -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON ninja -j4 tests examples pyarb cd - - if: ${{ matrix.variant == 'shared' }} @@ -162,7 +162,7 @@ jobs: export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" mkdir build cd build - cmake .. -GNinja -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=${{ matrix.config.simd }} -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.config.mpi }} -DARB_USE_BUNDLED_LIBS=ON -DARB_WITH_ASSERTIONS=ON -DBUILD_SHARED_LIBS=ON -DARB_WITH_PROFILING=ON + cmake .. -GNinja -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=${{ matrix.config.simd }} -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.config.mpi }} -DARB_WITH_ASSERTIONS=ON -DBUILD_SHARED_LIBS=ON -DARB_WITH_PROFILING=ON ninja -j4 tests examples pyarb cd - - name: Install arbor diff --git a/.github/workflows/test-pip.yml b/.github/workflows/test-pip.yml index d42ea1a0d3..833805e396 100644 --- a/.github/workflows/test-pip.yml +++ b/.github/workflows/test-pip.yml @@ -39,7 +39,7 @@ jobs: - name: Build and install Arbor using pip + build flags run: | source ~/env/bin/activate - CMAKE_ARGS="-DARB_VECTORIZE=ON -DARB_ARCH=native" pip install . + CMAKE_ARGS="-DARB_VECTORIZE=ON -DARB_ARCH=native" pip -vvv install . - name: Check that build flags match run: | source ~/env/bin/activate diff --git a/.gitmodules b/.gitmodules index 5ab557ea13..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,31 +0,0 @@ -[submodule "google-benchmark"] - path = ext/google-benchmark - url = https://github.com/google/benchmark.git - branch = main -[submodule "ext/fmt"] - path = ext/fmt - url = https://github.com/fmtlib/fmt.git - branch = master -[submodule "ext/random123"] - path = ext/random123 - url = https://github.com/DEShawResearch/random123.git - branch = main -[submodule "ext/json"] - path = ext/json - url = https://github.com/nlohmann/json.git - branch = master -[submodule "ext/pybind11"] - path = ext/pybind11 - url = https://github.com/pybind/pybind11.git - branch = master -[submodule "ext/googletest"] - path = ext/googletest - url = https://github.com/google/googletest.git - branch = main -[submodule "ext/pugixml"] - path = ext/pugixml - url = https://github.com/zeux/pugixml.git - branch = master -[submodule "ext/units"] - path = ext/units - url = https://github.com/LLNL/units.git diff --git a/.gitpod.yml b/.gitpod.yml index 407f086f36..19571c5163 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -11,7 +11,7 @@ tasks: mkdir build cmake -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -H/workspace/arbor -B/workspace/arbor/build \ -DCMAKE_INSTALL_PREFIX:STRING=/workspace/arbor/install -DCMAKE_BUILD_TYPE:STRING=Debug -G "Unix Makefiles" \ - -DARB_WITH_PYTHON:BOOL=ON -DARB_USE_BUNDLED_LIBS:BOOL=ON + -DARB_WITH_PYTHON:BOOL=ON ln -s /workspace/arbor/build/compile_commands.json /workspace/arbor export PYTHONPATH=/workspace/arbor/install/lib/python3.7/site-packages prebuild: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 05dab8a457..fb26e54b33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,23 @@ cmake_minimum_required(VERSION 3.19) include(CMakeDependentOption) include(CheckIPOSupported) +# Usually we don't want to hear those +set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ON CACHE INTERNAL "" FORCE) + # Make CUDA support throw errors if architectures remain unclear cmake_policy(SET CMP0104 NEW) +# Set release as the default build type (CMake default is debug.) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "debug" "release") +endif() + +set(CPM_USE_LOCAL_PACKAGES ON) +include(cmake/CPM.cmake) + file(READ VERSION FULL_VERSION_STRING) string(STRIP "${FULL_VERSION_STRING}" FULL_VERSION_STRING) string(REGEX MATCH "^[0-9]+(\\.[0-9]+)?(\\.[0-9]+)?(\\.[0-9]+)?" numeric_version "${FULL_VERSION_STRING}") @@ -22,7 +36,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) check_ipo_supported(RESULT HAVE_LTO OUTPUT ERR_LTO) if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) if(HAVE_LTO) - message (STATUS "LTO support found, enabling") + message (VERBOSE "LTO support found, enabling") set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) else() message(STATUS "No LTO: ${ERR_LTO}") @@ -33,6 +47,7 @@ endif() # redirected from the terminal (e.g. when using ninja or a pager). option(ARBDEV_COLOR "Always produce ANSI-colored output (GNU/Clang only)." OFF) +mark_as_advanced(FORCE ARBDEV_COLOR) #---------------------------------------------------------- # Configure-time build options for Arbor: @@ -56,13 +71,20 @@ option(ARB_VECTORIZE "use explicit SIMD code in generated mechanisms" OFF) option(ARB_USE_HWLOC "request support for thread pinning via HWLOC" OFF) mark_as_advanced(ARB_USE_HWLOC) +# Build tests and benchmarks, docs + +option(BUILD_TESTING "build tests and benchmarks" ON) +option(BUILD_DOCUMENTATION "build documentation" ON) + # Use externally built modcc? set(ARB_MODCC "" CACHE STRING "path to external modcc NMODL compiler") +mark_as_advanced(FORCE ARB_MODCC) # Use libunwind to generate stack traces on errors? option(ARB_BACKTRACE "Enable stacktraces on assertion and exceptions (requires Boost)." OFF) +mark_as_advanced(FORCE ARB_BACKTRACE) # Specify GPU build type @@ -75,18 +97,13 @@ cmake_dependent_option(ARB_USE_GPU_RNG "Use GPU generated random numbers (only cuda, not bitwise equal to CPU version)" OFF "ARB_USE_GPU_DEP" OFF) -# Use bundled 3rd party libraries -option(ARB_USE_BUNDLED_LIBS "Use bundled 3rd party libraries" OFF) - -# Use pybind11-stubgen to make type stubs. -option(ARB_BUILD_PYTHON_STUBS "Use pybind11-stubgen to build type stubs." ON) - # Optional additional CXX Flags used for all code that will run on the target # CPU architecture. Recorded in installed target, for downstream dependencies # to use. # Useful, for example, when a user wants to compile with target-specific -# optimization flags. +# optimization flag.spr set(ARB_CXX_FLAGS_TARGET "" CACHE STRING "Optional additional flags for compilation") +mark_as_advanced(FORCE ARB_CXX_FLAGS_TARGET) #---------------------------------------------------------- # Debug support @@ -102,7 +119,10 @@ mark_as_advanced(ARB_CAT_VERBOSE) option(ARB_WITH_MPI "build with MPI support" OFF) -option(ARB_WITH_PROFILING "use built-in profiling" OFF) +option(ARB_WITH_PROFILING "enable Tracy profiling" OFF) +cmake_dependent_option(ARB_WITH_STACK_PROFILING "enable stack collection in profiling" OFF "ARB_WITH_PROFILING" OFF) +cmake_dependent_option(ARB_WITH_MEMORY_PROFILING "enable memory in profiling" OFF "ARB_WITH_PROFILING" OFF) +mark_as_advanced(FORCE ARB_WITH_STACK_PROFILING ARB_WITH_MEMORY_PROFILING) option(ARB_WITH_ASSERTIONS "enable arb_assert() assertions in code" OFF) @@ -122,14 +142,6 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(GitSubmodule) # required for check_git_submodule include(ErrorTarget) # reguired for add_error_target -# Set release as the default build type (CMake default is debug.) - -if (NOT CMAKE_BUILD_TYPE AND NOT BUILD_TESTING) - set(CMAKE_BUILD_TYPE release CACHE STRING "Choose the type of build." FORCE) - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "debug" "release") -endif() - # Add CUDA as a language if GPU support requested. (This has to be set early so # as to enable CUDA tests in generator expressions.) if(ARB_GPU STREQUAL "cuda") @@ -199,6 +211,8 @@ set(CMAKE_CUDA_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +mark_as_advanced(FORCE CMAKE_OSX_ARCHITECTURES CMAKE_OSX_DEPLOYMENT_TARGET CMAKE_OSX_SYSROOT) + #---------------------------------------------------------- # Set up flags and dependencies: #---------------------------------------------------------- @@ -256,28 +270,11 @@ install(TARGETS arborio-public-deps EXPORT arborio-targets) install(PROGRAMS scripts/arbor-build-catalogue DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES mechanisms/BuildModules.cmake DESTINATION ${ARB_INSTALL_DATADIR}) -# External libraries in `ext` sub-directory: json, tinyopt and random123. -# Creates interface libraries `ext-tinyopt` and `ext-random123` - -cmake_dependent_option(ARB_USE_BUNDLED_FMT "Use bundled FMT lib." ON "ARB_USE_BUNDLED_LIBS" OFF) -cmake_dependent_option(ARB_USE_BUNDLED_PUGIXML "Use bundled XML lib." ON "ARB_USE_BUNDLED_LIBS" OFF) -cmake_dependent_option(ARB_USE_BUNDLED_GTEST "Use bundled GoogleTest." ON "ARB_USE_BUNDLED_LIBS" OFF) -# TODO When we get a units spack package... -#cmake_dependent_option(ARB_USE_BUNDLED_UNITS "Use bundled LLNL units." ON "ARB_USE_BUNDLED_LIBS" OFF) -set(ARB_USE_BUNDLED_UNITS ON CACHE STRING "Use bundled LLNL units.") +# Add all dependencies. -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 3.11.2 CONFIG REQUIRED) - message(STATUS "Using external JSON = ${nlohmann_json_VERSION}") -endif() - -cmake_dependent_option(ARB_USE_BUNDLED_RANDOM123 "Use bundled Random123 lib." ON "ARB_USE_BUNDLED_LIBS" OFF) -add_library(ext-random123 INTERFACE) -if(NOT ARB_USE_BUNDLED_RANDOM123) - find_package(Random123 REQUIRED) - target_include_directories(ext-random123 INTERFACE ${RANDOM123_INCLUDE_DIR}) -endif() +# First make ourselves less chatty +set(_saved_CMAKE_MESSAGE_LOG_LEVEL ${CMAKE_MESSAGE_LOG_LEVEL}) +set(CMAKE_MESSAGE_LOG_LEVEL WARNING) # in the event we can find hwloc, just add it find_package(hwloc QUIET) @@ -295,21 +292,115 @@ else() message(SEND_ERROR "Requested support for hwloc, but CMake couldn't find it.") endif() endif() +install(TARGETS ext-hwloc EXPORT arbor-targets) -add_library(ext-units INTERFACE) -if(ARB_USE_BUNDLED_UNITS) - target_link_libraries(ext-units INTERFACE units::units) +CPMAddPackage(NAME json + GITHUB_REPOSITORY nlohmann/json + VERSION 3.11.2 + OPTIONS "CMAKE_SUPPRESS_DEVELOPER_WARNINGS ON") +install(TARGETS nlohmann_json EXPORT arbor-targets) + +add_library(ext-random123 INTERFACE) +CPMAddPackage(NAME random123 + DOWNLOAD_ONLY YES + GITHUB_REPOSITORY DEShawResearch/random123 + VERSION 1.14.0) +if(random123_ADDED) + target_include_directories(ext-random123 INTERFACE $) else() - message(FATAL, "TODO: At the time of Arbor 0.10.0 there is no Spack package") + find_package(Random123 REQUIRED) + target_include_directories(ext-random123 INTERFACE ${RANDOM123_INCLUDE_DIR}) endif() +install(TARGETS ext-random123 EXPORT arbor-targets) +if (ARB_WITH_PYTHON) + CPMAddPackage(NAME pybind11 + GITHUB_REPOSITORY pybind/pybind11 + VERSION 2.10.1 + OPTIONS "PYBIND11_CPP_STANDARD -std=c++17") + # required for find_python_module + include(FindPythonModule) +endif() -add_subdirectory(ext) -install(TARGETS ext-hwloc EXPORT arbor-targets) -install(TARGETS ext-random123 EXPORT arbor-targets) +CPMAddPackage(NAME pugixml + GITHUB_REPOSITORY zeux/pugixml + VERSION 1.13 + DOWNLOAD_ONLY YES) +add_library(ext-pugixml INTERFACE) +if(pugixml_ADDED) + target_compile_definitions(ext-pugixml INTERFACE PUGIXML_HEADER_ONLY) + target_include_directories(ext-pugixml INTERFACE $) +else() + find_package(pugixml REQUIRED) + target_link_libraries(ext-pugixml INTERFACE pugixml::pugixml) +endif() +install(TARGETS ext-pugixml EXPORT arbor-targets) + +CPMAddPackage(NAME fmt + GITHUB_REPOSITORY fmtlib/fmt + VERSION 10.0.0 + GIT_TAG 10.0.0) + +if (BUILD_TESTING) + CPMAddPackage(NAME benchmark + GITHUB_REPOSITORY google/benchmark + VERSION 1.8.3 + OPTIONS "BENCHMARK_ENABLE_TESTING OFF" "CMAKE_BUILD_TYPE release" "BUILD_SHARED_LIBS OFF") + CPMAddPackage(NAME googletest + GITHUB_REPOSITORY google/googletest + GIT_TAG release-1.12.1 + VERSION 1.12.1 + OPTIONS "INSTALL_GTEST OFF" "BUILD_GMOCK OFF") +endif() + +CPMAddPackage(NAME units + GITHUB_REPOSITORY llnl/units + VERSION 0.9.1 + OPTIONS "CMAKE_PROJECT_NAME UNITS" + "UNITS_INSTALL ON" + "UNITS_BUILD_STATIC_LIBRARY ON" + "UNITS_ENABLE_TESTS OFF" + "UNITS_BUILD_CONVERTER_APP OFF" + "UNITS_BUILD_WEBSERVER OFF") +add_library(ext-units INTERFACE) +if(units_ADDED) + target_link_libraries(ext-units INTERFACE units::units) +else() + find_package(units REQUIRED) + target_link_libraries(ext-units INTERFACE units::units) +endif() target_link_libraries(arbor-public-deps INTERFACE ext-units) install(TARGETS ext-units EXPORT arbor-targets) -install(TARGETS units compile_flags_target EXPORT arbor-targets) + +CPMAddPackage(NAME tinyopt + GITHUB_REPOSITORY halfflat/tinyopt + GIT_TAG 7e6d707d49c6cb4be27ebd253856be65293288df + DOWNLOAD_ONLY YES) + +add_library(ext-tinyopt INTERFACE) +if(tinyopt_ADDED) + target_include_directories(ext-tinyopt INTERFACE $) +else() + message(FATAL_ERROR "Could not obtain tinyopt.") +endif() + +# hide all internal vars +mark_as_advanced(FORCE benchmark_DIR BENCHMARK_BUILD_32_BITS BENCHMARK_DOWNLOAD_DEPENDENCIES BENCHMARK_ENABLE_ASSEMBLY_TESTS BENCHMARK_ENABLE_DOXYGEN BENCHMARK_ENABLE_EXCEPTIONS BENCHMARK_ENABLE_GTEST_TESTS BENCHMARK_ENABLE_INSTALL BENCHMARK_ENABLE_LIBPFM BENCHMARK_ENABLE_LTO BENCHMARK_ENABLE_WERROR BENCHMARK_FORCE_WERROR BENCHMARK_INSTALL_DOCS BENCHMARK_USE_BUNDLED_GTEST BENCHMARK_USE_LIBCXX) +mark_as_advanced(FORCE googletest_DIR BUILD_GMOCK) +mark_as_advanced(FORCE json_DIR JSON_CI JSON_BuildTests JSON_Diagnostics JSON_DisableEnumSerialization JSON_GlobalUDLs JSON_ImplicitConversions JSON_Install JSON_LegacyDiscardedValueComparison JSON_MultipleHeaders JSON_SystemInclude) +mark_as_advanced(FORCE RANDOM123_INCLUDE_DIR) +mark_as_advanced(FORCE pybind11_DIR PYBIND11_PYTHONLIBS_OVERWRITE PYBIND11_PYTHON_VERSION PYBIND11_FINDPYTHON PYBIND11_INSTALL PYBIND11_INTERNALS_VERSION PYBIND11_NOPYTHON PYBIND11_SIMPLE_GIL_MANAGEMENT PYBIND11_TEST) +mark_as_advanced(FORCE pugixml_DIR) +mark_as_advanced(FORCE fmt_DIR) +mark_as_advanced(FORCE units_DIR UNITS_BUILD_OBJECT_LIBRARY UNITS_BUILD_SHARED_LIBRARY UNITS_HEADER_ONLY UNITS_NAMESPACE) +mark_as_advanced(FORCE tinyopt_DIR) +mark_as_advanced(FORCE CXXFEATURECHECK_DEBUG) +mark_as_advanced(FORCE CPM_DONT_CREATE_PACKAGE_LOCK CPM_DONT_UPDATE_MODULE_PATH CPM_DOWNLOAD_ALL CPM_INCLUDE_ALL_IN_PACKAGE_LOCK CPM_LOCAL_PACKAGES_ONLY CPM_SOURCE_CACHE CPM_USE_NAMED_CACHE_DIRECTORIES) +mark_as_advanced(FORCE FETCHCONTENT_BASE_DIR FETCHCONTENT_FULLY_DISCONNECTED FETCHCONTENT_QUIET FETCHCONTENT_SOURCE_DIR_BENCHMARK FETCHCONTENT_SOURCE_DIR_GOOGLETEST FETCHCONTENT_SOURCE_DIR_JSON FETCHCONTENT_SOURCE_DIR_PYBIND11 FETCHCONTENT_SOURCE_DIR_RANDOM123 FETCHCONTENT_SOURCE_DIR_TINYOPT FETCHCONTENT_SOURCE_DIR_UNITS FETCHCONTENT_UPDATES_DISCONNECTED FETCHCONTENT_UPDATES_DISCONNECTED_BENCHMARK FETCHCONTENT_UPDATES_DISCONNECTED_GOOGLETEST FETCHCONTENT_UPDATES_DISCONNECTED_JSON FETCHCONTENT_UPDATES_DISCONNECTED_PYBIND11 FETCHCONTENT_UPDATES_DISCONNECTED_RANDOM123 FETCHCONTENT_UPDATES_DISCONNECTED_TINYOPT FETCHCONTENT_UPDATES_DISCONNECTED_UNITS) + + +# Restore chattyness +set(CMAKE_MESSAGE_LOG_LEVEL ${_saved_CMAKE_MESSAGE_LOG_LEVEL}) # Keep track of packages we need to add to the generated CMake config # file for arbor. @@ -339,6 +430,7 @@ endif() # Add SVE compiler flags if detected/desired set(ARB_SVE_WIDTH "auto" CACHE STRING "Default SVE vector length in bits. Default: auto (detection during configure time).") mark_as_advanced(ARB_SVE_WIDTH) + if (ARB_VECTORIZE) if (ARB_SVE_WIDTH STREQUAL "auto") get_sve_length(ARB_HAS_SVE ARB_SVE_BITS) @@ -390,14 +482,11 @@ if(DEFINED 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) - if(DEFINED ENV{CIBUILDWHEEL} AND (UNIX AND NOT APPLE)) find_package(Python3 ${arb_py_version} COMPONENTS Interpreter Development.Module REQUIRED) else() find_package(Python3 ${arb_py_version} COMPONENTS Interpreter Development REQUIRED) endif() - else() # If not building the Python module, the interpreter is still required # to build some targets, e.g. when building the documentation. @@ -406,7 +495,7 @@ endif() if(${Python3_FOUND}) set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}") - message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}") + message(VERBOSE "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}") endif() # Threading model @@ -424,12 +513,8 @@ if(ARB_WITH_MPI) find_package(MPI REQUIRED CXX) target_compile_definitions(arbor-config-defs INTERFACE ARB_HAVE_MPI) - # target_compile_definitions(MPI::MPI_CXX INTERFACE MPICH_SKIP_MPICXX=1 OMPI_SKIP_MPICXX=1) - # target_link_libraries(arbor-public-deps INTERFACE MPI::MPI_CXX) - - # CMake 3.9 does not allow us to add definitions to an import target. - # so wrap MPI::MPI_CXX in an interface library 'mpi-wrap' instead. - + # CMake 3.9 does not allow us to add definitions to an import target. so + # wrap MPI::MPI_CXX in an interface library 'mpi-wrap' instead. add_library(mpi-wrap INTERFACE) target_link_libraries(mpi-wrap INTERFACE MPI::MPI_CXX) target_compile_definitions(mpi-wrap INTERFACE MPICH_SKIP_MPICXX=1 OMPI_SKIP_MPICXX=1) @@ -574,13 +659,17 @@ add_subdirectory(arborenv) add_subdirectory(arborio) # unit, unit-mpi, unit-local, unit-modcc -add_subdirectory(test) +if (BUILD_TESTING) + add_subdirectory(test) +endif() # self contained examples: add_subdirectory(example) # html: -add_subdirectory(doc) +if (BUILD_DOCUMENTATION) + add_subdirectory(doc) +endif() # python interface: if(ARB_WITH_PYTHON) diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt index 1b905d2932..29f5f32e6a 100644 --- a/arbor/CMakeLists.txt +++ b/arbor/CMakeLists.txt @@ -124,7 +124,10 @@ install(TARGETS arbor-private-headers EXPORT arbor-targets) # variable, build_all_mods target. Note: CMake source file properties are # directory-local. +set(_saved_CMAKE_MESSAGE_LOG_LEVEL ${CMAKE_MESSAGE_LOG_LEVEL}) +set(CMAKE_MESSAGE_LOG_LEVEL WARNING) add_subdirectory(../mechanisms "${CMAKE_BINARY_DIR}/mechanisms") +set(CMAKE_MESSAGE_LOG_LEVEL ${_saved_CMAKE_MESSAGE_LOG_LEVEL}) set_source_files_properties(${arbor-builtin-mechanisms} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES GENERATED TRUE) if(ARB_WITH_CUDA_CLANG OR ARB_WITH_HIP_CLANG) diff --git a/arbor/include/CMakeLists.txt b/arbor/include/CMakeLists.txt index bf912d3c1c..d990813d7d 100644 --- a/arbor/include/CMakeLists.txt +++ b/arbor/include/CMakeLists.txt @@ -44,10 +44,6 @@ if(ARB_WITH_PROFILING) # define ARB_PROFILE_ENABLED in version.hpp list(APPEND arb_features PROFILE) endif() -if(ARB_USE_BUNDLED_LIBS) - # define ARB_BUNDLED_ENABLED in version.hpp - list(APPEND arb_features BUNDLED) -endif() if(ARB_VECTORIZE) list(APPEND arb_features VECTORIZE) endif() diff --git a/arborio/CMakeLists.txt b/arborio/CMakeLists.txt index 6016a3417d..4677dfa600 100644 --- a/arborio/CMakeLists.txt +++ b/arborio/CMakeLists.txt @@ -12,14 +12,7 @@ set(arborio-sources add_library(arborio ${arborio-sources}) -if (ARB_USE_BUNDLED_PUGIXML) - target_include_directories(arborio PRIVATE $) - target_compile_definitions(arborio PRIVATE PUGIXML_HEADER_ONLY) -else() - find_package(pugixml REQUIRED) - target_link_libraries(arborio PUBLIC pugixml::pugixml) -endif() - +target_link_libraries(arborio PUBLIC ext-pugixml) add_library(arborio-public-headers INTERFACE) add_library(arborio-private-headers INTERFACE) diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000000..d0fd0e8ea4 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.39.0) +set(CPM_HASH_SUM "66639bcac9dd2907b2918de466783554c1334446b9874e90d38e3778d404c2ef") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/arbor-config.cmake.in b/cmake/arbor-config.cmake.in index c9edecdc53..592571d72e 100644 --- a/cmake/arbor-config.cmake.in +++ b/cmake/arbor-config.cmake.in @@ -2,10 +2,17 @@ include(CMakeFindDependencyMacro) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}") +# Work around the possibility of being installed in a pip-venv... +get_filename_component(prefix ${CMAKE_CURRENT_LIST_DIR} DIRECTORY) +set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "${prefix}") + foreach(dep @arbor_export_dependencies@) find_dependency(${dep}) endforeach() +# TODO Explicitely needed... until I find a way out. +find_dependency(units) + include("${CMAKE_CURRENT_LIST_DIR}/arbor-targets.cmake") set(_supported_components @arbor_supported_components@) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 0dc25850f1..7d5da0e682 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -6,6 +6,8 @@ add_target_if(SPHINX_FOUND "Finding Sphinx" "Sphinx must be installed to build documentation") +mark_as_advanced(FORCE SPHINX_EXECUTABLE) + find_python_module(svgwrite) add_target_if(HAVE_SVGWRITE ext-svgwrite diff --git a/doc/install/build_install.rst b/doc/install/build_install.rst index cd6850df3b..6d4ad17c51 100644 --- a/doc/install/build_install.rst +++ b/doc/install/build_install.rst @@ -169,23 +169,6 @@ will need to install `Sphinx `_. .. _install-downloading: -External dependencies -~~~~~~~~~~~~~~~~~~~~~ - -For the (optional) python bindings Arbor uses `pybind11 `_, and -JSON parsing is faciliated through `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 `_ is retrieved from a Git submodule (see below) -and `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. - .. _building: Building and installing Arbor @@ -211,7 +194,7 @@ For more detailed build configuration options, see the `quick start /random123/include) - message(STATUS "Using Random123 submodule: ${CMAKE_CURRENT_SOURCE_DIR}>/random123") -endif() - -# tinyopt command line parsing libary (header-only). - -add_library(ext-tinyopt INTERFACE) -target_include_directories(ext-tinyopt INTERFACE tinyopt/include) - - -# functionality for adding external projects - -include(ExternalProject) -# Arguments: -# NAME: project folder -# INTERFACE_NAME: generated cmake target to link against -# LIB: library name -# CMAKE_ARGS: additional cmake arguments -function(add_external_cmake_project) - # handle named arguments: fills variables EP_NAME, EP_INTERFACE_NAME, EP_LIB, and EP_CMAKE_ARGS - set(options OPTIONAL) - set(oneValueArgs NAME INTERFACE_NAME LIB) - set(multiValueArgs CMAKE_ARGS) - cmake_parse_arguments(EP "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - set(EP_BUILD "${EP_NAME}-build") - set(EP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${EP_NAME}") - set(EP_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/${EP_NAME}") - - set(EP_INTERFACE_INCLUDE_DIR "${EP_INSTALL_DIR}/include") - set(EP_INTERFACE_LIB_NAME "${EP_INSTALL_DIR}/lib/${EP_LIB}") - - check_git_submodule(${EP_NAME}_sub ${EP_NAME}) - if(${EP_NAME}_sub_avail) - # populate cmake arguments - set(EP_ALL_CMAKE_ARGS - "-DCMAKE_INSTALL_PREFIX=${EP_INSTALL_DIR}" - "-DCMAKE_INSTALL_LIBDIR=${EP_INSTALL_DIR}/lib" - "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") - list(APPEND EP_ALL_CMAKE_ARGS ${EP_CMAKE_ARGS}) - - # add external project - ExternalProject_Add(${EP_BUILD} - # Add dummy DOWNLOAD_COMMAND to stop ExternalProject_Add terminating CMake if the - # git submodule had not been udpated. - DOWNLOAD_COMMAND "${CMAKE_COMMAND}" -E echo "Warning: ${EP_SOURCE_DIR} empty or missing." - BUILD_BYPRODUCTS "${EP_INTERFACE_LIB_NAME}" - SOURCE_DIR "${EP_SOURCE_DIR}" - CMAKE_ARGS "${EP_ALL_CMAKE_ARGS}" - INSTALL_DIR "${EP_INSTALL_DIR}" - ) - set_target_properties(${EP_BUILD} PROPERTIES EXCLUDE_FROM_ALL TRUE) - - # make top level interface library which links to external project - add_library(${EP_INTERFACE_NAME} INTERFACE) - add_dependencies(${EP_INTERFACE_NAME} ${EP_BUILD}) - target_include_directories(${EP_INTERFACE_NAME} INTERFACE ${EP_INTERFACE_INCLUDE_DIR}) - target_link_libraries(${EP_INTERFACE_NAME} INTERFACE ${EP_INTERFACE_LIB_NAME}) - else() - add_error_target(${EP_BUILD} - "Building ${EP_NAME} library" - "The git submodule for ${EP_NAME} is not available") - endif() -endfunction() - -# Google benchmark for microbenchmarks: - -# Set up google benchmark as an external project. -add_external_cmake_project( - NAME google-benchmark - INTERFACE_NAME ext-benchmark - LIB libbenchmark.a - CMAKE_ARGS - "-DCMAKE_BUILD_TYPE=release" - "-DBUILD_SHARED_LIBS=OFF") - -# Google Test framework: - -if (ARB_USE_BUNDLED_GTEST) - # Set up google test as an external project. - add_external_cmake_project( - NAME googletest - INTERFACE_NAME ext-gtest - LIB libgtest.a - CMAKE_ARGS - "-DCMAKE_BUILD_TYPE=release" - "-DBUILD_SHARED_LIBS=OFF" - "-DBUILD_GMOCK=OFF") - - # on some systems we need link explicitly against threads - if (TARGET ext-gtest) - find_package (Threads) - target_link_libraries(ext-gtest INTERFACE Threads::Threads) - endif() -else() - # Use system provided google test - find_package(GTest) - add_library(ext-gtest INTERFACE) - if (${CMAKE_VERSION} VERSION_LESS "3.20.0") - target_link_libraries(ext-gtest INTERFACE GTest::GTest GTest::Main) - else() - target_link_libraries(ext-gtest INTERFACE GTest::gtest GTest::gtest_main) - endif() -endif() - -if (ARB_USE_BUNDLED_UNITS) - set(UNITS_ENABLE_TESTS OFF CACHE INTERNAL "") - set(UNITS_BUILD_STATIC_LIBRARY ON CACHE INTERNAL "") - set(UNITS_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "") - set(UNITS_BUILD_CONVERTER_APP OFF CACHE INTERNAL "") - set(UNITS_BUILD_WEBSERVER OFF CACHE INTERNAL "") - set(UNITS_INSTALL ON CACHE INTERNAL "") - # set(UNITS_NAMESPACE "llnl::units" CACHE INTERNAL "") - - add_subdirectory("${PROJECT_SOURCE_DIR}/ext/units" "${PROJECT_BINARY_DIR}/ext/units") - - mark_as_advanced(UNITS_BUILD_OBJECT_LIBRARY) - mark_as_advanced(UNITS_HEADER_ONLY) - mark_as_advanced(UNITS_NAMESPACE) -endif() diff --git a/ext/fmt b/ext/fmt deleted file mode 160000 index e69e5f977d..0000000000 --- a/ext/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e69e5f977d458f2650bb346dadf2ad30c5320281 diff --git a/ext/google-benchmark b/ext/google-benchmark deleted file mode 160000 index cb8a0cc10f..0000000000 --- a/ext/google-benchmark +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cb8a0cc10f8b634fd554251ae086da522b58f50e diff --git a/ext/googletest b/ext/googletest deleted file mode 160000 index 58d77fa807..0000000000 --- a/ext/googletest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 58d77fa8070e8cec2dc1ed015d66b454c8d78850 diff --git a/ext/json b/ext/json deleted file mode 160000 index 9cca280a4d..0000000000 --- a/ext/json +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 diff --git a/ext/pugixml b/ext/pugixml deleted file mode 160000 index db78afc2b7..0000000000 --- a/ext/pugixml +++ /dev/null @@ -1 +0,0 @@ -Subproject commit db78afc2b7d8f043b4bc6b185635d949ea2ed2a8 diff --git a/ext/pybind11 b/ext/pybind11 deleted file mode 160000 index 8a099e44b3..0000000000 --- a/ext/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8a099e44b3d5f85b20f05828d919d2332a8de841 diff --git a/ext/random123 b/ext/random123 deleted file mode 160000 index 726a093cd9..0000000000 --- a/ext/random123 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 726a093cd9a73f3ec3c8d7a70ff10ed8efec8d13 diff --git a/ext/sup/include/sup/export.hpp b/ext/sup/include/sup/export.hpp deleted file mode 100644 index a446e3daee..0000000000 --- a/ext/sup/include/sup/export.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -//#ifndef ARB_EXPORT_DEBUG -//# define ARB_EXPORT_DEBUG -//#endif - -#include - -/* library build type (ARB_SUP_STATIC_LIBRARY/ARB_SUP_SHARED_LIBRARY) */ -#define ARB_SUP_STATIC_LIBRARY - -#ifndef ARB_SUP_EXPORTS -# if defined(arbor_sup_EXPORTS) - /* we are building arbor-sup dynamically */ -# ifdef ARB_EXPORT_DEBUG -# pragma message "we are building arbor-sup dynamically" -# endif -# define ARB_SUP_API ARB_SYMBOL_EXPORT -# elif defined(arbor_sup_EXPORTS_STATIC) - /* we are building arbor-sup statically */ -# ifdef ARB_EXPORT_DEBUG -# pragma message "we are building arbor-sup statically" -# endif -# define ARB_SUP_API -# else - /* we are using the library arbor-sup */ -# if defined(ARB_SUP_SHARED_LIBRARY) - /* we are importing arbor-sup dynamically */ -# ifdef ARB_EXPORT_DEBUG -# pragma message "we are importing arbor-sup dynamically" -# endif -# define ARB_SUP_API ARB_SYMBOL_IMPORT -# else - /* we are importing arbor-sup statically */ -# ifdef ARB_EXPORT_DEBUG -# pragma message "we are importing arbor-sup statically" -# endif -# define ARB_SUP_API -# endif -# endif -#endif diff --git a/ext/tinyopt/LICENSE b/ext/tinyopt/LICENSE deleted file mode 100644 index a6e2baebad..0000000000 --- a/ext/tinyopt/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2019, Stuart Yates -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ext/tinyopt/README.md b/ext/tinyopt/README.md deleted file mode 100644 index a4ee15dbb4..0000000000 --- a/ext/tinyopt/README.md +++ /dev/null @@ -1,576 +0,0 @@ -Smaller, better? option parsing. - -## Goals - -This project constitutes yet another header-only option parsing library for C++. - -Design goals: - -* Header only. -* C++14 and C++17 compatible. -* Fairly short and easy to customize. - -Features: - -* Support 'short' and 'long' style options: `-v 3` and `--value=3`. -* Support 'compact' bunching of options: `-abc 3` vs `-a -b -c 3`. -* Save and restore options and arguments in a shell-compatible format, - allowing e.g. `` program `cat previous-options` --foo=bar ``. -* Options can be interpreted modally. - -Non-features: - -* _Does not support multibyte encodings with shift sequences or wide character streams._ - This is due to laziness. But it does try to at least not break UTF-8. -* _Does not automatically generate help/usage text._ - What constitutes good help output is too specific to any given program. -* _Does not support optional or multiple arguments to an option._ - This is mainly due to problems of ambiguous parsing, though in a pinch this can - be set up through the use of modal option parsing (see _Filters and Modals_ below). - -The library actually provides two interfaces: -1. One can iterate through the command line arguments explicitly, testing them - with `to::parse`. This precludes the use of compact-style options or modal parsing, - but gives more control to the user code. -2. Or one can make a table of `to::option` specifications, and pass them to - `to::run`, which will handle all the parsing itself. - -## Simple examples - -More examples are found in the `ex/` subdirectory. - -Simple interface (`to::parse`) code for parsing options three options, one numeric, -one a keyword from a table, and one just a flag. -``` -#include -#include -#include - -const char* usage_str = - "[OPTION]...\n" - "\n" - " -n, --number=N Specify N\n" - " -f, --function=FUNC Perform FUNC, which is one of: one, two\n" - " -h, --help Display usage information and exit\n"; - -int main(int argc, char** argv) { - try { - int n = 1, fn = 0; - bool help = false; - - std::pair functions[] = { - { "one", 1 }, { "two", 2 } - }; - - for (auto arg = argv+1; *arg; ) { - bool ok = - help << to::parse(arg, "-h", "--help") || - n << to::parse(arg, "-n", "--number") || - fn << to::parse(arg, to::keywords(functions), "-f", "--function"); - - if (!ok) throw to::option_error("unrecognized argument", *arg); - } - - if (help) { - to::usage(argv[0], usage_str); - return 0; - } - - if (n<1) throw to::option_error("N must be at least 1"); - if (fn<1) throw to::option_error("Require FUNC"); - - // Do things with arguments: - - for (int i = 0; i -#include -#include - -const char* usage_str = - "[OPTION]...\n" - "\n" - " -n, --number=N Specify N\n" - " -f, --function=FUNC Perform FUNC, which is one of: one, two\n" - " -h, --help Display usage information and exit\n"; - -int main(int argc, char** argv) { - try { - int n = 1, fn = 0; - - std::pair functions[] = { - { "one", 1 }, { "two", 2 } - }; - - auto help = [argv0 = argv[0]] { to::usage(argv0, usage_str); }; - - to::option opts[] = { - { n, "-n", "--number" }, - { {fn, to::keywords(functions)}, "-f", "--function", to::mandatory }, - { to::action(help), to::flag, to::exit, "-h", "--help" } - }; - - if (!to::run(opts, argc, argv+1)) return 0; - - if (argv[1]) throw to::option_error("unrecogonized argument", argv[1]); - if (n<1) throw to::option_error("N must be at least 1"); - - // Do things with arguments: - - for (int i = 0; i struct maybe` - -`maybe` is a simple work-alike for (some of) the C++17 `std::optional` class. -A default constructed `maybe` has no value, and evaluates to false in a `bool` -context; it will otherwise evaluate to true. - -If `m` is an object of type `maybe`, then `*m` evaluates to the contained value -if defined. `m.value()` does the same, but will throw an exception of type -`std::invalid_argument` if `m` does not contain a value. - -The special value `nothing` is implicitly convertible to an empty `maybe` for any `V`. -The expression `just(v)` function returns a `maybe` holding the value `v`. - -As a special case, `maybe` simply maintains a has-value state; it will return -true in a `bool` context if has been initialized or assigned with any `maybe` -that contains a value, or by any other value that is not `nothing`. `something` -is a pre-defined non-empty value of type `maybe`. - -`maybe` values support basic monadic-like functionality via `operator<<`. -* If `x` is an lvalue and `m` is of type `maybe`, then - `x << m` has type `maybe` (`V` is the type of `x=*m`) and assigns `m.value()` to `x` - if `m` has a value. In the case that `U` is `void`, then the value of `m` is taken - to be `true`. -* If `f` is a function or function object with signature `V f(U)`, and `m` is of type `maybe`, then - `f << m` has type `maybe` and contains `f(*m)` if `m` has a value. -* if `f` has signature `V f()`, and `m` is of type `maybe` or `maybe`, then - `f << m` has type `maybe` and contains `f()` if `m` has a value. - -#### `option_error` - -An exception class derived from `std::runtime_error`. It has two constructors: -* `option_error(const std::string&)` simply sets the what string to the argument. -* `option_error(const std::string& message, const std::string& arg)` sets the what string to - the value of `arg+": "+mesage`. - -The option parsers can throw exceptions derived from `option_error`, namely: -`option_parse_error`, `missing_mandatory_option`, and `missing_argument`. - -#### `usage(const char *argv0, const std::string& usagemsg, const std::string& prefix = "Usage: ")` - -Extract a program name from `argv0` (everything after the last '/' if present) and -print a message to standard out in the form "Usage: \n". -An alternative prefix to "Usage: " can be supplied optionally. - -#### `usage_error(const char *argv0, const std::string& usagemsg, const std::string& error, const std::string& prefix = "Usage: ")` - -Extract a program name from `argv0` (everything after the last '/' if present) and -print a message to standard error in the form -`: \nUsage: \n`. -An alternative prefix to "Usage: " can be supplied optionally. - -### Parsers - -A parser is a function or functional object with signature `maybe (const char*)` -for some type `X`. They are used to try to convert a C-string argument into a value. - -If no explicit parser is given to a the`parse` function or to an `option` specification, -the default parser `default_parser` is used, which will use `std::istream::operator>>` -to read the supplied argument. - -Tinyopt supplies additional parsers: - -* `keyword_parser` - - Constructed from a table of key-value pairs, the `keyword_parser` parser - will return the first value found in the table with matching key, or `nothing` - if there is no match. - - The `keywords(pairs)` function constructs a `keyword_parser` object from the - collection of keyword pairs `pairs`, where each element in the collection is - a `std::pair`. The first component of each pair is used to construct the `std::string` - key in the keyword table, and the second the value. The value type `V` is deduced - from this second component. - -* `delimited_parser

` - - The delimited parser uses another parser of type `P` to parse individual - elements in a delimited sequence, and returns a `std::vector` of the - corresponding values. - - The convenience constructor `delimited(char delim = ',')` will make - a `delimited_parser` using the default parser for `V` and delimiter - `delim` (by default, a comma). - - `delimited(char delim, P&& parser)` is a convenience wrapper for - `delimited_parser

::delimited_parser(delim, parser)`. - -### Keys - -Keys are how options are specified on the command line. They consist of -a string label and a style, which is one of `key::shortfmt`, -`key::longfmt`, or `key::compact`. - -All options that take an argument will take that argument from the -next item in the argument list, and only options with a 'compact' -key can be combined together in a single argument. - -An option with a 'long' key can additionally take its argument by -following the key with an equals sign and then the argument value. - -As an example, let "-s" be a short option key, "--long" a long option key, -"-a" a compact option key for a flag, and "-b" a compact option key for -an option that takes a value. Then the follwing are equivalent ways -for specifying these options on a command line: - -``` --s 1 --long 2 -a -b 3 -``` - -``` --s 1 --long=2 -a -b3 -``` - -``` --s 1 --long=2 -ab3 -``` - -Keys can be constructed explicitly, implicitly from labels, or -from the use of string literal functions: - -* `key(std::string label, enum key::style style)`, `key(const char* label, enum key::style style)` - - Make a key with given label and style. - -* `key(std::string label)`, `key(const char* label)` - - Make a key with the given label. The style will be `key::shortfmt`, unless - the label starts with a double dash "--". This constructor is implicit. - -* `operator""_short`, `operator""_long`, `operator""_compact`. - - Make a key in the corresonding style from a string literal. - -The string literal operators are included in an inline namespace `literals` -that can be included in user code via `using namespace to::literals`. - -### Using `to::parse` - -The Tinyopt `to::parse` functions compare a single command line argument -against one or more short- or long-style options, parsing any corresponding -option argument with the default or explicitly provided parser. - -* `maybe parse(char**& argp, key k, ...)` - - Attempt to parse an option with no argument (i.e. a flag) at `argp`, given - by the option key `k` or subsequent. Returns an empty `maybe` value - if it fails to match any of the keys. - - If the match is successful, increment `argp` to point to the next argument. - -* `maybe parse(char**& argp, const P& parser, key k, ...)`
- `maybe parse(char**& argp, key k, ...)` - - Attempt to parse an option with an argument to be interpreted as type `V`, - matching the option against the key `k` or subsequent. If no `parser` is - supplied, use the default parser for the type `V` to convert the option - argument. - - If the match and value pase is successful, increment `argp` once or twice - as required to advance to the next option. - -The `parse` functions will throw `missing_argument` if no argument is found -for a non-flag option, and `option_parse_error` if the parser for an argument -returns `nothing`. - -The monadic `maybe` return values allow straightforward chaining of multiple -`to::parse` calls in a single expression; see `ex/ex1-parse.cc` for an -example. - -### Using `to::run` - -The `to::run` function hands more control to the library for option parsing. -The basic workflow is: - -1. The user code sets up a collection of `option` objects, each of which describe - a command line option or flag, and how to handle the result of parsing it. -2. This collection is passed to the `run` function, along with `argc` and `argv`. - `argv` is modified in place to remove matched options; anything remaining - can be handled by the user code. -3. The `run` function returns a saved set of matched options that can, for example, - be saved in program output for tracking processing steps, or in some file - to allow for re-execution of the code with the same arguments. - -An `option` describes one command line flag or option with an argument. It has -five components: a `sink`, that describes what to do with a successfully parsed -option; a set of `key`s, which are how the option is presented on the command line; -a sequence of `filter`s, which can limit the scope in which the option is valid; -a sequence of `modal`s, which change the modal state of the parser when the option -is matched; and finally a set of option flags that modify behaviour. - -#### Sinks - -A sink wraps a function that takes a `const char*` value, representing the -argument to an option, and returns a `bool`. A return value of `true` indicates -a successful parsing of the argument; `false` represents a parse error. - -A sink has three constructors: -* `sink(sink::action_t, Action a)` - - `action_t` is a tag type, indicating that the second argument is a functional - to be used directly as the wrapped function. `sink::action` is a value with - this type for use in this constructor. - - This constructor is used by the `action` wrapper function described below. - -* `sink(V& var, P parser)` - - Make a sink that uses the parser `P` (see above for a description of what - constitutes a tinyopt parser) to get a value of type `V`, and write that - value to `var`. - -* `sink(V& var)` - - Equivalent to `sink(var, default_parser{})` - -If an option doesn't have any associated argument, i.e. it is a flag, -the `sink` object is passed `nullptr`. - -The `action` wrapper function takes a nullary or unary function or functional -object, and optionally a parser for the function's argument. It returns a -`sink` object that applies the default or supplied parser object -and if successful, calls the function with the parsed value. - -A special action wrapper is `error(const std::string& message)`, which will -throw a `user_option_error` with the given message. - -#### Sink adaptors - -The library supplies some convenience adaptors for making `sink`s for common -situations: - -* `push_back(Container& c, P parser = P{})` - - Append parsed values to the container `c` using `Container::push_back`. - The default parser is `default_parser`. - -* `set(V& v, X value)` - - Set `v` to `value`, ignoring any argument. - -* `set(V& v)` - - Set `v` to `true`, ignoring any argument. - -* `increment(V& v, X delta)` - - Perform `v += delta`, ignoring any argument. - -* `increment(V& v)` - - Perform `++v`, ignoring any argument. - -#### Filters and modals - -Options are by default always available for consideration. The `single` -flag described below provides one simple constraint on option matching; the -modal interfaces provide a more elaborate system should it be required. - -Each option maintains a sequence of _filters_ and a sequence of _modals_. - -A _filter_ is a `filter` object (an alias for `std::function`) -that takes the current mode (an integer, by default zero) and returns `false` -if the option should not be considered for matching. Options will be ignored if -any of its filters return false. - -A _modal_ is a `modal` object (an alias for `std::functional`) -that is passed the current mode value and returns the new mode value when the -option is successfully matched and parsed. - -Filters can be made with the `when` adaptor. Given a functional object, -it will wrap it in a `filter`. Given an integer value, it will make -a `filter` that returns true only if the mode matches that value. -If `when` is given multiple arguments, it constructs a filter that is -the disjunction of filters constructed from each argument. - -`then(f)` constructs a `modal` object wrapping the functional object `f`; -`then(int v)` constructs a `modal` that just returns the value `m`. - -#### Flags - -Option behaviour can be modified by supplying `enum option_flag` values: - -* `flag` — Option takes no argument, i.e. it is a flag. -* `ephemeral` — Do not record this option in the saved data returned by `run()`. -* `single` — This option will be checked at most once, and then ignored for the remainder of option processing. -* `mandatory` — Throw an exception if this option does not appear in the command line arguments. -* `exit` — On successful parsing of this option, stop any further option processing and return `nothing` from `run()`. -* `stop` — On successful parsing of this option, stop any further option processing but return saved options as normal from `run()`. - -These enum values are all powers of two and can be combined via bitwise or `|`. - -When to use `exit`, and when to use `stop`? `exit` is intended to describe -the situation where the program should not proceed further with normal -processing; a standard use case for `exit` is for `--help` options, which -should cause the program to exit after printing help text. `stop`, on the -other hand, is used for options that indicate that no further special -argument processing should be performed; this corresponds to the common -convention of `--` on the command line indicating that all remaining -arguments should be interpreted as command line options. - -#### Specifying an option - -The `option` constructor takes a `sink` as the first argument, followed by -any number of keys, filters, modals, and flags in any order. - -An `option` may have no keys at all — these will always match an item in the -command line argument list, and that item will be passed directly to the -option's sink. - -Some example specifications: -``` - // Saves integer argument to variable 'n': - int n = 0; - to::option opt_n = { n, "-n" }; - - // Flag '-v' or '--verbose' that increases verbosity level, but is not - // kept in the returned list of saved options. - int verbosity = 0; - to::option opt_v = { to::increment(verbosity), "-v", "--verbose", to::flag, to::ephemeral }; - - // Save vector of values from one argument of comma separated values, e.g. - // -x 1,2,3,4,5: - std::vector xs; - to::option opt_x = { {xs, to::delimited()}, "-x" }; - - // Save vector of values one by one, e.g., - // -k 1 -k 2 -k 3 -k 4 -k 5 - to::option opt_k = { to::push_back(xs), "-k" }; - - // A 'help' flag that calls a help() function and stops further option processing. - to::option opt_h = { to::action(help), to::flag, to::exit, "-h", "--help" }; - - // A modal flag that sets the mode to 2, and a filtered option that only - // applies when the mode is 2. - enum mode { none = 0, list = 1, install = 2} prog_mode = none; - to::option opt_m = { to::set(prog_mode, install), "install", to::then(install) }; - to::option opt_h = { to::action(install_help), to::flag, to::exit, "-h", "--help", to::when(install) }; - - // Compact option keys using to::literals: - using namespace to::literals; - bool a = false, b = false, c = false; - to::option flags[] = { - { to::set(a), "-a"_compact, to::flag }, - { to::set(b), "-b"_compact, to::flag }, - { to::set(c), "-c"_compact, to::flag } - }; -``` - -#### Saved options - -The `run()` function (see below) returns a value of type -`maybe`. The `saved_options` object holds a record of the -successfully parsed options. It wraps a `std::vector` that has one -element per option key and argument, in the order they were matched (excluding -options with the `ephemeral` flag). - -While the contents can be inspected via methods `begin()`, `end()`, `empty()` -and `size()`, it is primarily intended to support serialization. Overloads of -`operator<<` and `operator>>` will write and read a `saved_options` object to a -`std::ostream` or `std::istream` object respectively. The serialized format -uses POSIX shell-compatible escaping with backslashes and single quotes so that -they be incorporated directly on the command line in later program invocations. - -#### Running a set of options - -A command line argument list or `saved_options` object is run against a -collection of `option` specifications with `run()`. There are five overloads, -each of which returns a `saved_options` value in normal execution or `nothing` -if an option with the `exit` flag is matched. - -In the following `Options` is any iterable collection of `option` values. - -* `maybe run(const Options& options, int& argc, char** argv)` - - Parse the items in `argv` against the options provided in the first argument. - Starting at the beginning of the `argv` list, options with keys are checked first, - in the order they appear in `options`, followed by options without keys. - - Successfully parsed options are removed from the `argv` list in-place, and - `argc` is adjusted accordingly. - -* `maybe run(const Options& options, int& argc, char** argv, const saved_options& restore)` - - As for `run(options, argc, argv)`, but first run the options against the saved command line - arguments in `restore`, and then again against `argv`. - - A mandatory option can be satisfied by the restore set or by the argv set. - -* `maybe run(const Options& options, const saved_options& restore)` - - As for `run(options, argc, argv, restore)`, but with an empty argc/argv list. - -* `maybe run(const Options& options, char** argv)` - - As for `run(options, argc, argv)`, but ignoring argc. - -* `maybe run(const Options& options, char** argv, const saved_options& restore)` - - As for `run(options, argc, argv, restore)`, but ignoring argc. - -Like the `to::parse` functions, the `run()` function can throw `missing_argument` or -`option_parse_error`. In addition, it will throw `missing_mandatory_option` if an option -marked with `mandatory` is not found during command line argument parsing. - -Note that the arguments in `argv` are checked from the beginning; when calling `run` from within, -e.g the main function `int main(int argc, char** argv)`, one should pass `argv+1` to `run` -so as to avoid including the program name in `argv[0]`. diff --git a/ext/tinyopt/include/tinyopt/tinyopt.h b/ext/tinyopt/include/tinyopt/tinyopt.h deleted file mode 100644 index 9cd2bf903b..0000000000 --- a/ext/tinyopt/include/tinyopt/tinyopt.h +++ /dev/null @@ -1,1156 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TINYOPT_VERSION "1.0" -#define TINYOPT_VERSION_MAJOR 1 -#define TINYOPT_VERSION_MINOR 0 -#define TINYOPT_VERSION_PATCH 0 -#define TINYOPT_VERSION_PRERELEASE "" - -namespace to { - -// maybe represents an optional value of type T, -// with an interface similar to C++17 std::optional. -// -// Other than C++14 compatibility, the main deviations/extensions -// from std::optional are: -// -// 1. maybe represents an optional value of an 'empty' type. -// This is used primarily for consistency in generic uses of -// maybe in contexts where functions may not return or consume -// a value. -// -// 2. Monadic-like overloads of operator<< that lift a function -// or a functional object on the left hand side when the -// right hand side is maybe (see below). With an lvalue -// on the left hand side, operator<< acts as a conditional -// assignment. -// -// nothing is a special value that converts to an empty maybe for any T. - -constexpr struct nothing_t {} nothing; - -template -struct maybe { - maybe() noexcept: ok(false) {} - maybe(nothing_t) noexcept: ok(false) {} - maybe(const T& v): ok(true) { construct(v); } - maybe(T&& v): ok(true) { construct(std::move(v)); } - maybe(const maybe& m): ok(m.ok) { if (ok) construct(*m); } - maybe(maybe&& m): ok(m.ok) { if (ok) construct(std::move(*m)); } - - ~maybe() { destroy(); } - - template - maybe(const maybe& m): ok(m.ok) { if (ok) construct(*m); } - - template - maybe(maybe&& m): ok(m.ok) { if (ok) construct(std::move(*m)); } - - maybe& operator=(nothing_t) { return destroy(), *this; } - maybe& operator=(const T& v) { return assign(v), *this; } - maybe& operator=(T&& v) { return assign(std::move(v)), *this; } - maybe& operator=(const maybe& m) { return m.ok? assign(*m): destroy(), *this; } - maybe& operator=(maybe&& m) { return m.ok? assign(std::move(*m)): destroy(), *this; } - - const T& value() const & { return assert_ok(), *vptr(); } - T&& value() && { return assert_ok(), std::move(*vptr()); } - - const T& operator*() const & noexcept { return *vptr(); } - const T* operator->() const & noexcept { return vptr(); } - T&& operator*() && { return std::move(*vptr()); } - - bool has_value() const noexcept { return ok; } - explicit operator bool() const noexcept { return ok; } - - template friend struct maybe; - -private: - bool ok = false; - alignas(T) char data[sizeof(T)]; - - T* vptr() noexcept { return reinterpret_cast(data); } - const T* vptr() const noexcept { return reinterpret_cast(data); } - - void construct(const T& v) { new (data) T(v); ok = true; } - void construct(T&& v) { new (data) T(std::move(v)); ok = true; } - - void assign(const T& v) { if (ok) *vptr()=v; else construct(v); } - void assign(T&& v) { if (ok) *vptr()=std::move(v); else construct(std::move(v)); } - - void destroy() { if (ok) (**this).~T(); ok = false; } - void assert_ok() const { if (!ok) throw std::invalid_argument("is nothing"); } -}; - -namespace impl { - template - struct is_maybe_: std::false_type {}; - - template - struct is_maybe_>: std::true_type {}; -} - -template -using is_maybe = impl::is_maybe_>>; - -// maybe acts as a maybe with an empty or inaccessible wrapped value. - -template <> -struct maybe { - bool ok = false; - - constexpr maybe() noexcept: ok(false) {} - constexpr maybe(nothing_t&) noexcept: ok(false) {} - constexpr maybe(const nothing_t&) noexcept: ok(false) {} - constexpr maybe(nothing_t&&) noexcept: ok(false) {} - constexpr maybe(const maybe& m) noexcept: ok(m.ok) {} - - template ::value>> - constexpr maybe(X&&) noexcept: ok(true) {} - - template - constexpr maybe(const maybe& m) noexcept: ok(m.ok) {} - - maybe& operator=(nothing_t) noexcept { return ok = false, *this; } - maybe& operator=(const maybe& m) noexcept { return ok = m.ok, *this; } - template - maybe& operator=(U&&) noexcept { return ok = true, *this; } - - bool has_value() const noexcept { return ok; } - constexpr explicit operator bool() const noexcept { return ok; } -}; - -// something is a non-empty maybe value. - -constexpr maybe something(true); - -// just converts a value of type X to a maybe containing the value. - -template -auto just(X&& x) { return maybe>(std::forward(x)); } - -// operator<< offers monadic-style chaining of maybe values: -// (f << m) evaluates to an empty maybe if m is empty, or else to -// a maybe value wrapping the result of applying f to the value -// in m. - -template < - typename F, - typename T, - typename R = std::decay_t()(std::declval()))>, - typename = std::enable_if_t::value> -> -maybe operator<<(F&& f, const maybe& m) { - if (m) return f(*m), something; else return nothing; -} - -template < - typename F, - typename T, - typename R = std::decay_t()(std::declval()))>, - typename = std::enable_if_t::value> -> -maybe operator<<(F&& f, const maybe& m) { - if (m) return f(*m); else return nothing; -} - -template < - typename F, - typename R = std::decay_t()())>, - typename = std::enable_if_t::value> -> -maybe operator<<(F&& f, const maybe& m) { - return m? (f(), something): nothing; -} - -template < - typename F, - typename R = std::decay_t()())>, - typename = std::enable_if_t::value> -> -maybe operator<<(F&& f, const maybe& m) { - return m? just(f()): nothing; -} - -// If the lhs is not functional, return a maybe value with the result -// of assigning the value in the rhs, or nothing if the rhs is nothing. - -template -auto operator<<(T& x, const maybe& m) -> maybe> { - if (m) return x=*m; else return nothing; -} - -template -auto operator<<(T& x, const maybe& m) -> maybe> { - if (m) return x=true; else return nothing; -} - -// Tinyopt exceptions, usage, error reporting functions: - -// `option_error` is the base class for exceptions thrown -// by the option handling functions. - -struct option_error: public std::runtime_error { - option_error(const std::string& message): std::runtime_error(message) {} - option_error(const std::string& message, std::string arg): - std::runtime_error(message+": "+arg), arg(std::move(arg)) {} - - std::string arg; -}; - -struct option_parse_error: option_error { - option_parse_error(const std::string &arg): - option_error("option parse error", arg) {} -}; - -struct missing_mandatory_option: option_error { - missing_mandatory_option(const std::string &arg): - option_error("missing mandatory option", arg) {} -}; - -struct missing_argument: option_error { - missing_argument(const std::string &arg): - option_error("option misssing argument", arg) {} -}; - -struct user_option_error: option_error { - user_option_error(const std::string &arg): - option_error(arg) {} -}; - -// `usage` prints usage information to stdout (no error message) -// or to stderr (with error message). It extracts the program basename -// from the provided argv[0] string. - -inline void usage(const char* argv0, const std::string& usage_str, const std::string& prefix = "Usage: ") { - const char* basename = std::strrchr(argv0, '/'); - basename = basename? basename+1: argv0; - - std::cout << prefix << basename << " " << usage_str << "\n"; -} - -inline void usage_error(const char* argv0, const std::string& usage_str, const std::string& parse_err, const std::string& prefix = "Usage: ") { - const char* basename = std::strrchr(argv0, '/'); - basename = basename? basename+1: argv0; - - std::cerr << basename << ": " << parse_err << "\n"; - std::cerr << prefix << basename << " " << usage_str << "\n"; -} - -// Parser objects act as functionals, taking -// a const char* argument and returning maybe -// for some T. - -template -struct default_parser { - maybe operator()(const char* text) const { - if (!text) return nothing; - V v; - std::istringstream stream(text); - if (!(stream >> v)) return nothing; - if (!stream.eof()) stream >> std::ws; - return stream.eof()? maybe(v): nothing; - } -}; - -template <> -struct default_parser { - maybe operator()(const char* text) const { - return just(text); - } -}; - -template <> -struct default_parser { - maybe operator()(const char* text) const { - return just(std::string(text)); - } -}; - -template <> -struct default_parser { - maybe operator()(const char*) const { - return something; - } -}; - -template -class keyword_parser { - std::vector> map_; - -public: - template - keyword_parser(const KeywordPairs& pairs) { - using std::begin; - using std::end; - map_.assign(begin(pairs), end(pairs)); - } - - maybe operator()(const char* text) const { - if (!text) return nothing; - for (const auto& p: map_) { - if (text==p.first) return p.second; - } - return nothing; - } -}; - -// Returns a parser that matches a set of keywords, -// returning the corresponding values in the supplied -// pairs. - -template -auto keywords(const KeywordPairs& pairs) { - using std::begin; - using value_type = std::decay_t(*begin(pairs)))>; - return keyword_parser(pairs); -} - - -// A parser for delimited sequences of values; returns -// a vector of the values obtained from the supplied -// per-item parser. - -template -class delimited_parser { - char delim_; - P parse_; - using inner_value_type = std::decay_t()(""))>; - -public: - template - delimited_parser(char delim, Q&& parse): delim_(delim), parse_(std::forward(parse)) {} - - maybe> operator()(const char* text) const { - if (!text) return nothing; - - std::vector values; - if (!*text) return values; - - std::size_t n = std::strlen(text); - std::vector input(1+n); - std::copy(text, text+n, input.data()); - - char* p = input.data(); - char* end = input.data()+1+n; - do { - char* q = p; - while (*q && *q!=delim_) ++q; - *q++ = 0; - - if (auto mv = parse_(p)) values.push_back(*mv); - else return nothing; - - p = q; - } while (p -auto delimited(char delim, Q&& parse) { - using P = std::decay_t; - return delimited_parser

(delim, std::forward(parse)); -} - -template -auto delimited(char delim = ',') { - return delimited(delim, default_parser{}); -} - -// Option keys -// ----------- -// -// A key is how the option is specified in an argument list, and is typically -// represented as a 'short' (e.g. '-a') option or a 'long' option (e.g. -// '--apple'). -// -// The value for an option can always be taken from the next argument in the -// list, but in addition can be specified together with the key itself, -// depending on the properties of the option key: -// -// --key=value 'Long' style argument for key "--key" -// -kvalue 'Compact' style argument for key "-k" -// -// Compact option keys can be combined in the one item in the argument list, if -// the options do not take any values (that is, they are flags). For example, -// if -a, -b are flags and -c takes an integer argument, with all three keys -// marked as compact, then an item '-abc3' in the argument list will be parsed -// in the same way as the sequence of items '-a', '-b', '-c', '3'. -// -// An option without a key will match any item in the argument list; options -// with keys are always checked first. -// -// Only short and long kets can be used with to::parse. - -struct key { - std::string label; - enum style { shortfmt, longfmt, compact } style = shortfmt; - - key(std::string l): label(std::move(l)) { - if (label[0]=='-' && label[1]=='-') style = longfmt; - } - - key(const char* label): key(std::string(label)) {} - - key(std::string label, enum style style): - label(std::move(label)), style(style) {} -}; - -inline namespace literals { - -inline key operator""_short(const char* label, std::size_t) { - return key(label, key::shortfmt); -} - -inline key operator""_long(const char* label, std::size_t) { - return key(label, key::longfmt); -} - -inline key operator""_compact(const char* label, std::size_t) { - return key(label, key::compact); -} - -} // namespace literals - -// Argument state -// -------------- -// -// to::state represents the collection of command line arguments. Mutating -// operations (shift(), successful option matching, etc.) will modify the -// underlying set of arguments used to construct the state object. -// -// This is used only internally — it is not part of the public API. -// Members are left public for the purpose of unit testing. - -struct state { - int& argc; - char** argv; - unsigned optoff = 0; - - state(int& argc, char** argv): argc(argc), argv(argv) {} - - // False => no more arguments. - explicit operator bool() const { return *argv; } - - // Shift arguments left in-place. - void shift(unsigned n = 1) { - char** skip = argv; - while (*skip && n) ++skip, --n; - - argc -= (skip-argv); - auto p = argv; - do { *p++ = *skip; } while (*skip++); - - optoff = 0; - } - - // Skip current argument without modifying list. - void skip() { - if (*argv) ++argv; - } - - // Match an option given by the key which takes an argument. - // If successful, consume option and argument and return pointer to - // argument string, else return nothing. - maybe match_option(const key& k) { - const char* p = nullptr; - - if (k.style==key::compact) { - if ((p = match_compact_key(k.label.c_str()))) { - if (!*p) { - p = argv[1]; - shift(2); - } - else shift(); - return p; - } - } - else if (!optoff && k.label==*argv) { - p = argv[1]; - shift(2); - return p; - } - else if (!optoff && k.style==key::longfmt) { - auto keylen = k.label.length(); - if (!std::strncmp(*argv, k.label.c_str(), keylen) && (*argv)[keylen]=='=') { - p = &(*argv)[keylen+1]; - shift(); - return p; - } - } - - return nothing; - } - - // Match a flag given by the key. - // If successful, consume flag and return true, else return false. - bool match_flag(const key& k) { - if (k.style==key::compact) { - if (auto p = match_compact_key(k.label.c_str())) { - if (!*p) shift(); - return true; - } - } - else if (!optoff && k.label==*argv) { - shift(); - return true; - } - - return false; - } - - // Compact-style keys can be combined in one argument; combined keys - // with a common prefix only need to supply the prefix once at the - // beginning of the argument. - const char* match_compact_key(const char* k) { - unsigned keylen = std::strlen(k); - - unsigned prefix_max = std::min(keylen-1, optoff); - for (std::size_t l = 0; l<=prefix_max; ++l) { - if (l && strncmp(*argv, k, l)) break; - if (strncmp(*argv+optoff, k+l, keylen-l)) continue; - optoff += keylen-l; - return *argv+optoff; - } - - return nullptr; - } -}; - -// Sinks and actions -// ----------------- -// -// Sinks wrap a function that takes a pointer to an option parameter and stores -// or acts upon the parsed result. -// -// They can be constructed from an lvalue reference or a functional object (via -// the `action` function) with or without an explicit parser function. If no -// parser is given, a default one is used if the correct value type can be -// determined. - -namespace impl { - template struct fn_arg_type { using type = void; }; - template struct fn_arg_type { using type = X; }; - template struct fn_arg_type { using type = X; }; - template struct fn_arg_type { using type = X; }; - template struct fn_arg_type { using type = X; }; - template struct fn_arg_type { using type = X; }; - template struct fn_arg_type { using type = X; }; - - template struct void_type { using type = void; }; -} - -template -struct unary_argument_type { using type = typename impl::fn_arg_type::type; }; - -template -struct unary_argument_type::type> { - using type = typename impl::fn_arg_type::type; -}; - -template -using unary_argument_type_t = typename unary_argument_type::type; - -struct sink { - // Tag class for constructor. - struct action_t {}; - - constexpr static action_t action{}; - - sink(): - sink(action, [](const char*) { return true; }) - {} - - template - sink(V& var): sink(var, default_parser{}) {} - - template - sink(V& var, P parser): - sink(action, [ref=std::ref(var), parser](const char* param) { - if (auto p = parser(param)) return ref.get() = std::move(*p), true; - else return false; - }) - {} - - template - sink(action_t, Action a): op(std::move(a)) {} - - bool operator()(const char* param) const { return op(param); } - std::function op; - -}; - -// Convenience functions for construction of sink actions -// with explicit or implicit parser. - -template >> -sink action(F f) { - return sink(sink::action, - [f = std::move(f)](const char* arg) -> bool { - return static_cast(f << default_parser{}(arg)); - }); -} - -template -sink action(F f, P parser) { - return sink(sink::action, - [f = std::move(f), parser = std::move(parser)](const char* arg) -> bool { - return static_cast(f << parser(arg)); - }); -} - -// Special actions: -// -// error(message) Throw a user_option_error with the supplied message. - -inline sink error(std::string message) { - return sink(sink::action, - [m = std::move(message)](const char*) -> bool { - throw user_option_error(m); - }); -} - -// Sink adaptors: -// -// These adaptors constitute short cuts for making actions that count the -// occurance of a flag, set a fixed value when a flag is provided, or for -// appending an option parameter onto a vector of values. - -// Push parsed option parameter on to container. -template > -sink push_back(Container& c, P parser = P{}) { - return action( - [ref = std::ref(c)](typename Container::value_type v) { ref.get().push_back(std::move(v)); }, - std::move(parser)); -} - -// Set v to value when option parsed; ignore any option parameter. -template -sink set(V& v, X value) { - return action([ref = std::ref(v), value = std::move(value)] { ref.get() = value; }); -} - -// Set v to true when option parsed; ignore any option parameter. -template -sink set(V& v) { - return set(v, true); -} - -// Incrememnt v when option parsed; ignore any option parameter. -template -sink increment(V& v) { - return action([ref = std::ref(v)] { ++ref.get(); }); -} - -template -sink increment(V& v, X delta) { - return action([ref = std::ref(v), delta = std::move(delta)] { ref.get() += delta; }); -} - -// Modal configuration -// ------------------- -// -// Options can be filtered by some predicate, and can trigger a state -// change when successfully processed. -// -// Filter construction: -// to::when(Fn f) f is a functional with signature bool (int) -// to::when(int s) equivalent to to::when([](int k) { return k==s; }) -// to::when(a, b, ...) filter than is satisfied by to::when(a) or -// to::when(b) or ... -// -// The argument to the filter predicate is the 'mode', a mutable state maintained -// during a single run of to::run(). -// -// Mode changes: -// to::then(Fn f) f is a functional with signature int (int) -// to::then(int s) equivalent to to::then([](int) { return s; }) -// -// The argument to the functional is the current mode; the return value -// sets the new value of mode. -// -// Filters are called before keys are matched; modal changes are called -// after an option is processed. All - -using filter = std::function; -using modal = std::function; - -template ()(0))> -filter when(F f) { - return [f = std::move(f)](int mode) { return static_cast(f(mode)); }; -} - -inline filter when(int m) { - return [m](int mode) { return m==mode; }; -} - -template -filter when(A a, B&& b, Rest&&... rest) { - return - [fhead = when(std::forward(a)), - ftail = when(std::forward(b), std::forward(rest)...)](int m) { - return fhead(m) || ftail(m); - }; -} - -template ()(0))> -modal then(F f) { - return [f = std::move(f)](int mode) { return static_cast(f(mode)); }; -} - -inline modal then(int m) { - return [m](int) { return m; }; -} - - -// Option specification -// -------------------- -// -// An option specification comprises zero or more keys (e.g. "-a", "--foo"), -// a sink (where the parsed argument will be sent), a parser - which may be -// the default parser - and zero or more flags that modify its behaviour. -// -// Option flags: - -enum option_flag { - flag = 1, // Option takes no parameter. - ephemeral = 2, // Option is not saved in returned results. - single = 4, // Option is parsed at most once. - mandatory = 8, // Option must be present in argument list. - exit = 16, // Option stops further argument processing, return `nothing` from run(). - stop = 32, // Option stops further argument processing, return saved options. -}; - -struct option { - sink s; - std::vector keys; - std::vector filters; - std::vector modals; - - bool is_flag = false; - bool is_ephemeral = false; - bool is_single = false; - bool is_mandatory = false; - bool is_exit = false; - bool is_stop = false; - - template - option(sink s, Rest&&... rest): s(std::move(s)) { - init_(std::forward(rest)...); - } - - bool has_key(const std::string& arg) const { - if (keys.empty() && arg.empty()) return true; - for (const auto& k: keys) { - if (arg==k.label) return true; - } - return false; - } - - bool check_mode(int mode) const { - for (auto& f: filters) { - if (!f(mode)) return false; - } - return true; - } - - void set_mode(int& mode) const { - for (auto& f: modals) mode = f(mode); - } - - void run(const std::string& label, const char* arg) const { - if (!is_flag && !arg) throw missing_argument(label); - if (!s(arg)) throw option_parse_error(label); - } - - std::string longest_label() const { - const std::string* p = 0; - for (auto& k: keys) { - if (!p || k.label.size()>p->size()) p = &k.label; - } - return p? *p: std::string{}; - } - -private: - void init_() {} - - template - void init_(enum option_flag f, Rest&&... rest) { - is_flag |= f & flag; - is_ephemeral |= f & ephemeral; - is_single |= f & single; - is_mandatory |= f & mandatory; - is_exit |= f & exit; - is_stop |= f & stop; - init_(std::forward(rest)...); - } - - template - void init_(filter f, Rest&&... rest) { - filters.push_back(std::move(f)); - init_(std::forward(rest)...); - } - - template - void init_(modal f, Rest&&... rest) { - modals.push_back(std::move(f)); - init_(std::forward(rest)...); - } - - template - void init_(key k, Rest&&... rest) { - keys.push_back(std::move(k)); - init_(std::forward(rest)...); - } - -}; - -// Saved options -// ------------- -// -// Successfully matched options, excluding those with the to::ephemeral flag -// set, are collated in a saved_options structure for potential documentation -// or replay. - -struct saved_options: private std::vector { - using std::vector::begin; - using std::vector::end; - using std::vector::size; - using std::vector::empty; - - void add(std::string s) { push_back(std::move(s)); } - - saved_options& operator+=(const saved_options& so) { - insert(end(), so.begin(), so.end()); - return *this; - } - - struct arglist { - int argc; - char** argv; - std::vector arg_data; - }; - - // Construct argv representing argument list. - arglist as_arglist() const { - arglist A; - - for (auto& a: *this) A.arg_data.push_back(const_cast(a.c_str())); - A.arg_data.push_back(nullptr); - A.argv = A.arg_data.data(); - A.argc = A.arg_data.size()-1; - return A; - } - - // Serialized representation: - // - // Saved arguments are separated by white space. If an argument - // contains whitespace or a special character, it is escaped with - // single quotes in a POSIX shell compatible fashion, so that - // the representation can be used directly on a shell command line. - - friend std::ostream& operator<<(std::ostream& out, const saved_options& s) { - auto escape = [](const std::string& v) { - if (!v.empty() && v.find_first_of("\\*?[#~=%|^;<>()$'`\" \t\n")==std::string::npos) return v; - - // Wrap string in single quotes, replacing any internal single quote - // character with: '\'' - - std::string q ="'"; - for (auto c: v) { - c=='\''? q += "'\\''": q += c; - } - return q += '\''; - }; - - bool first = true; - for (auto& p: s) { - if (first) first = false; else out << ' '; - out << escape(p); - } - return out; - } - - friend std::istream& operator>>(std::istream& in, saved_options& s) { - std::string w; - bool have_word = false; - bool quote = false; // true => within single quotes. - bool escape = false; // true => previous character was backslash. - while (in) { - int c = in.get(); - if (c==EOF) break; - - if (quote) { - if (c!='\'') w += c; - else quote = false; - } - else { - if (escape) { - w += c; - escape = false; - } - else if (c=='\\') { - escape = true; - have_word = true; - } - else if (c=='\'') { - quote = true; - have_word = true; - } - else if (c!=' ' && c!='\t' && c!='\n') { - w += c; - have_word = true; - } - else { - if (have_word) s.add(w); - have_word = false; - w = ""; - } - } - } - - if (have_word) s.add(w); - return in; - } -}; - -// Option with mutable state (for checking single and mandatory flags), -// used by to::run(). - -namespace impl { - struct counted_option: option { - int count = 0; - - counted_option(const option& o): option(o) {} - - // On successful match, return pointers to matched key and value. - // For flags, use nullptr for value; for empty key sets, use - // nullptr for key. - maybe> match(state& st) { - if (is_flag) { - for (auto& k: keys) { - if (st.match_flag(k)) return set(k.label, nullptr); - } - return nothing; - } - else if (!keys.empty()) { - for (auto& k: keys) { - if (auto param = st.match_option(k)) return set(k.label, *param); - } - return nothing; - } - else { - const char* param = *st.argv; - st.shift(); - return set("", param); - } - } - - std::pair set(const char* arg) { - run("", arg); - ++count; - return {nullptr, arg}; - } - - std::pair set(const std::string& label, const char* arg) { - run(label, arg); - ++count; - return {label.c_str(), arg}; - } - }; -} // namespace impl - -// Running a set of options -// ------------------------ -// -// to::run() can be used to parse options from the command-line and/or from -// saved_options data. -// -// The first argument is a collection or sequence of option specifications, -// followed optionally by command line argc and argv or just argv. A -// saved_options object can be optionally passed as the last parameter. -// -// If an option with the to::exit flag is matched, option parsing will -// immediately stop and an empty value will be returned. Otherwise to::run() -// will return a saved_options structure recording the successfully parsed -// options. - -namespace impl { - inline maybe run(std::vector& opts, int& argc, char** argv) { - saved_options collate; - bool exit = false; - bool stop = false; - state st{argc, argv}; - int mode = 0; - while (st && !exit && !stop) { - // Try options with a key first. - for (auto& o: opts) { - if (o.keys.empty()) continue; - if (o.is_single && o.count) continue; - if (!o.check_mode(mode)) continue; - - if (auto ma = o.match(st)) { - if (!o.is_ephemeral) { - if (ma->first) collate.add(ma->first); - if (ma->second) collate.add(ma->second); - } - o.set_mode(mode); - exit = o.is_exit; - stop = o.is_stop; - goto next; - } - } - - // Literal "--" terminates option parsing. - if (!std::strcmp(*argv, "--")) { - st.shift(); - return collate; - } - - // Try free options. - for (auto& o: opts) { - if (!o.keys.empty()) continue; - if (o.is_single && o.count) continue; - if (!o.check_mode(mode)) continue; - - if (auto ma = o.match(st)) { - if (!o.is_ephemeral) collate.add(ma->second); - o.set_mode(mode); - exit = o.is_exit; - goto next; - } - } - - // Nothing matched, so increment argv. - st.skip(); - next: ; - } - - return exit? nothing: just(collate); - } -} // namespace impl - - -template -maybe run(const Options& options, int& argc, char** argv, const saved_options& restore = saved_options{}) { - using std::begin; - using std::end; - std::vector opts(begin(options), end(options)); - auto r_args = restore.as_arglist(); - - saved_options coll1, coll2; - if (coll1 << impl::run(opts, r_args.argc, r_args.argv) && coll2 << impl::run(opts, argc, argv)) { - for (auto& o: opts) { - if (o.is_mandatory && !o.count) throw missing_mandatory_option(o.longest_label()); - } - return coll1 += coll2; - } - - return nothing; -} - -template -maybe run(const Options& options, const saved_options& restore) { - int ignore_argc = 0; - char* end_of_args = nullptr; - return run(options, ignore_argc, &end_of_args, restore); -} - -template -maybe run(const Options& options, char** argv) { - int ignore_argc = 0; - return run(options, ignore_argc, argv); -} - -template -maybe run(const Options& options, char** argv, const saved_options& restore) { - int ignore_argc = 0; - return run(options, ignore_argc, argv, restore); -} - -// Running through command line arguments explicitly. -// -------------------------------------------------- -// -// `to::parse` checks the given argument against the provided keys, and on a match will try to -// parse and consume an argument of type V. A custom parser can be supplied. -// -// `to::parse` will do the same, for flag options that take no argument. - -template < - typename V, - typename P, - typename = std::enable_if_t::value>, - typename = std::enable_if_t::value>, - typename... Tail -> -maybe parse(char**& argp, const P& parser, key k0, Tail... krest) { - key keys[] = { std::move(k0), std::move(krest)... }; - - const char* arg = argp[0]; - if (!arg) return nothing; - - const char* text = nullptr; - for (const key& k: keys) { - if (k.label==arg) { - if (!argp[1]) throw missing_argument(arg); - text = argp[1]; - argp += 2; - goto match; - } - else if (k.style==key::longfmt) { - const char* eq = std::strrchr(arg, '='); - if (eq && !std::strncmp(arg, k.label.c_str(), eq-arg)) { - text = eq+1; - argp += 1; - goto match; - } - } - } - return nothing; - -match: - if (auto v = parser(text)) return v; - else throw option_parse_error(arg); -} - -template < - typename V, - typename = std::enable_if_t::value>, - typename... Tail -> -maybe parse(char**& argp, key k0, Tail... krest) { - return parse(argp, default_parser{}, std::move(k0), std::move(krest)...); -} - -template -maybe parse(char**& argp, key k0, Tail... krest) { - key keys[] = { std::move(k0), std::move(krest)... }; - - const char* arg = argp[0]; - if (!arg) return nothing; - - for (const key& k: keys) { - if (k.label==arg) { - ++argp; - return true; - } - } - return nothing; -} - -} // namespace to diff --git a/ext/units b/ext/units deleted file mode 160000 index 7917f5f2cf..0000000000 --- a/ext/units +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7917f5f2cfefdcc90b5085ade91761d06df74e59 diff --git a/modcc/CMakeLists.txt b/modcc/CMakeLists.txt index b5a4982f07..af691004b1 100644 --- a/modcc/CMakeLists.txt +++ b/modcc/CMakeLists.txt @@ -32,50 +32,19 @@ set(modcc_sources modcc.cpp) add_library(libmodcc STATIC ${libmodcc_sources}) target_link_libraries(libmodcc PUBLIC arbor-public-headers) -if (ARB_USE_BUNDLED_FMT) - target_include_directories(libmodcc - PUBLIC - $ - $ - PRIVATE - $) +target_include_directories(libmodcc + PUBLIC $ + $) - target_compile_definitions(libmodcc PRIVATE FMT_HEADER_ONLY) -else() - target_include_directories(libmodcc - PUBLIC - $ - $) - find_package(fmt REQUIRED) - target_link_libraries(libmodcc PRIVATE fmt::fmt-header-only) -endif() +target_link_libraries(libmodcc PRIVATE fmt::fmt-header-only) set_target_properties(libmodcc PROPERTIES OUTPUT_NAME modcc) export_visibility(libmodcc) -if (NOT ARB_WITH_EXTERNAL_MODCC) - add_executable(modcc ${modcc_sources}) +add_executable(modcc ${modcc_sources}) - if (ARB_USE_BUNDLED_FMT) - target_include_directories(modcc - PUBLIC - $ - $ - PRIVATE - $) - - target_compile_definitions(modcc PRIVATE FMT_HEADER_ONLY) - else() - target_include_directories(modcc - PUBLIC - $ - $) - find_package(fmt REQUIRED) - target_link_libraries(modcc PRIVATE fmt::fmt-header-only) - endif() - - target_link_libraries(modcc PRIVATE libmodcc ext-tinyopt) - set_target_properties(modcc libmodcc PROPERTIES EXCLUDE_FROM_ALL ${ARB_WITH_EXTERNAL_MODCC}) - install(TARGETS modcc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -endif() +target_link_libraries(modcc PRIVATE fmt::fmt-header-only) +target_link_libraries(modcc PRIVATE libmodcc ext-tinyopt) +set_target_properties(modcc libmodcc PROPERTIES EXCLUDE_FROM_ALL ${ARB_WITH_EXTERNAL_MODCC}) +install(TARGETS modcc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pyproject.toml b/pyproject.toml index 72b1c3caa6..973a85749c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ arbor-build-catalogue = "arbor:build_catalogue" [tool.scikit-build] cmake.args = [ "-DARB_WITH_PYTHON=ON", - "-DARB_USE_BUNDLED_LIBS=ON", ] sdist.include = ["ext/*/.git"] wheel.install-dir = "arbor" diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 9a42a09c7d..e08c15bb6c 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,26 +1,5 @@ include(GNUInstallDirs) -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}/ext/pybind11") - check_git_submodule(pybind11 "${pb11_src_dir}") - - if(NOT pybind11_avail) - message(FATAL_ERROR "The git submodule for pybind11 is not available, required for python support") - 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. - add_subdirectory(${pb11_src_dir} pybind11) -else() - find_package(pybind11 2.10.0 REQUIRED) -endif() - set(pyarb_source cable_cell_io.cpp probes.cpp @@ -110,7 +89,8 @@ get_filename_component(ARB_PYTHON_LIB_PATH_DEFAULT "${ARB_PYTHON_LIB_PATH_DEFAUL # Default to installing in that path, override with user specified ARB_PYTHON_LIB_PATH set(ARB_PYTHON_LIB_PATH ${ARB_PYTHON_LIB_PATH_DEFAULT} CACHE PATH "path for installing Python module for Arbor.") -message(STATUS "Python module installation path: ${ARB_PYTHON_LIB_PATH}") +message(VERBOSE "Python module installation path: ${ARB_PYTHON_LIB_PATH}") +mark_as_advanced(FORCE ARB_PYTHON_LIB_PATH) # generate type stubs and copy them to the expected places if(ARB_BUILD_PYTHON_STUBS) diff --git a/python/test/cpp/CMakeLists.txt b/python/test/cpp/CMakeLists.txt index 0b05ef13e3..d2102853f4 100644 --- a/python/test/cpp/CMakeLists.txt +++ b/python/test/cpp/CMakeLists.txt @@ -21,7 +21,7 @@ target_include_directories( target_link_libraries( py_unit PRIVATE - ext-gtest + gtest gtest_main arbor py_unit_lib pybind11::module diff --git a/scripts/check-all-tags.sh b/scripts/check-all-tags.sh index 2caca4fd97..7394d2154d 100755 --- a/scripts/check-all-tags.sh +++ b/scripts/check-all-tags.sh @@ -25,7 +25,7 @@ do echo " * simd=$simd" out=results/$tag-`git rev-parse --short HEAD`/cpp/simd=$simd cd build - cmake .. -DARB_USE_BUNDLED_LIBS=ON -DCMAKE_CXX_COMPILER=$cxx -DCMAKE_C_COMPILER=$cc -DCMAKE_BUILD_TYPE=release -DARB_VECTORIZE=$simd -DARB_ARCH=native + cmake .. -DCMAKE_CXX_COMPILER=$cxx -DCMAKE_C_COMPILER=$cc -DCMAKE_BUILD_TYPE=release -DARB_VECTORIZE=$simd -DARB_ARCH=native ninja install examples cd - for ex in bench brunel gap_junctions generators lfp ring single-cell "probe-demo v" diff --git a/spack/package.py b/spack/package.py index cda93eaf26..08c3a5ce99 100644 --- a/spack/package.py +++ b/spack/package.py @@ -149,9 +149,6 @@ def cmake_args(self): # Might return nothing if opt_flags: args.append("-DARB_CXX_FLAGS_TARGET=" + opt_flags) - # Needed, spack has no units package - args.append("-DARB_USE_BUNDLED_UNITS=ON") - return args @run_after("install", when="+python") diff --git a/sup/CMakeLists.txt b/sup/CMakeLists.txt index cb9e606476..98b3b9f066 100644 --- a/sup/CMakeLists.txt +++ b/sup/CMakeLists.txt @@ -5,23 +5,7 @@ set(sup-sources add_library(arbor-sup ${sup-sources}) -if (ARB_USE_BUNDLED_FMT) - target_include_directories(arbor-sup - PUBLIC - $ - $ - PRIVATE - $) - - target_compile_definitions(arbor-sup PRIVATE FMT_HEADER_ONLY) -else() - target_include_directories(arbor-sup - PUBLIC - $ - $) - find_package(fmt REQUIRED) - target_link_libraries(arbor-sup PRIVATE fmt::fmt-header-only) -endif() +target_link_libraries(arbor-sup PRIVATE fmt::fmt-header-only) # Compile sup library with the same optimization flags as libarbor. target_compile_options(arbor-sup PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) diff --git a/test/ubench/CMakeLists.txt b/test/ubench/CMakeLists.txt index 8b40a245af..56bf7b4048 100644 --- a/test/ubench/CMakeLists.txt +++ b/test/ubench/CMakeLists.txt @@ -26,7 +26,7 @@ foreach(bench_src ${bench_sources}) string(REGEX REPLACE "\\.[^.]*$" "" bench_exe ${bench_src}) add_executable(${bench_exe} EXCLUDE_FROM_ALL "${bench_src}") - target_link_libraries(${bench_exe} arbor arborio arbor-private-headers ext-benchmark) + target_link_libraries(${bench_exe} arbor arborio arbor-private-headers benchmark) target_compile_options(${bench_exe} PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) target_compile_definitions(${bench_exe} PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") list(APPEND bench_exe_list ${bench_exe}) diff --git a/test/unit-distributed/CMakeLists.txt b/test/unit-distributed/CMakeLists.txt index 96ea5abf4b..2a5e580695 100644 --- a/test/unit-distributed/CMakeLists.txt +++ b/test/unit-distributed/CMakeLists.txt @@ -5,7 +5,6 @@ set(unit-distributed_sources test_mpi.cpp test_distributed_for_each.cpp test_network_generation.cpp - # unit test driver test.cpp ) @@ -15,7 +14,7 @@ add_dependencies(tests unit-local) target_compile_options(unit-local PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) target_compile_definitions(unit-local PRIVATE TEST_LOCAL) -target_link_libraries(unit-local PRIVATE ext-gtest arbor arborenv arborio arbor-sup arbor-private-headers ext-tinyopt) +target_link_libraries(unit-local PRIVATE gtest gtest_main arbor arborenv arborio arbor-sup arbor-private-headers ext-tinyopt) if(ARB_WITH_MPI) add_executable(unit-mpi EXCLUDE_FROM_ALL ${unit-distributed_sources}) @@ -23,6 +22,5 @@ if(ARB_WITH_MPI) target_compile_options(unit-mpi PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) target_compile_definitions(unit-mpi PRIVATE TEST_MPI) - target_link_libraries(unit-mpi PRIVATE ext-gtest arbor arborenv arborio arbor-sup arbor-private-headers ext-tinyopt) + target_link_libraries(unit-mpi PRIVATE gtest gtest_main arbor arborenv arborio arbor-sup arbor-private-headers ext-tinyopt) endif() - diff --git a/test/unit-modcc/CMakeLists.txt b/test/unit-modcc/CMakeLists.txt index dec3ad2aff..620ca70883 100644 --- a/test/unit-modcc/CMakeLists.txt +++ b/test/unit-modcc/CMakeLists.txt @@ -25,5 +25,5 @@ set(unit-modcc_sources add_executable(unit-modcc EXCLUDE_FROM_ALL ${unit-modcc_sources}) add_dependencies(tests unit-modcc) -target_link_libraries(unit-modcc PRIVATE libmodcc ext-gtest) +target_link_libraries(unit-modcc PRIVATE libmodcc gtest gtest_main) target_compile_definitions(unit-modcc PRIVATE "DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"") diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 96f7e6d328..fc3f79e896 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -189,7 +189,7 @@ make_catalogue_standalone( MOD dummy CXX CXX_FLAGS_TARGET ${ARB_CXX_FLAGS_TARGET_FULL} - VERBOSE ON) + VERBOSE ${ARB_CAT_VERBOSE}) target_link_libraries(dummy-catalogue PRIVATE arbor-private-deps) add_dependencies(unit dummy-catalogue) @@ -199,4 +199,4 @@ target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR} target_compile_definitions(unit PRIVATE "-DLIBDIR=\"${PROJECT_BINARY_DIR}/lib\"") target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated/testing") -target_link_libraries(unit PRIVATE ext-gtest ext-random123 arbor arborenv arborio arborio-private-headers arbor-private-headers arbor-sup) +target_link_libraries(unit PRIVATE gtest gtest_main ext-random123 arbor arborenv arborio arborio-private-headers arbor-private-headers arbor-sup) diff --git a/test/unit/test_version.cpp b/test/unit/test_version.cpp index 184224c8a5..dbc016a64f 100644 --- a/test/unit/test_version.cpp +++ b/test/unit/test_version.cpp @@ -49,7 +49,10 @@ TEST(version, libmatch) { } TEST(version, sane_config) { - EXPECT_TRUE(arb::build_config=="DEBUG"s || arb::build_config=="RELEASE"s || arb::build_config=="RELWITHDEBINFO"s || arb::build_config=="MINSIZEREL"s); + EXPECT_TRUE(arb::build_config=="DEBUG"s + || arb::build_config=="RELEASE"s + || arb::build_config=="RELWITHDEBINFO"s + || arb::build_config=="MINSIZEREL"s) << " unexpected config value '" << arb::build_config << "'"; } TEST(version, version_components) { From f81b4f2f06f3c70535b33e503e66d1deefbd0ae9 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Wed, 11 Sep 2024 07:43:47 +0200 Subject: [PATCH 3/3] Fix embarrassingly quadratic bug in fvm-layout. (#2397) Fixes accidentally quadratic memory use in `fvm_layout` where every cell allocates space for the whole cell group when an ion is diffusive. --- arbor/fvm_layout.cpp | 9 ++++----- test/unit/test_diffusion.cpp | 4 +++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index 2e667031f3..04b1ac9ced 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -947,6 +947,7 @@ fvm_build_mechanism_data(const cable_cell_global_properties& gprop, else { throw cable_cell_error("unrecognized ion '"+ion+"' in mechanism."); } + } return combined; } @@ -1016,11 +1017,9 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& // Track ion usage of mechanisms so that ions are only instantiated where required. fvm_ion_map ion_build_data; - // add diffusive ions to support: If diffusive, it's everywhere. - for (const auto& [ion, data]: D.diffusive_ions) { - auto& s = ion_build_data[ion].support; - s.resize(D.geometry.size()); - std::iota(s.begin(), s.end(), 0); + // pre-extend support of diffusive ions, if diffusive, it's everywhere. + for (auto& [ion, data]: D.diffusive_ions) { + assign(ion_build_data[ion].support, D.geometry.cell_cvs(cell_idx)); } fvm_mechanism_data M; diff --git a/test/unit/test_diffusion.cpp b/test/unit/test_diffusion.cpp index 54529cd5a7..264450ce79 100644 --- a/test/unit/test_diffusion.cpp +++ b/test/unit/test_diffusion.cpp @@ -86,7 +86,9 @@ struct linear: public recipe { using result_t = std::vector>; testing::AssertionResult all_near(const result_t& a, const result_t& b, double eps) { - if (a.size() != b.size()) return testing::AssertionFailure() << "sequences differ in length"; + if (a.size() != b.size()) return testing::AssertionFailure() << "sequences differ in length" + << " #expected=" << b.size() + << " #received=" << a.size(); std::stringstream res; for (size_t ix = 0; ix < a.size(); ++ix) { const auto&[ax, ay, az] = a[ix];