diff --git a/.github/workflows/wheels-template.yml b/.github/workflows/wheels-template.yml index 7549e8977a..42d9d299c0 100644 --- a/.github/workflows/wheels-template.yml +++ b/.github/workflows/wheels-template.yml @@ -73,7 +73,7 @@ jobs: run: | brew install --cask xquartz brew uninstall cmake || echo "CMake was not pre-installed" - brew install flex bison cmake mpich + brew install flex bison cmake mpich ninja brew unlink mpich && brew install openmpi # Install newer version of Bash on MacOS brew install bash @@ -127,7 +127,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt update - sudo apt install -y mpich openmpi-bin libopenmpi-dev libmpich-dev + sudo apt install -y mpich openmpi-bin libopenmpi-dev libmpich-dev ninja-build - name: "Apply workaround for MPICH on Ubuntu 24.04" if: inputs.platform == 'ubuntu-24.04' @@ -141,6 +141,22 @@ jobs: wget 'https://launchpad.net/ubuntu/+source/mpich/4.2.0-5.1/+build/28285882/+files/libmpich12_4.2.0-5.1_amd64.deb' sudo dpkg --install mpich_4.2.0-5.1_amd64.deb libmpich12_4.2.0-5.1_amd64.deb + - name: Set env vars for testing (Linux) + if: startsWith(inputs.platform, "ubuntu") + run: | + echo CMAKE_BUILD_PARALLEL_LEVEL=4 >> $GITHUB_ENV + echo CMAKE_GENERATOR=Ninja >> $GITHUB_ENV + + - name: Set env vars for testing (MacOS) + if: startsWith(runner.os, "macOS") + run: | + if [[ "${{runner.os}}" == "macOS-15-intel" ]]; then + echo CMAKE_BUILD_PARALLEL_LEVEL=4 >> $GITHUB_ENV + else + echo CMAKE_BUILD_PARALLEL_LEVEL=3 >> $GITHUB_ENV + fi + echo CMAKE_GENERATOR=Ninja >> $GITHUB_ENV + - name: Test wheel with ${{ inputs.python_version }} run: | minor_version="$(python${{ inputs.python_version }} -c 'import sys;print(sys.version_info.minor)')" diff --git a/CMakeLists.txt b/CMakeLists.txt index bd728be932..422ade560c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -587,6 +587,8 @@ endif() # initialize CLI11 submodule cpp_cc_git_submodule(CLI11 BUILD PACKAGE CLI11 REQUIRED) +# coreneuron targets will get propagated down from the subdirectory + # ============================================================================= # Enable NMODL code-generator support # ============================================================================= @@ -1117,6 +1119,52 @@ if(NRN_MACOS_BUILD AND NOT SKBUILD) nrn_macos_after_install() endif() +# ============================================================================= +# Install CMake glue +# ============================================================================= +configure_file(cmake/neuronMechMaker.cmake + "${PROJECT_BINARY_DIR}/lib/cmake/neuron/neuronMechMaker.cmake" COPYONLY) +configure_file(cmake/mod_reg_nrn.cpp.in "${PROJECT_BINARY_DIR}/share/nrn/mod_reg_nrn.cpp.in" + COPYONLY) +configure_file(cmake/mod_reg_corenrn.cpp.in + "${PROJECT_BINARY_DIR}/share/nrn/mod_reg_corenrn.cpp.in" COPYONLY) +install( + TARGETS ${CORENRN_INSTALL_LIB_TARGETS} + EXPORT NeuronTargets + DESTINATION "${NRN_INSTALL_DATA_PREFIX}lib") +install( + TARGETS ${CORENRN_INSTALL_BIN_TARGETS} + EXPORT NeuronTargets + DESTINATION "${NRN_INSTALL_DATA_PREFIX}bin") +export( + EXPORT NeuronTargets + FILE ${PROJECT_BINARY_DIR}/lib/cmake/neuron/neuronTargets.cmake + NAMESPACE neuron::) + +install( + EXPORT NeuronTargets + FILE neuronTargets.cmake + NAMESPACE neuron:: + DESTINATION "${NRN_INSTALL_DATA_PREFIX}lib/cmake/neuron") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/neuronConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/neuron/neuronConfig.cmake @ONLY) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/neuron/neuronConfig.cmake + DESTINATION "${NRN_INSTALL_DATA_PREFIX}lib/cmake/neuron") +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/neuronMechMaker.cmake + DESTINATION "${NRN_INSTALL_DATA_PREFIX}lib/cmake/neuron") + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_reg_nrn.cpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_reg_corenrn.cpp.in + DESTINATION "${NRN_INSTALL_DATA_PREFIX}share/nrn") + +# configuration and installation of nrnivmodl +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/nrnivmodl.cmake + ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/neuron/nrnivmodl/CMakeLists.txt @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/neuron/nrnivmodl/CMakeLists.txt + DESTINATION "${NRN_INSTALL_DATA_PREFIX}lib/cmake/neuron/nrnivmodl") + # ============================================================================= # Copy bash executable for windows # ============================================================================= diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt index 09a3e19d1a..ed7b721ecf 100644 --- a/bin/CMakeLists.txt +++ b/bin/CMakeLists.txt @@ -47,7 +47,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nrnpyenv.sh.in ${PROJECT_BINARY_DIR}/ # Make sure nrnivmodl and neurondemo are executable in the build folder, so we can execute it to # prepare test files. This can be done more elegantly in newer CMake versions; v3.19+ have # file(CHMOD ...) and v3.20+ support setting permissions directly in configure_file(...). -set(NRN_CONFIG_EXE_FILES "nrnivmodl" "neurondemo") +set(NRNIVMODL_NEURON ON) +set(NRN_CONFIG_EXE_FILES "nrnivmodl" "neurondemo" "nrnivmodl-cmake") foreach(NRN_CONFIG_EXE_FILE ${NRN_CONFIG_EXE_FILES}) configure_file("${NRN_CONFIG_EXE_FILE}.in" "tmp/${NRN_CONFIG_EXE_FILE}" @ONLY) file( @@ -62,13 +63,39 @@ foreach(NRN_CONFIG_EXE_FILE ${NRN_CONFIG_EXE_FILES}) WORLD_READ WORLD_EXECUTE) endforeach() +unset(NRNIVMODL_NEURON) +if(NRN_ENABLE_CORENEURON) + # nrnivmodl-all-cmake builds mechanisms for both NEURON and coreNEURON + set(NRNIVMODL_NEURON ON) + set(NRNIVMODL_CORENEURON ON) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nrnivmodl-cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/tmp/nrnivmodl-all-cmake" @ONLY) + file( + COPY "${CMAKE_CURRENT_BINARY_DIR}/tmp/nrnivmodl-all-cmake" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" + FILE_PERMISSIONS + OWNER_READ + OWNER_WRITE + OWNER_EXECUTE + GROUP_READ + GROUP_EXECUTE + WORLD_READ + WORLD_EXECUTE) + unset(NRNIVMODL_NEURON) + unset(NRNIVMODL_CORENEURON) +endif() file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/bin/tmp") # ============================================================================= # Install targets # ============================================================================= install(PROGRAMS ${PROJECT_BINARY_DIR}/bin/nrngui ${PROJECT_BINARY_DIR}/bin/neurondemo - ${PROJECT_BINARY_DIR}/bin/nrnivmodl DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin) + ${PROJECT_BINARY_DIR}/bin/nrnivmodl ${PROJECT_BINARY_DIR}/bin/nrnivmodl-cmake + DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin) +if(NRN_ENABLE_CORENEURON) + install(PROGRAMS ${PROJECT_BINARY_DIR}/bin/nrnivmodl-all-cmake + DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin) +endif() install(FILES ${PROJECT_BINARY_DIR}/bin/nrnmech_makefile DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/sortspike ${CMAKE_CURRENT_SOURCE_DIR}/mkthreadsafe diff --git a/bin/nrnivmodl-cmake.in b/bin/nrnivmodl-cmake.in new file mode 100755 index 0000000000..e814499fb4 --- /dev/null +++ b/bin/nrnivmodl-cmake.in @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# ============================================================================= +# Simple wrapper for CMake that builds mechanisms from mod files +# ============================================================================= + +set -eu + +# Where the output files will be placed +bindir="$(uname -m)" + +# The name of the current program +program_name="$(basename "${0}")" + +# Where the `*.cmake` files are located +NRN_CMAKE_PREFIX_PATH_DEFAULT="$(readlink -f "$(dirname "${0}")/../lib/cmake/")" +if [ -n "${CMAKE_PREFIX_PATH:-}" ]; then + CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:${NRN_CMAKE_PREFIX_PATH_DEFAULT}" +else + CMAKE_PREFIX_PATH="${NRN_CMAKE_PREFIX_PATH_DEFAULT}" +fi +export CMAKE_PREFIX_PATH + +# On MacOS we need to set the deployment target to be equal to the one of NEURON +if command -v xcrun >& /dev/null; then + @NRN_OSX_BUILD_TRUE@export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" + @NRN_OSX_BUILD_TRUE@export MACOSX_DEPLOYMENT_TARGET="@CMAKE_OSX_DEPLOYMENT_TARGET@" + if [ -z "${MACOSX_DEPLOYMENT_TARGET}" ]; then + unset MACOSX_DEPLOYMENT_TARGET + fi +fi + +# We use the CMakeLists.txt from the NEURON installation directory to not have +# to deal with any pre-existing CMakeLists.txt in the current directory +srcdir="${NRN_CMAKE_PREFIX_PATH_DEFAULT}/neuron/nrnivmodl/" + +# In case of no files, default to using the files in the current dir +if [ $# -lt 1 ]; then + set -- ./*.mod +# In case of a single input, check if it's a directory; if it is, collect all mod files in it +elif [ $# -eq 1 ] && [ -d "${1}" ]; then + printf "[%s] Collecting mod files under %s\n" "${program_name}" "$(readlink -f "${1}")" + set -- "${1}"/*.mod +fi + +# After collecting the mod files, check each mod file actually exists +for mod_file in "$@"; do + if [ ! -e "${mod_file}" ]; then + printf "[%s] ERROR: Mod file %s does not exist!\n" "${program_name}" "${mod_file}" >&2 + exit 4 + fi +done + +# Convert all mod file paths to absolute paths because the source dir is in the NEURON install +args=() +for f in "$@"; do + resolved=$(readlink -f "$f") || exit 1 + args+=("$resolved") +done +set -- "${args[@]}" + +# TODO what if the mod filenames contain semicolons? +modfiles=$(IFS=";"; echo "$*") + +# Configure the mod files +cmake \ + -S "${srcdir}" \ + -B "${bindir}" \ + -DNRNIVMODL_MOD_FILES="${modfiles}" \ + -DNRNIVMODL_NEURON=@NRNIVMODL_NEURON@ \ + -DNRNIVMODL_CORENEURON=@NRNIVMODL_CORENEURON@ + +# Actually build them +cmake --build "${bindir}" diff --git a/ci/azure-wheel-test-upload.yml b/ci/azure-wheel-test-upload.yml index c62902a706..b6f5a61f70 100644 --- a/ci/azure-wheel-test-upload.yml +++ b/ci/azure-wheel-test-upload.yml @@ -18,6 +18,7 @@ steps: - script: | export SKIP_EMBEDED_PYTHON_TEST=true + export CMAKE_BUILD_PARALLEL_LEVEL=4 packaging/python/test_wheels.sh $(which python) $(ls -t wheelhouse/*.whl) displayName: 'Test with System Python' diff --git a/ci/requirements.txt b/ci/requirements.txt index 8a27a2e545..838129e3ac 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -2708,6 +2708,7 @@ sphinx==7.3.7 \ # sphinx-rtd-theme # sphinxcontrib-jquery # sphinxcontrib-mermaid + # sphinxcontrib-moderncmakedomain sphinx-design==0.6.1 \ --hash=sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c \ --hash=sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632 @@ -2750,6 +2751,10 @@ sphinxcontrib-mermaid==1.0.0 \ --hash=sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146 \ --hash=sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3 # via -r docs/docs_requirements.txt +sphinxcontrib-moderncmakedomain==3.29.0 \ + --hash=sha256:22385d9956e9565311eca41a09f26688312e4997a7366cb965fa610d1fd1436c \ + --hash=sha256:3587def241ff2577d0bbef11810a082e9dec1b78a3d4b4a066240b5f3dc1b5b2 + # via -r docs/docs_requirements.txt sphinxcontrib-qthelp==2.0.0 \ --hash=sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab \ --hash=sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb diff --git a/cmake/mod_reg_corenrn.cpp.in b/cmake/mod_reg_corenrn.cpp.in new file mode 100644 index 0000000000..ff71d4e6c8 --- /dev/null +++ b/cmake/mod_reg_corenrn.cpp.in @@ -0,0 +1,16 @@ +#include +namespace coreneuron { +extern int nrnmpi_myid; +extern int nrn_nobanner_; + +@MECH_DECLARE@ + +void modl_reg() { + if (!nrn_nobanner_) if (nrnmpi_myid < 1) { + fprintf(stderr, " Additional mechanisms from files\n"); + @MECH_PRINT@ + fprintf(stderr, "\n\n"); + } + @MECH_REGISTRE@ +} +} //namespace coreneuron diff --git a/cmake/mod_reg_nrn.cpp.in b/cmake/mod_reg_nrn.cpp.in new file mode 100644 index 0000000000..ae9fda03d4 --- /dev/null +++ b/cmake/mod_reg_nrn.cpp.in @@ -0,0 +1,16 @@ +#include +#include "hocdec.h" +extern int nrnmpi_myid; +extern int nrn_nobanner_; + +@MECH_DECLARE@ + +extern "C" void modl_reg() { + if (!nrn_nobanner_) if (nrnmpi_myid < 1) { + fprintf(stderr, "Additional mechanisms from files\n"); + @MECH_PRINT@ + fprintf(stderr, "\n"); + } + @MECH_REGISTRE@ +} + diff --git a/cmake/neuronConfig.cmake.in b/cmake/neuronConfig.cmake.in new file mode 100644 index 0000000000..b791720bd3 --- /dev/null +++ b/cmake/neuronConfig.cmake.in @@ -0,0 +1,25 @@ +include(CMakeFindDependencyMacro) + +find_dependency(Threads) + +include("${CMAKE_CURRENT_LIST_DIR}/neuronTargets.cmake") + +get_filename_component(_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_prefix "${_dir}/../../.." ABSOLUTE) + +set(NRN_ENABLE_CORENEURON @NRN_ENABLE_CORENEURON@) + +set(_NEURON_MAIN "${_prefix}/share/nrn/nrnmain.cpp") +set(_NEURON_MAIN_INCLUDE_DIR "${_prefix}/include/nrncvode" "${_prefix}/include") +set(_NEURON_MECH_REG "${_prefix}/share/nrn/mod_reg_nrn.cpp.in") + +set(_CORENEURON_BASE_MOD "${_prefix}/share/modfile") +set(_CORENEURON_MAIN "${_prefix}/share/coreneuron/coreneuron.cpp") +set(_CORENEURON_MECH_REG "${_prefix}/share/nrn/mod_reg_corenrn.cpp.in") +set(_CORENEURON_MECH_ENG "${_prefix}/share/coreneuron/enginemech.cpp") +set(_CORENEURON_RANDOM_INCLUDE "${_prefix}/include/coreneuron/utils/randoms" "${_prefix}/include") +set(_CORENEURON_FLAGS @CORENRN_CXX_FLAGS@) + +set(_CORENEURON_MIN_CUDA_TOOLKIT_VERSION @CORENRN_CUDA_VERSION_SHORT@) + +include("${CMAKE_CURRENT_LIST_DIR}/neuronMechMaker.cmake") diff --git a/cmake/neuronMechMaker.cmake b/cmake/neuronMechMaker.cmake new file mode 100644 index 0000000000..ece62bbf43 --- /dev/null +++ b/cmake/neuronMechMaker.cmake @@ -0,0 +1,537 @@ +#[=======================================================================[.rst: + +Mechanism building with CMake +============================= + +This module contains helper functions for generating (core)NEURON mechanism +libraries directly in CMake. The basic idea is to replicate all of the previous +functionality of the Makefiles, but without having to deal with their +maintenance or having to worry about managing dependencies. + +Overview +-------- + +What ``nrnivmodl`` and ``nrnivmodl -coreneuron`` were doing was essentially: + +- create a subdir equivalent to ``CMAKE_HOST_SYSTEM_PROCESSOR`` in the current working directory +- translate a given list of mod files to cpp files (using either NOCMODL or NMODL) +- create a file ``mod_func.cpp`` which dynamically (that is, upon running ``nrniv`` or similar) registers the mechanisms in NEURON +- create a ``nrnmech`` library in the ``CMAKE_HOST_SYSTEM_PROCESSOR`` subdirectory from all of the above listed cpp files +- create a ``special`` executable in the ``CMAKE_HOST_SYSTEM_PROCESSOR`` subdirectory from the ``nrnmain.cpp`` file +- link the above executable to the ``nrnmech`` library + +In case the ``-coreneuron`` option is given, it additionally does the following: + +- create a ``corenrnmech`` library in the ``CMAKE_HOST_SYSTEM_PROCESSOR`` subdirectory from all of the above listed cpp files, except with a different ``mod_func.cpp`` which correctly registers it under the ``coreneuron`` cpp namespace +- create a ``special-core`` executable in the ``CMAKE_HOST_SYSTEM_PROCESSOR`` subdirectory from the ``coreneuron.cpp`` file +- link the above executable to the ``corenrnmech`` library + +API reference +------------- + +.. note:: + + This API is **experimental** and subject to change. + +.. cmake:command:: create_nrnmech + + .. code-block:: cmake + + create_nrnmech( + [NEURON] + [CORENEURON] + [SPECIAL] + [NMODL_NEURON_CODEGEN] + [TARGET_LIBRARY_NAME lib_tgt] + [TARGET_EXECUTABLE_NAME exe_tgt] + [LIBRARY_OUTPUT_DIR lib_outdir] + [EXECUTABLE_OUTPUT_DIR exe_outdir] + [ARTIFACTS_OUTPUT_DIR art_outdir] + [LIBRARY_TYPE type] + [NOCMODL_EXECUTABLE path/to/nocmodl] + [NMODL_EXECUTABLE path/to/nmodl] + [MOD_FILES mod1 mod2 ...] + [NMODL_NEURON_EXTRA_ARGS arg1 arg2 ...] + [NMODL_CORENEURON_EXTRA_ARGS arg1 arg2 ...] + [EXTRA_ENV KEY1=VAL1 KEY2=VAL2 ...] + ) + + **Options** + + ``NEURON`` + (*optional*) whether a library compatible with NEURON should be created. + + ``CORENEURON`` + (*optional*) whether a library compatible with coreNEURON should be created. At least one of ``NEURON`` or ``CORENEURON`` must be specified. + + ``SPECIAL`` + (*optional*) whether a ``special`` (or ``special-core`` in case of coreNEURON) executable should be created. + + ``NMODL_NEURON_CODEGEN`` + (*optional*) whether to use NMODL to generate files compatible with NEURON. + + ``TARGET_LIBRARY_NAME`` + (*optional*, default: ``nrnmech``) the name of the CMake target for the library. Note that ``core`` is prepended to the coreNEURON target. + + ``TARGET_EXECUTABLE_NAME`` + (*optional*, default: ``special``) the name of the CMake target for the executable. Note that ``-core`` is appended to the coreNEURON target. + + ``LIBRARY_OUTPUT_DIR`` + (*optional*, default: ``CMAKE_CURRENT_BINARY_DIR``) the path where the library targets will be placed at build-time. + + ``EXECUTABLE_OUTPUT_DIR`` + (*optional*, default: ``CMAKE_CURRENT_BINARY_DIR``) the path where the executable targets will be placed at build-time. + + ``ARTIFACTS_OUTPUT_DIR`` + (*optional*, default: ``CMAKE_CURRENT_BINARY_DIR``) the path where the CPP files will be placed at build-time. + + ``LIBRARY_TYPE`` + (*optional*, default: ``SHARED``) the type of library to build. + + ``NOCMODL_EXECUTABLE`` + (*optional*) the path to the NOCMODL executable. If not specified, attempts to find deduce the location of the executable from the NEURON CMake configuration. + + ``NMODL_EXECUTABLE`` + (*optional*) the path to the NMODL executable. If not specified, attempts to find deduce the location of the executable from the NEURON CMake configuration. + + ``MOD_FILES`` + list of mod files to convert. + + ``NMODL_NEURON_EXTRA_ARGS`` + (*optional*, default: None) list of additional arguments to pass to NMODL for NEURON codegen. + + ``NMODL_CORENEURON_EXTRA_ARGS`` + (*optional*, default: ``passes --inline host --c`` if CUDA disabled, ``passes --inline host --c acc --oacc`` if CUDA enabled) list of additional arguments to pass to NMODL for coreNEURON codegen. + + ``EXTRA_ENV`` + (*optional*, default: None) list of additional environmental variables to pass when building the targets. + + +Examples +-------- + +.. note:: + + You need to insert the path to ``neuronTargets.cmake`` in ``CMAKE_PREFIX_PATH``. + If you installed NEURON via CMake, the usual loaction of it is ``CMAKE_INSTALL_PREFIX/lib/cmake``. + If instead you installed NEURON as a Python wheel, the usual location is ``WHEEL_INSTALL_DIR/neuron/.data/lib/make``, where ``WHEEL_INSTALL_DIR`` is listed after ``Location:`` when calling ``pip show neuron``. + +To build a mechanism, put this in your ``CMakeLists.txt``: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.15) + project(custom_modfiles LANGUAGES C CXX) + + find_package(neuron REQUIRED) + + create_nrnmech(NEURON CORENEURON SPECIAL MOD_FILES + modfile1.mod + path/to/modfile2.mod) + +.. note:: + + If you want to enable coreNEURON's GPU support, you need to first build NEURON itself with ``CORENRN_ENABLE_GPU=ON`` and customize the ``CMAKE_C_COMPILER``, ``CMAKE_CXX_COMPILER``, and ``CMAKE_CUDA_COMPILER`` variables. + Currently only NVHPC is supported. + You also need to add ``CUDA`` to the ``LANGUAGES`` above. + +Then you can configure your mechanisms using: + +.. code-block:: sh + + cmake -B build + +Any CMake option (such as the compiler, generator, etc.) can be specified. +To build the mechanisms (i.e. the ``nrnmech`` library and ``special`` executable), run: + +.. code-block:: sh + + cmake --build build + +The ``nrnmech`` library will then be available under ``build``, and can be loaded in NEURON using: + +.. code-block:: sh + + nrniv -dll build/libnrnmech.so + +#]=======================================================================] + +function(create_nrnmech) + set(options NEURON CORENEURON SPECIAL NMODL_NEURON_CODEGEN) + set(oneValueArgs + MECHANISM_NAME + TARGET_LIBRARY_NAME + TARGET_EXECUTABLE_NAME + LIBRARY_OUTPUT_DIR + EXECUTABLE_OUTPUT_DIR + ARTIFACTS_OUTPUT_DIR + LIBRARY_TYPE + NOCMODL_EXECUTABLE + NMODL_EXECUTABLE) + set(multiValueArgs MOD_FILES NMODL_NEURON_EXTRA_ARGS NMODL_CORENEURON_EXTRA_ARGS EXTRA_ENV) + cmake_parse_arguments(NRN_MECH "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # The message priority for logging + set(MESSAGE_PRIORITY "VERBOSE") + + # The name of the output library + set(LIBNAME "nrnmech") + + # The name of the output executable + set(EXENAME "special") + + # The default type of the output library. Note that `nrnmech` and `corenrnmech` are actually + # module libraries (since they are loaded via `dlopen`-like functionality), but a module library + # cannot be linked to, and since `special` must link to `nrnmech`, so to avoid having to compile + # the source files twice, we use a shared library instead. + set(DEFAULT_LIBRARY_TYPE "SHARED") + + # The default name of the mechanism + set(DEFAULT_MECHANISM_NAME "neuron") + + if(NRN_MECH_CORENEURON) + if(NOT NRN_ENABLE_CORENEURON) + message(FATAL_ERROR "CoreNEURON support not enabled") + endif() + endif() + + if(NOT NRN_MECH_NEURON AND NOT NRN_MECH_CORENEURON) + message( + FATAL_ERROR + "No output specified for mod files, please specify at least one of `NEURON` or `CORENEURON` outputs" + ) + endif() + + if(NOT NRN_MECH_MOD_FILES) + message(FATAL_ERROR "No input mod files specified!") + endif() + + if(NRN_MECH_NEURON) + message("${MESSAGE_PRIORITY}" "NEURON | enabled") + else() + message("${MESSAGE_PRIORITY}" "NEURON | disabled") + endif() + + if(NRN_MECH_CORENEURON) + message("${MESSAGE_PRIORITY}" "coreNEURON | enabled") + else() + message("${MESSAGE_PRIORITY}" "coreNEURON | disabled") + endif() + + if(NRN_MECH_SPECIAL) + message("${MESSAGE_PRIORITY}" "special executable | enabled") + else() + message("${MESSAGE_PRIORITY}" "special executable | disabled") + endif() + + if(NRN_MECH_MECHANISM_NAME) + set(MECHANISM_NAME "${NRN_MECH_MECHANISM_NAME}") + else() + set(MECHANISM_NAME "${DEFAULT_MECHANISM_NAME}") + endif() + + message("${MESSAGE_PRIORITY}" "MECHANISM_NAME | ${MECHANISM_NAME}") + + # the `nmodl` and `nocmodl` executables are usually found through `find_program` on the user's + # system, but we allow overrides (for testing purposes only) + if(NRN_MECH_NMODL_EXECUTABLE) + set(NMODL_EXECUTABLE "${NRN_MECH_NMODL_EXECUTABLE}") + else() + set(NMODL_EXECUTABLE $) + endif() + + message("${MESSAGE_PRIORITY}" "NMODL_EXECUTABLE | ${NMODL_EXECUTABLE}") + + if(NRN_MECH_NOCMODL_EXECUTABLE) + set(NOCMODL_EXECUTABLE "${NRN_MECH_NOCMODL_EXECUTABLE}") + else() + set(NOCMODL_EXECUTABLE $) + endif() + + message("${MESSAGE_PRIORITY}" "NOCMODL_EXECUTABLE | ${NOCMODL_EXECUTABLE}") + + # the option `CORENRN_ENABLE_SHARED` toggles the kind of library we want to build, so we respect + # it here + if(NRN_MECH_LIBRARY_TYPE) + set(LIBRARY_TYPE "${NRN_MECH_LIBRARY_TYPE}") + else() + set(LIBRARY_TYPE "${DEFAULT_LIBRARY_TYPE}") + endif() + + message("${MESSAGE_PRIORITY}" "LIBRARY_TYPE | ${LIBRARY_TYPE}") + + # nmodl by default generates code for coreNEURON, so we toggle this via an option + if(NRN_MECH_NMODL_NEURON_CODEGEN) + set(NEURON_TRANSPILER_LAUNCHER ${NMODL_EXECUTABLE} --neuron) + else() + set(NEURON_TRANSPILER_LAUNCHER ${NOCMODL_EXECUTABLE}) + endif() + + # raise warning that NMODL extra args will be ignored if we use NEURON codegen with NOCMODL + if(NRN_MECH_NEURON + AND NOT NRN_MECH_NMODL_NEURON_CODEGEN + AND NRN_MECH_NMODL_NEURON_EXTRA_ARGS) + message( + WARNING + "${CMAKE_CURRENT_FUNCTION}: requested NEURON library with NOCMODL codegen, but NMODL_NEURON_EXTRA_ARGS is not empty; " + "will ignore NMODL_NEURON_EXTRA_ARGS when building NEURON library.\n" + "Hint: if you want to use NMODL for codegen for NEURON, add the NMODL_NEURON_CODEGEN option when calling this function." + ) + set(NRN_MECH_NMODL_NEURON_EXTRA_ARGS) + endif() + + list(JOIN NRN_MECH_NMODL_NEURON_EXTRA_ARGS "" NMODL_NEURON_EXTRA_ARGS_SPACES) + message("${MESSAGE_PRIORITY}" "NMODL_NEURON_EXTRA_ARGS | ${NMODL_NEURON_EXTRA_ARGS_SPACES}") + + # set default flags for NMODL for coreNEURON. + if(NOT NRN_MECH_NMODL_CORENEURON_EXTRA_ARGS) + set(NRN_MECH_NMODL_CORENEURON_EXTRA_ARGS passes --inline) + list(APPEND NRN_MECH_NMODL_CORENEURON_EXTRA_ARGS host --c) + # OpenACC flags + if(CMAKE_CUDA_COMPILER) + list(APPEND NRN_MECH_NMODL_CORENEURON_EXTRA_ARGS acc --oacc) + endif() + endif() + + list(JOIN NRN_MECH_NMODL_CORENEURON_EXTRA_ARGS " " NMODL_CORENEURON_EXTRA_ARGS_SPACES) + message("${MESSAGE_PRIORITY}" + "NMODL_CORENEURON_EXTRA_ARGS | ${NMODL_CORENEURON_EXTRA_ARGS_SPACES}") + + # any extra environment variables that need to be passed (for testing purposes only). Because + # CMake likes to escape and quote things, we need to do it the roundabout way... + if(NRN_MECH_EXTRA_ENV) + set(ENV_COMMAND "${CMAKE_COMMAND}" -E env ${NRN_MECH_EXTRA_ENV}) + else() + set(ENV_COMMAND) + endif() + + message("${MESSAGE_PRIORITY}" "EXTRA_ENV | ${NRN_MECH_EXTRA_ENV}") + + # nocmodl sometimes does not work due to lack of MODLUNIT, see: + # https://github.com/neuronsimulator/nrn/issues/3470 + if(DEFINED ENV{MODLUNIT}) + list(APPEND ENV_COMMAND "MODLUNIT=$ENV{MODLUNIT}") + endif() + + # Override the _target_ name, but not the library name. This is useful when we are using this + # function for building NEURON components, since we may experience collisions in the target names + if(NRN_MECH_TARGET_LIBRARY_NAME) + set(TARGET_LIBRARY_NAME "${NRN_MECH_TARGET_LIBRARY_NAME}") + else() + set(TARGET_LIBRARY_NAME "${LIBNAME}") + endif() + + message("${MESSAGE_PRIORITY}" "TARGET_LIBRARY_NAME | ${TARGET_LIBRARY_NAME}") + + # Override the _target_ name, but not the executable name. This is useful when we are using this + # function for building NEURON components, since we may experience collisions in the target names + if(NRN_MECH_TARGET_EXECUTABLE_NAME) + set(TARGET_EXECUTABLE_NAME "${NRN_MECH_TARGET_EXECUTABLE_NAME}") + else() + set(TARGET_EXECUTABLE_NAME "${EXENAME}") + endif() + + message("${MESSAGE_PRIORITY}" "TARGET_EXECUTABLE_NAME | ${TARGET_EXECUTABLE_NAME}") + + # Where to output the library (during build) + if(NRN_MECH_LIBRARY_OUTPUT_DIR) + set(LIBRARY_OUTPUT_DIR "${NRN_MECH_LIBRARY_OUTPUT_DIR}") + else() + set(LIBRARY_OUTPUT_DIR "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + endif() + + message("${MESSAGE_PRIORITY}" "LIBRARY_OUTPUT_DIR | ${LIBRARY_OUTPUT_DIR}") + + # Where to output the executable (during build) + if(NRN_MECH_EXECUTABLE_OUTPUT_DIR) + set(EXECUTABLE_OUTPUT_DIR "${NRN_MECH_EXECUTABLE_OUTPUT_DIR}") + else() + set(EXECUTABLE_OUTPUT_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + endif() + + message("${MESSAGE_PRIORITY}" "EXECUTABLE_OUTPUT_DIR | ${EXECUTABLE_OUTPUT_DIR}") + + # Where the intermediate CPP files will be placed + if(NRN_MECH_ARTIFACTS_OUTPUT_DIR) + set(ARTIFACTS_OUTPUT_DIR "${NRN_MECH_ARTIFACTS_OUTPUT_DIR}") + else() + set(ARTIFACTS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + message("${MESSAGE_PRIORITY}" "ARTIFACTS_OUTPUT_DIR | ${ARTIFACTS_OUTPUT_DIR}") + + # Collect mod files, output any warnings + set(MOD_FILES "") + foreach(MOD_FILE IN LISTS NRN_MECH_MOD_FILES) + if(NOT MOD_FILE MATCHES ".*mod$") + message(WARNING "File ${MOD_FILE} has an extension that is not .mod, compilation may fail") + endif() + get_filename_component(MOD_STUB "${MOD_FILE}" NAME_WLE) + list(APPEND INPUT_STUBS "${MOD_STUB}") + list(APPEND MOD_FILES "${MOD_FILE}") + endforeach() + + message("${MESSAGE_PRIORITY}" "MOD_FILES | ${MOD_FILES}") + + # We later include the directories where the mod files are in case people add headers in VERBATIM + # blocks + set(MOD_DIRECTORIES) + + # Convert mod files for use with NEURON + if(NRN_MECH_NEURON) + # Convert to CPP files + foreach(MOD_FILE IN LISTS MOD_FILES) + get_filename_component(MOD_STUB "${MOD_FILE}" NAME_WLE) + # nocmodl has trouble with symlinks, so we always use the real path + get_filename_component(MOD_ABSPATH "${MOD_FILE}" REALPATH) + get_filename_component(MOD_DIRECTORY "${MOD_ABSPATH}" DIRECTORY) + if(NOT MOD_DIRECTORY IN_LIST MOD_DIRECTORIES) + list(APPEND MOD_DIRECTORIES "${MOD_DIRECTORY}") + endif() + set(CPP_FILE "cpp/${MOD_STUB}.cpp") + file(RELATIVE_PATH MOD_SHORT "${CMAKE_SOURCE_DIR}" "${MOD_ABSPATH}") + + list(APPEND L_MECH_DECLARE "extern \"C\" void _${MOD_STUB}_reg(void)\;") + list(APPEND L_MECH_PRINT "fprintf(stderr, \" \\\"${MOD_SHORT}\\\"\")\;") + list(APPEND L_MECH_REGISTRE "_${MOD_STUB}_reg()\;") + + add_custom_command( + COMMAND ${ENV_COMMAND} ${NEURON_TRANSPILER_LAUNCHER} -o "${ARTIFACTS_OUTPUT_DIR}/cpp" + "${MOD_ABSPATH}" ${NRN_MECH_NMODL_NEURON_EXTRA_ARGS} + OUTPUT "${ARTIFACTS_OUTPUT_DIR}/${CPP_FILE}" + COMMENT "Converting ${MOD_ABSPATH} to ${ARTIFACTS_OUTPUT_DIR}/${CPP_FILE}" + # TODO some mod files may include other files, and NMODL can get the AST of a given file in + # JSON form, which we could potentially parse with CMake and get the full list of + # dependencies + DEPENDS "${MOD_ABSPATH}" + VERBATIM) + + list(APPEND L_SOURCES "${ARTIFACTS_OUTPUT_DIR}/${CPP_FILE}") + endforeach() + + # add the nrnmech library + add_library(${TARGET_LIBRARY_NAME} ${LIBRARY_TYPE} ${L_SOURCES}) + set_target_properties( + ${TARGET_LIBRARY_NAME} PROPERTIES OUTPUT_NAME "${LIBNAME}" LIBRARY_OUTPUT_DIRECTORY + "${LIBRARY_OUTPUT_DIR}") + target_link_libraries(${TARGET_LIBRARY_NAME} PUBLIC neuron::nrniv) + + # we need to add the `mech_func.cpp` file as well since it handles registration of mechanisms + list(JOIN L_MECH_DECLARE "\n" MECH_DECLARE) + list(JOIN L_MECH_PRINT " \n" MECH_PRINT) + list(JOIN L_MECH_REGISTRE " \n" MECH_REGISTRE) + get_filename_component(MECH_REG "${_NEURON_MECH_REG}" NAME_WLE) + configure_file(${_NEURON_MECH_REG} "${ARTIFACTS_OUTPUT_DIR}/${MECH_REG}" @ONLY) + target_sources(${TARGET_LIBRARY_NAME} PRIVATE "${ARTIFACTS_OUTPUT_DIR}/${MECH_REG}") + target_compile_definitions(${TARGET_LIBRARY_NAME} PUBLIC AUTO_DLOPEN_NRNMECH=0) + target_include_directories(${TARGET_LIBRARY_NAME} BEFORE PUBLIC ${_NEURON_MAIN_INCLUDE_DIR}) + # sometimes people will add `#include`s in VERBATIM blocks; usually those are in the same + # directory as the mod file, so let's add that as well + target_include_directories(${TARGET_LIBRARY_NAME} PRIVATE "${MOD_DIRECTORIES}") + + # add the special executable + if(NRN_MECH_SPECIAL) + add_executable(${TARGET_EXECUTABLE_NAME} ${_NEURON_MAIN} + "${ARTIFACTS_OUTPUT_DIR}/${MECH_REG}") + target_include_directories(${TARGET_EXECUTABLE_NAME} BEFORE + PUBLIC ${_NEURON_MAIN_INCLUDE_DIR}) + target_link_libraries(${TARGET_EXECUTABLE_NAME} PUBLIC ${TARGET_LIBRARY_NAME}) + set_target_properties( + ${TARGET_EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME "special" RUNTIME_OUTPUT_DIRECTORY + "${EXECUTABLE_OUTPUT_DIR}") + endif() + + endif() + + # Convert mod files for use with coreNEURON + if(NRN_MECH_CORENEURON) + # CoreNEURON requires additional mod files. Only append them to the input list if similar named + # mods are _not yet present_ + file(GLOB BASE_MOD_FILES "${_CORENEURON_BASE_MOD}/*.mod") + foreach(MOD_FILE IN LISTS BASE_MOD_FILES) + get_filename_component(MOD_STUB "${MOD_FILE}" NAME_WLE) + if(NOT "${MOD_STUB}" IN_LIST INPUT_STUBS) + list(APPEND MOD_FILES "${MOD_FILE}") + endif() + endforeach() + + # Convert to CPP files + foreach(MOD_FILE IN LISTS MOD_FILES) + get_filename_component(MOD_STUB "${MOD_FILE}" NAME_WLE) + # nmodl _may_ have trouble with symlinks, so we always use the real path + get_filename_component(MOD_ABSPATH "${MOD_FILE}" REALPATH) + set(CPP_FILE "cpp_core/${MOD_STUB}.cpp") + file(RELATIVE_PATH MOD_SHORT "${CMAKE_SOURCE_DIR}" "${MOD_ABSPATH}") + + list(APPEND L_CORE_MECH_DECLARE "extern int _${MOD_STUB}_reg(void)\;") + list(APPEND L_CORE_MECH_PRINT "fprintf(stderr, \" \\\"${MOD_SHORT}\\\"\")\;") + list(APPEND L_CORE_MECH_REGISTRE "_${MOD_STUB}_reg()\;") + + add_custom_command( + COMMAND ${ENV_COMMAND} ${NMODL_EXECUTABLE} -o "${ARTIFACTS_OUTPUT_DIR}/cpp_core" + "${MOD_ABSPATH}" ${NRN_MECH_NMODL_CORENEURON_EXTRA_ARGS} + OUTPUT "${ARTIFACTS_OUTPUT_DIR}/${CPP_FILE}" + COMMENT "Converting ${MOD_ABSPATH} to ${ARTIFACTS_OUTPUT_DIR}/${CPP_FILE}" + DEPENDS "${MOD_ABSPATH}" + VERBATIM) + + list(APPEND L_CORE_SOURCES "${ARTIFACTS_OUTPUT_DIR}/${CPP_FILE}") + endforeach() + + add_library(core${TARGET_LIBRARY_NAME} ${LIBRARY_TYPE} ${_CORENEURON_MECH_ENG} + ${L_CORE_SOURCES}) + set_target_properties( + core${TARGET_LIBRARY_NAME} PROPERTIES OUTPUT_NAME "core${LIBNAME}" LIBRARY_OUTPUT_DIRECTORY + "${LIBRARY_OUTPUT_DIR}") + target_include_directories(core${TARGET_LIBRARY_NAME} BEFORE + PUBLIC ${_CORENEURON_RANDOM_INCLUDE}) + target_compile_options(core${TARGET_LIBRARY_NAME} BEFORE PRIVATE ${_CORENEURON_FLAGS}) + target_link_libraries(core${TARGET_LIBRARY_NAME} PUBLIC neuron::corenrn) + target_compile_definitions(core${TARGET_LIBRARY_NAME} PUBLIC ADDITIONAL_MECHS) + + if(CMAKE_CUDA_COMPILER) + # Find the cuda toolkit and openacc (if not found already) + if(NOT CUDAToolkit_FOUND) + find_package(CUDAToolkit ${_CORENEURON_MIN_CUDA_TOOLKIT_VERSION} REQUIRED) + endif() + if(NOT OpenACC_FOUND) + find_package(OpenACC REQUIRED) + endif() + # Random123 does not play nicely with NVHPC + target_compile_definitions(core${TARGET_LIBRARY_NAME} PUBLIC R123_USE_INTRIN_H=0) + # if using NVHPC, link corenrnmech lib to the CUDA runtime and openacc + if("${LIBRARY_TYPE}" STREQUAL "STATIC") + target_link_libraries(core${TARGET_LIBRARY_NAME} PUBLIC CUDA::cudart_static + OpenACC::OpenACC_CXX) + elseif("${LIBRARY_TYPE}" STREQUAL "SHARED") + target_link_libraries(core${TARGET_LIBRARY_NAME} PUBLIC CUDA::cudart OpenACC::OpenACC_CXX) + else() + message(FATAL_ERROR "Unsupported library type for CUDA: ${LIBRARY_TYPE}") + endif() + # for some reason we need to add `-cuda` to nrnmech (which gets propagated to special-core) + target_link_options(core${TARGET_LIBRARY_NAME} PUBLIC "-cuda") + endif() + + list(JOIN L_CORE_MECH_DECLARE "\n" MECH_DECLARE) + list(JOIN L_CORE_MECH_PRINT " \n" MECH_PRINT) + list(JOIN L_CORE_MECH_REGISTRE " \n" MECH_REGISTRE) + + get_filename_component(CORE_MECH_REG "${_CORENEURON_MECH_REG}" NAME_WLE) + configure_file(${_CORENEURON_MECH_REG} "${ARTIFACTS_OUTPUT_DIR}/core${CORE_MECH_REG}" @ONLY) + + target_sources(core${TARGET_LIBRARY_NAME} + PRIVATE "${ARTIFACTS_OUTPUT_DIR}/core${CORE_MECH_REG}") + + if(NRN_MECH_SPECIAL) + add_executable(core${TARGET_EXECUTABLE_NAME} ${_CORENEURON_MAIN} + "${ARTIFACTS_OUTPUT_DIR}/core${CORE_MECH_REG}") + target_include_directories(core${TARGET_EXECUTABLE_NAME} BEFORE + PUBLIC ${_NEURON_MAIN_INCLUDE_DIR}) + target_link_libraries(core${TARGET_EXECUTABLE_NAME} PUBLIC core${TARGET_LIBRARY_NAME}) + target_compile_definitions(core${TARGET_EXECUTABLE_NAME} PUBLIC ADDITIONAL_MECHS) + set_target_properties( + core${TARGET_EXECUTABLE_NAME} + PROPERTIES OUTPUT_NAME "special-core" RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_DIR}") + endif() + endif() +endfunction() diff --git a/cmake/nrnivmodl.cmake b/cmake/nrnivmodl.cmake new file mode 100644 index 0000000000..bb9fb8a405 --- /dev/null +++ b/cmake/nrnivmodl.cmake @@ -0,0 +1,43 @@ +# ============================================================== +# CMake wrapper for generating mechanisms for (core)NEURON +# ============================================================== + +cmake_minimum_required(VERSION @CMAKE_MINIMUM_REQUIRED_VERSION@) +project(nrnivmodl) + +find_package(neuron REQUIRED) + +set(NRNIVMODL_MOD_FILES + "" + CACHE STRING "List of mod files to convert to mechanisms") +set(NRNIVMODL_NEURON + ON + CACHE BOOL "Whether to generate mechanisms for NEURON") +set(NRNIVMODL_CORENEURON + OFF + CACHE BOOL "Whether to generate mechanisms for coreNEURON") +set(NRNIVMODL_SPECIAL + ON + CACHE BOOL "Whether to generate the `special` executable") + +set(NRNIVMODL_ARGS) +if(NRNIVMODL_NEURON) + list(APPEND NRNIVMODL_ARGS "NEURON") +endif() +if(NRNIVMODL_CORENEURON) + list(APPEND NRNIVMODL_ARGS "CORENEURON") +endif() +if(NRNIVMODL_SPECIAL) + list(APPEND NRNIVMODL_ARGS "SPECIAL") +endif() + +message(STATUS "Received mod files: ${NRNIVMODL_MOD_FILES}") + +create_nrnmech( + ${NRNIVMODL_ARGS} + MOD_FILES + ${NRNIVMODL_MOD_FILES} + LIBRARY_OUTPUT_DIR + "${CMAKE_CURRENT_BINARY_DIR}" + EXECUTABLE_OUTPUT_DIR + "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/docs/conf.py b/docs/conf.py index 9be5e7704e..b0ed359074 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,6 +43,7 @@ "sphinx_design", "sphinx_inline_tabs", "sphinxcontrib.mermaid", + "sphinxcontrib.moderncmakedomain", ] source_suffix = { diff --git a/docs/dev/index.rst b/docs/dev/index.rst index 19b3402981..97d1a798cb 100644 --- a/docs/dev/index.rst +++ b/docs/dev/index.rst @@ -10,7 +10,7 @@ NEURON Development topics gpu-testing.rst nmodl-development.rst workflow-code-paths.rst + nrnivmodl-cmake.rst ./python/wheels.md ./morphology/morphology.md hocdomain-sphinx.md - diff --git a/docs/dev/nrnivmodl-cmake.rst b/docs/dev/nrnivmodl-cmake.rst new file mode 100644 index 0000000000..5f242aca84 --- /dev/null +++ b/docs/dev/nrnivmodl-cmake.rst @@ -0,0 +1 @@ +.. cmake-module:: ../../cmake/neuronMechMaker.cmake diff --git a/docs/dev/workflow-code-paths.rst b/docs/dev/workflow-code-paths.rst index 498557894d..b13bb833aa 100644 --- a/docs/dev/workflow-code-paths.rst +++ b/docs/dev/workflow-code-paths.rst @@ -22,6 +22,10 @@ The ``nrnivmodl`` executable translates the ``MOD`` files from the ``NMODL`` lan .. raw:: html :file: mechanism_registration.svg +.. note:: + + Have a look at :doc:`nrnivmodl-cmake` for a more modern (experimental!) way of building mechanisms. + Circuit creation ^^^^^^^^^^^^^^^^ diff --git a/docs/docs_requirements.txt b/docs/docs_requirements.txt index 7b7dddb337..bfa94f6f20 100644 --- a/docs/docs_requirements.txt +++ b/docs/docs_requirements.txt @@ -23,3 +23,4 @@ packaging<=24.2,>=22.0 tenacity<8.4 anywidget sphinxcontrib-mermaid<=1.0.0 +sphinxcontrib-moderncmakedomain<=3.29.0 diff --git a/packaging/python/test_wheels.sh b/packaging/python/test_wheels.sh index a48470c4c3..e3b80b30c3 100755 --- a/packaging/python/test_wheels.sh +++ b/packaging/python/test_wheels.sh @@ -57,42 +57,48 @@ run_mpi_test () { rm -rf *.dat fi - # build new special - rm -rf $ARCH_DIR - nrnivmodl tmp_mod - - # run python test via nrniv and special (except on azure pipelines) - if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then - $mpi_launcher -n 2 ./$ARCH_DIR/special -python src/parallel/test0.py -mpi --expected-hosts 2 - $mpi_launcher -n 2 nrniv -python src/parallel/test0.py -mpi --expected-hosts 2 - fi - - # coreneuron execution via neuron - if [[ "$has_coreneuron" == "true" ]]; then - rm -rf $ARCH_DIR - TEMP_DIR="${TMPDIR:-/tmp}/test/coreneuron/mod files/" - if [ ! -d "${TEMP_DIR}" ]; then - mkdir -p "${TEMP_DIR}" - fi - cp "test/coreneuron/mod files/"* "${TEMP_DIR}/" - # also copy one MOD file containing sparse solver - cp share/examples/nrniv/nmodl/capmp.mod "${TEMP_DIR}" - nrnivmodl -coreneuron "${TEMP_DIR}" + compilers=("nrnivmodl" "nrnivmodl-cmake") + core_compilers=("nrnivmodl -coreneuron" "nrnivmodl-all-cmake") + for index in "${!compilers[@]}"; do + nrnivmodl="${compilers[$index]}" + nrnivmodl_core="${core_compilers[$index]}" + # build new special + rm -rf $ARCH_DIR + ${nrnivmodl} tmp_mod - $mpi_launcher -n 1 $python_exe test/coreneuron/test_direct.py + # run python test via nrniv and special (except on azure pipelines) + if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then + $mpi_launcher -n 2 ./$ARCH_DIR/special -python src/parallel/test0.py -mpi --expected-hosts 2 + $mpi_launcher -n 2 nrniv -python src/parallel/test0.py -mpi --expected-hosts 2 + fi - # using -python doesn't work on Azure CI - if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then - $mpi_launcher -n 2 nrniv -python -mpi test/coreneuron/test_direct.py - NVCOMPILER_ACC_TIME=1 CORENRN_ENABLE_GPU=0 $mpi_launcher -n 2 ./$ARCH_DIR/special -python -mpi test/coreneuron/test_direct.py - fi - fi + # coreneuron execution via neuron + if [[ "$has_coreneuron" == "true" ]]; then + rm -rf $ARCH_DIR + TEMP_DIR="${TMPDIR:-/tmp}/test/coreneuron/mod files/" + if [ ! -d "${TEMP_DIR}" ]; then + mkdir -p "${TEMP_DIR}" + fi + cp "test/coreneuron/mod files/"* "${TEMP_DIR}/" + # also copy one MOD file containing sparse solver + cp share/examples/nrniv/nmodl/capmp.mod "${TEMP_DIR}" + ${nrnivmodl_core} "${TEMP_DIR}" + + $mpi_launcher -n 1 $python_exe test/coreneuron/test_direct.py + + # using -python doesn't work on Azure CI + if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then + $mpi_launcher -n 2 nrniv -python -mpi test/coreneuron/test_direct.py + NVCOMPILER_ACC_TIME=1 CORENRN_ENABLE_GPU=0 $mpi_launcher -n 2 ./$ARCH_DIR/special -python -mpi test/coreneuron/test_direct.py + fi + fi - if [ -n "$mpi_module" ]; then - echo "Unloading module $mpi_module" - module unload $mpi_module - fi - echo -e "----------------------\n\n" + if [ -n "$mpi_module" ]; then + echo "Unloading module $mpi_module" + module unload $mpi_module + fi + echo -e "----------------------\n\n" + done } @@ -113,47 +119,53 @@ run_serial_test () { rm -rf *.dat fi - # Test 4: execute nrnivmodl - rm -rf $ARCH_DIR - nrnivmodl tmp_mod + # Test 4: execute nrnivmodl and friends + compilers=("nrnivmodl" "nrnivmodl-cmake") + for compiler in "${compilers[@]}"; do + rm -rf $ARCH_DIR + ${compiler} tmp_mod - # Test 5: execute special hoc interpreter - ./$ARCH_DIR/special -c "print \"hello\"" + # Test 5: execute special hoc interpreter + ./$ARCH_DIR/special -c "print \"hello\"" - # Test 6: run basic tests via python while loading shared library - $python_exe -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" + # Test 6: run basic tests via python while loading shared library + $python_exe -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" - # Test 7: run basic test to use compiled mod file - $python_exe -c "import neuron; from neuron import h; s = h.Section(); s.insert('cacum'); quit()" + # Test 7: run basic test to use compiled mod file + $python_exe -c "import neuron; from neuron import h; s = h.Section(); s.insert('cacum'); quit()" - # Test 8: run basic tests via special : azure pipelines get stuck with their - # own python from hosted cache (most likely security settings). - if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then - ./$ARCH_DIR/special -python -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" - nrniv -python -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" - else - $python_exe -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" - fi + # Test 8: run basic tests via special : azure pipelines get stuck with their + # own python from hosted cache (most likely security settings). + if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then + ./$ARCH_DIR/special -python -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" + nrniv -python -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" + else + $python_exe -c "import neuron; neuron.test(); neuron.test_rxd(); quit()" + fi + done # Test 9: coreneuron execution via neuron if [[ "$has_coreneuron" == "true" ]]; then - rm -rf $ARCH_DIR + compilers=("nrnivmodl -coreneuron" "nrnivmodl-all-cmake") + for compiler in "${compilers[@]}"; do + rm -rf $ARCH_DIR - # first test vanialla coreneuron support, without nrnivmodl - $python_exe test/coreneuron/test_psolve.py + # first test vanialla coreneuron support, without nrnivmodl + $python_exe test/coreneuron/test_psolve.py - nrnivmodl -coreneuron "test/coreneuron/mod files/" + ${compiler} "test/coreneuron/mod files/" - # coreneuron+gpu can be used via python but special only - $python_exe test/coreneuron/test_direct.py + # coreneuron+gpu can be used via python but special only + $python_exe test/coreneuron/test_direct.py - # using -python doesn't work on Azure CI - if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then - ./$ARCH_DIR/special -python test/coreneuron/test_direct.py - nrniv -python test/coreneuron/test_direct.py - fi + # using -python doesn't work on Azure CI + if [[ "$SKIP_EMBEDED_PYTHON_TEST" != "true" ]]; then + ./$ARCH_DIR/special -python test/coreneuron/test_direct.py + nrniv -python test/coreneuron/test_direct.py + fi - rm -rf $ARCH_DIR + rm -rf $ARCH_DIR + done fi diff --git a/share/lib/python/scripts/CMakeLists.txt b/share/lib/python/scripts/CMakeLists.txt index 23a34609a0..11a6998984 100644 --- a/share/lib/python/scripts/CMakeLists.txt +++ b/share/lib/python/scripts/CMakeLists.txt @@ -11,7 +11,8 @@ if(SKBUILD) nrnivmodl nrnivmodl-core nrnpyenv.sh - sortspike) + sortspike + nrnivmodl-cmake) foreach(script IN LISTS scripts) install( @@ -19,4 +20,12 @@ if(SKBUILD) DESTINATION "${SKBUILD_SCRIPTS_DIR}" RENAME "${script}") endforeach() + if(NRN_ENABLE_CORENEURON) + install( + PROGRAMS binwrapper.py + DESTINATION "${SKBUILD_SCRIPTS_DIR}" + RENAME "nrnivmodl-all-cmake") + endif() + # in order for mechanism building via CMake to work with a Python wheel, we need `binwrapper.py` + install(PROGRAMS binwrapper.py DESTINATION "${NRN_INSTALL_DATA_PREFIX}share/nrn") endif() diff --git a/src/coreneuron/CMakeLists.txt b/src/coreneuron/CMakeLists.txt index 23190b1cfe..0e89125600 100644 --- a/src/coreneuron/CMakeLists.txt +++ b/src/coreneuron/CMakeLists.txt @@ -390,8 +390,21 @@ endif() if(CORENRN_ENABLE_GPU) set(coreneuron_cuda_target coreneuron-cuda) add_library(coreneuron-cuda ${COMPILE_LIBRARY_TYPE} ${CORENEURON_CUDA_FILES}) + set_target_properties(coreneuron-cuda PROPERTIES EXPORT_NAME corenrn-cuda) target_link_libraries(coreneuron-core PUBLIC coreneuron-cuda) + # list() commands don't propagate to parent scope + set(CORENRN_INSTALL_LIB_TARGETS + "coreneuron-core;coreneuron-cuda" + PARENT_SCOPE) +else() + set_target_properties(coreneuron-core PROPERTIES EXPORT_NAME corenrn) + set(CORENRN_INSTALL_LIB_TARGETS + "coreneuron-core" + PARENT_SCOPE) endif() +set(CORENRN_INSTALL_BIN_TARGETS + "nmodl" + PARENT_SCOPE) foreach(target coreneuron-core ${coreneuron_cuda_target}) target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/src @@ -469,6 +482,10 @@ endif() target_compile_options(coreneuron-core PRIVATE ${CORENEURON_CXX_WARNING_SUPPRESSIONS}) target_link_libraries(coreneuron-core PUBLIC ${sonatareport_LIBRARY} ${CORENRN_CALIPER_LIB} ${CORENRN_LIKWID_LIB}) +set_target_properties(coreneuron-core PROPERTIES EXPORT_NAME corenrn) +target_compile_features(coreneuron-core PUBLIC cxx_std_17) +target_include_directories(coreneuron-core + INTERFACE $) # TODO: fix adding a dependency of coreneuron-core on CLI11::CLI11 when CLI11 is a submodule. Right # now this doesn't work because the CLI11 targets are not exported/installed but coreneuron-core is. @@ -507,7 +524,7 @@ set(output_binaries "${nrniv_core_prefix}/special-core" "${corenrn_mech_library} add_custom_command( OUTPUT ${output_binaries} - DEPENDS coreneuron-core ${NMODL_TARGET_TO_DEPEND} ${CORENEURON_BUILTIN_MODFILES} + DEPENDS coreneuron-core nmodl::nmodl ${CORENEURON_BUILTIN_MODFILES} COMMAND ${CMAKE_COMMAND} -E env NMODLHOME=${NMODL_PROJECT_PLATLIB_BINARY_DIR} ${CMAKE_BINARY_DIR}/bin/nrnivmodl-core -b ${COMPILE_LIBRARY_TYPE} -m ${CORENRN_NMODL_BINARY} -n @@ -531,7 +548,7 @@ endif() # tests will depend on this, so it must in turn depend on everything that is needed to run nrnivmodl # -coreneuron. add_custom_target(coreneuron-for-tests) -add_dependencies(coreneuron-for-tests coreneuron-core ${NMODL_TARGET_TO_DEPEND}) +add_dependencies(coreneuron-for-tests coreneuron-core nmodl::nmodl) # Create an extra target for internal use that unit tests and so on can depend on. # ${corenrn_mech_library} is libcorenrnmech_internal.{a,so}, which contains both the compiled # default mechanisms and the content of libcoreneuron-core.a. @@ -569,6 +586,9 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/utils/profile/profiler_interface.h # CORENRN_LIB_LINK_FLAGS, which contains the arguments that must be added to the link line for # `special` to link against `libcorenrnmech_internal.{a,so}` include(MakefileBuildOptions) +set(CORENRN_CXX_FLAGS + "${CORENRN_CXX_FLAGS}" + PARENT_SCOPE) # ============================================================================= # CoreNEURON related configuration @@ -581,13 +601,13 @@ configure_file(${PROJECT_SOURCE_DIR}/bin/nrnivmodl_core_makefile.in ${CMAKE_BINARY_DIR}/share/coreneuron/nrnivmodl_core_makefile @ONLY) configure_file(${PROJECT_SOURCE_DIR}/bin/nrnivmodl-core.in ${CMAKE_BINARY_DIR}/bin/nrnivmodl-core @ONLY) -# nrnivmodl-core depends on the building of NMODL_TARGET_TO_DEPEND and the configuration of the -# nrnivmodl-core and nrnivmodl_core_makefile this doesn't imply that whenever there is a change in -# one of those files then the prebuilt mod files are going to be rebuilt +# nrnivmodl-core depends on the building of nmodl::nmodl and the configuration of the nrnivmodl-core +# and nrnivmodl_core_makefile this doesn't imply that whenever there is a change in one of those +# files then the prebuilt mod files are going to be rebuilt add_custom_target( nrnivmodl-core ALL DEPENDS ${CMAKE_BINARY_DIR}/bin/nrnivmodl-core - ${CMAKE_BINARY_DIR}/share/coreneuron/nrnivmodl_core_makefile ${NMODL_TARGET_TO_DEPEND}) + ${CMAKE_BINARY_DIR}/share/coreneuron/nrnivmodl_core_makefile nmodl::nmodl) if(CORENRN_ENABLE_UNIT_TESTS) add_subdirectory(${PROJECT_SOURCE_DIR}/test/coreneuron/unit diff --git a/src/ivoc/nrnmain.cpp b/src/ivoc/nrnmain.cpp index 153e9d4559..4e734e6a85 100644 --- a/src/ivoc/nrnmain.cpp +++ b/src/ivoc/nrnmain.cpp @@ -1,6 +1,6 @@ #include "nrnconf.h" #include "nrnmpi.h" -#include "../nrncvode/nrnneosm.h" +#include "nrnneosm.h" #include #include diff --git a/src/nrniv/CMakeLists.txt b/src/nrniv/CMakeLists.txt index a4b1979c2b..dfc77e9ea3 100644 --- a/src/nrniv/CMakeLists.txt +++ b/src/nrniv/CMakeLists.txt @@ -399,18 +399,23 @@ include_directories(${NRN_INCLUDE_DIRS}) add_library(nrniv_lib ${NRN_LIBRARY_TYPE} ${NRN_NRNIV_LIB_SRC_FILES}) add_dependencies(nrniv_lib generated_source_files) add_cpp_git_information(nrniv_lib PRIVATE) -target_link_libraries(nrniv_lib nrngnu) -target_link_libraries(nrniv_lib sparse13) -target_link_libraries(nrniv_lib fmt::fmt) -target_include_directories(nrniv_lib SYSTEM PUBLIC ${PROJECT_SOURCE_DIR}/${NRN_3RDPARTY_DIR}/eigen) +target_link_libraries(nrniv_lib PRIVATE nrngnu) +target_link_libraries(nrniv_lib PRIVATE sparse13) +target_link_libraries(nrniv_lib PRIVATE fmt::fmt) +# We feel at liberty to include across various subdirectories without clear interfaces, thus this +# should be public at _build_ time, but not at install time. +target_include_directories( + nrniv_lib SYSTEM PUBLIC $) cpp_cc_configure_sanitizers(TARGET nrniv_lib) # Source-directory .cpp needs to find generated .hpp. -target_include_directories(nrniv_lib PUBLIC "${NRN_OC_GEN}") +target_include_directories(nrniv_lib PUBLIC $) +target_include_directories(nrniv_lib + INTERFACE $) if(NRN_ENABLE_PYTHON AND NOT NRN_ENABLE_PYTHON_DYNAMIC) - target_link_libraries(nrniv_lib nrnpython) + target_link_libraries(nrniv_lib PRIVATE nrnpython) endif() if(NRN_ENABLE_THREADS) - target_link_libraries(nrniv_lib Threads::Threads) + target_link_libraries(nrniv_lib PUBLIC Threads::Threads) endif() if(${NRN_ENABLE_DIGEST}) @@ -421,48 +426,49 @@ if(${NRN_ENABLE_DIGEST}) APPEND PROPERTY INCLUDE_DIRECTORIES /opt/homebrew/Cellar/openssl@3/3.1.0/include) find_library(LIB_CRYPTO crypto PATHS /opt/homebrew/Cellar/openssl@3/3.1.0/lib REQUIRED) - target_link_libraries(nrniv_lib ${LIB_CRYPTO}) + target_link_libraries(nrniv_lib PRIVATE ${LIB_CRYPTO}) else() - target_link_libraries(nrniv_lib crypto) + target_link_libraries(nrniv_lib PRIVATE crypto) endif() endif() if(${NRN_ENABLE_ARCH_INDEP_EXP_POW}) find_library(LIB_MPFR mpfr REQUIRED) - target_link_libraries(nrniv_lib ${LIB_MPFR}) + target_link_libraries(nrniv_lib PRIVATE ${LIB_MPFR}) endif() if(NRN_WINDOWS_BUILD) - target_link_libraries(nrniv_lib ${TERMCAP_LIBRARIES} ${Readline_LIBRARY}) + target_link_libraries(nrniv_lib PUBLIC ${TERMCAP_LIBRARIES} ${Readline_LIBRARY}) else() if(READLINE_FOUND) - target_link_libraries(nrniv_lib ${Readline_LIBRARY}) + target_link_libraries(nrniv_lib PRIVATE ${Readline_LIBRARY}) else() - target_link_libraries(nrniv_lib readline) + target_link_libraries(nrniv_lib PRIVATE readline) endif() if(CURSES_FOUND) - target_link_libraries(nrniv_lib ${CURSES_LIBRARIES}) + target_link_libraries(nrniv_lib PUBLIC ${CURSES_LIBRARIES}) elseif(TERMCAP_FOUND) - target_link_libraries(nrniv_lib ${TERMCAP_LIBRARIES}) + target_link_libraries(nrniv_lib PUBLIC ${TERMCAP_LIBRARIES}) endif() endif() if(NRN_ENABLE_MUSIC AND NOT NRN_ENABLE_MPI_DYNAMIC) - target_link_libraries(nrniv_lib ${MUSIC_LIBRARY}) + target_link_libraries(nrniv_lib PUBLIC ${MUSIC_LIBRARY}) endif() if(NRN_ENABLE_PROFILING) - target_link_libraries(nrniv_lib ${likwid_LIBRARIES} ${CALIPER_LIB} ${LIKWID_LIB}) + target_link_libraries(nrniv_lib PRIVATE ${likwid_LIBRARIES} ${CALIPER_LIB} ${LIKWID_LIB}) endif() -set_property(TARGET nrniv_lib PROPERTY OUTPUT_NAME nrniv) +set_target_properties(nrniv_lib PROPERTIES EXPORT_NAME nrniv OUTPUT_NAME nrniv) +target_compile_features(nrniv_lib PUBLIC cxx_std_17) # ============================================================================= # Link with backward-cpp if enabled # ============================================================================= if(NRN_USE_BACKWARD) - target_link_libraries(nrniv_lib Backward::Backward) + target_link_libraries(nrniv_lib PRIVATE Backward::Backward) target_include_directories(nrniv_lib PRIVATE ${PROJECT_SOURCE_DIR}/external/backward) endif() @@ -478,13 +484,13 @@ if(NRN_ENABLE_MPI) list(GET NRN_MPI_LIBNAME_LIST ${val} libname) add_library(${libname}_lib SHARED ${NRN_NRNMPI_SRC_FILES}) - target_link_libraries(${libname}_lib fmt::fmt) + target_link_libraries(${libname}_lib PRIVATE fmt::fmt) target_include_directories(${libname}_lib BEFORE PUBLIC ${include}) # Note that we do not link here to libmpi. That is dlopen first. if(MINGW) # type msmpi only add_dependencies(${libname}_lib nrniv_lib) - target_link_libraries(${libname}_lib ${MPI_C_LIBRARIES}) - target_link_libraries(${libname}_lib nrniv_lib) + target_link_libraries(${libname}_lib PUBLIC ${MPI_C_LIBRARIES}) + target_link_libraries(${libname}_lib PUBLIC nrniv_lib) endif() set_property(TARGET ${libname}_lib PROPERTY OUTPUT_NAME ${libname}) install(TARGETS ${libname}_lib DESTINATION ${NRN_INSTALL_SHARE_LIB_DIR}) @@ -498,21 +504,21 @@ if(NRN_ENABLE_MPI) add_library(${libnrnmusic}_lib SHARED ${NRN_MUSIC_SRC_FILES}) target_include_directories(${libnrnmusic}_lib BEFORE PUBLIC ${include}) add_dependencies(${libnrnmusic}_lib nrniv_lib ${libname}_lib) - target_link_libraries(${libnrnmusic}_lib nrniv_lib ${libname}_lib) + target_link_libraries(${libnrnmusic}_lib PUBLIC nrniv_lib ${libname}_lib) set_property(TARGET ${libnrnmusic}_lib PROPERTY OUTPUT_NAME ${libnrnmusic}) install(TARGETS ${libnrnmusic}_lib DESTINATION ${NRN_INSTALL_SHARE_LIB_DIR}) endif() else() - target_link_libraries(nrniv_lib ${MPI_C_LIBRARIES}) + target_link_libraries(nrniv_lib PUBLIC ${MPI_C_LIBRARIES}) target_include_directories(nrniv_lib PUBLIC ${MPI_INCLUDE_PATH}) endif() endif() if(NRN_ENABLE_INTERVIEWS) include_directories(${IV_INCLUDE_DIR}) - target_link_libraries(nrniv_lib interviews) + target_link_libraries(nrniv_lib PRIVATE interviews) else() - target_include_directories(nrniv_lib PUBLIC ${NRN_IVOS_SRC_DIR}) + target_include_directories(nrniv_lib PUBLIC $) endif() if(IV_ENABLE_X11_DYNAMIC) @@ -543,7 +549,7 @@ if(IV_ENABLE_X11_DYNAMIC) ${PROJECT_BINARY_DIR}/lib/${LIBIVX11DYNAM_NAME}) endif() else() - target_link_libraries(nrniv_lib ${X11_LIBRARIES}) + target_link_libraries(nrniv_lib PRIVATE ${X11_LIBRARIES}) endif() # ============================================================================= @@ -554,17 +560,17 @@ cpp_cc_configure_sanitizers(TARGET nrniv) if(NOT "${NRN_LINK_FLAGS_FOR_ENTRY_POINTS}" STREQUAL "") target_link_options(nrniv PUBLIC ${NRN_LINK_FLAGS_FOR_ENTRY_POINTS}) endif() -target_link_libraries(nrniv nrniv_lib ${INTERNAL_READLINE} ${CMAKE_DL_LIBS}) +target_link_libraries(nrniv PRIVATE nrniv_lib ${INTERNAL_READLINE} ${CMAKE_DL_LIBS}) if(NRN_ENABLE_THREADS) - target_link_libraries(nrniv Threads::Threads) + target_link_libraries(nrniv PRIVATE Threads::Threads) endif() if(NOT MINGW) - target_link_libraries(nrniv_lib ${CMAKE_DL_LIBS}) + target_link_libraries(nrniv_lib PUBLIC ${CMAKE_DL_LIBS}) endif() # TODO: unset in top level CMake is not working if(NOT NRN_MACOS_BUILD AND READLINE_FOUND) - target_link_libraries(nrniv ${Readline_LIBRARY}) + target_link_libraries(nrniv PRIVATE ${Readline_LIBRARY}) endif() # ============================================================================= @@ -575,8 +581,16 @@ endif() # For now, we keep this distinction as it reduces the PATH and is # expected when ctypes looks for dlls # ~~~ -install(TARGETS nrniv nocmodl modlunit RUNTIME DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin) -install(TARGETS nrniv_lib DESTINATION ${NRN_INSTALL_SHARE_LIB_DIR}) +install(TARGETS nrniv modlunit RUNTIME DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin) +install( + TARGETS nocmodl + DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin + EXPORT NeuronTargets) +install( + TARGETS nrniv_lib + DESTINATION ${NRN_INSTALL_SHARE_LIB_DIR} + EXPORT NeuronTargets) + if(LIBIVX11DYNAM_NAME) install(FILES ${PROJECT_BINARY_DIR}/lib/${LIBIVX11DYNAM_NAME} DESTINATION ${NRN_INSTALL_DATA_PREFIX}lib) diff --git a/src/nrnpython/CMakeLists.txt b/src/nrnpython/CMakeLists.txt index fb7b1e171f..884bfa5e1e 100644 --- a/src/nrnpython/CMakeLists.txt +++ b/src/nrnpython/CMakeLists.txt @@ -70,7 +70,7 @@ if(NRN_ENABLE_PYTHON_DYNAMIC) add_library(nrnpython${pyver} SHARED ${NRNPYTHON_FILES_LIST}) target_include_directories(nrnpython${pyver} BEFORE PUBLIC ${pyinc} ${INCLUDE_DIRS}) target_link_libraries(nrnpython${pyver} PUBLIC nrniv_lib) - target_link_libraries(nrnpython${pyver} PRIVATE ${Readline_LIBRARY} ${nanobind_target}) + target_link_libraries(nrnpython${pyver} PRIVATE fmt::fmt ${Readline_LIBRARY} ${nanobind_target}) if(NRN_LINK_AGAINST_PYTHON) target_link_libraries(nrnpython${pyver} PUBLIC ${pylib}) endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 37bf03842a..ffa28da9b2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,7 +35,7 @@ if(NRN_ENABLE_THREADS) endif() foreach(target ${catch2_targets}) cpp_cc_configure_sanitizers(TARGET ${target}) - target_link_libraries(${target} Catch2::Catch2 nrniv_lib) + target_link_libraries(${target} Catch2::Catch2 fmt::fmt nrniv_lib) if(NOT MINGW) target_link_libraries(${target} ${CMAKE_DL_LIBS}) endif() @@ -831,3 +831,11 @@ if((NRN_ENABLE_NMODL OR NRN_ENABLE_CORENEURON) AND NRN_ENABLE_TESTS) add_subdirectory(nmodl/transpiler/usecases) endif() endif() + +# Test nrnivmodl-cmake, but only without sanitizers since those cause issues, and end-users won't +# get builds with sanitizer builds anyway. The same reasoning applies to coverage. +if(NRN_ENABLE_TESTS + AND NOT NRN_SANITIZERS + AND NOT NRN_ENABLE_COVERAGE) + add_subdirectory(nrnivmodl_cmake) +endif() diff --git a/test/nrnivmodl_cmake/CMakeLists.txt b/test/nrnivmodl_cmake/CMakeLists.txt new file mode 100644 index 0000000000..f477db26a1 --- /dev/null +++ b/test/nrnivmodl_cmake/CMakeLists.txt @@ -0,0 +1,34 @@ +# ~~~ +# Test for `create_nrnmech` as if it was already installed +# ~~~ +set(TEST_NAMESPACE "nrnivmodl_cmake") +file(GLOB MOD_FILES "${PROJECT_SOURCE_DIR}/test/coreneuron/mod files/*.mod") + +# This is admittedly a bit hacky, but we are launching CMake inside of CMake, and we cannot use any +# of the set variables, either via CMake itself, or through the env. The only thing that must be +# available is the NEURON CMake config file, and NMODL must be in PATH at CMake configure time. Note +# that we do not try to use Python abilities of `special`, since those require setting env +# variables, which the user may not be aware of. +set(TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/build_cmake/build") +if(NRN_ENABLE_CORENEURON) + set(CORENEURON CORENEURON) +endif() +# we need to enable CUDA if using NVHPC, otherwise the flags are not propagated properly +if(CMAKE_CUDA_COMPILER) + set(CUDA_STRING "CUDA") +endif() +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build_cmake/CMakeLists.txt" + "${CMAKE_CURRENT_BINARY_DIR}/build_cmake/CMakeLists.txt" @ONLY) +add_test( + NAME "${TEST_NAMESPACE}::modfiles" + COMMAND + ${CMAKE_COMMAND} "-DTEST_DIR=${TEST_DIR}" "-DMOD_FILES=${MOD_FILES}" + "-DCORENEURON=${CORENEURON}" "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CUDA_COMPILER=${CMAKE_CUDA_COMPILER}" -P + "${CMAKE_CURRENT_SOURCE_DIR}/run_modfiles_test.cmake") + +set_tests_properties( + "${TEST_NAMESPACE}::modfiles" + PROPERTIES + ENVIRONMENT + "CMAKE_PREFIX_PATH=${PROJECT_BINARY_DIR}/lib/cmake;PATH=${PROJECT_BINARY_DIR}/bin:$ENV{PATH}") diff --git a/test/nrnivmodl_cmake/build_cmake/CMakeLists.txt b/test/nrnivmodl_cmake/build_cmake/CMakeLists.txt new file mode 100644 index 0000000000..f166d35692 --- /dev/null +++ b/test/nrnivmodl_cmake/build_cmake/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION @CMAKE_MINIMUM_REQUIRED_VERSION@) +project(modfile_test LANGUAGES C CXX @CUDA_STRING@) + +find_package(neuron REQUIRED) + +create_nrnmech(NEURON ${CORENEURON} SPECIAL MOD_FILES ${MOD_FILES}) diff --git a/test/nrnivmodl_cmake/run_modfiles_test.cmake b/test/nrnivmodl_cmake/run_modfiles_test.cmake new file mode 100644 index 0000000000..d1d163b6af --- /dev/null +++ b/test/nrnivmodl_cmake/run_modfiles_test.cmake @@ -0,0 +1,57 @@ +# Ensure cleanup even if something fails +function(cleanup) + message(STATUS "Cleaning up ${TEST_DIR}") + file(REMOVE_RECURSE "${TEST_DIR}") +endfunction() + +cleanup() + +message(STATUS "Mod files: ${MOD_FILES}") +# Due to CMake not being a useful language, and having only a string type, we need to do some +# escaping in order to pass things along to the CLI +string(REPLACE " " "\ " MOD_FILES "${MOD_FILES}") +string(REPLACE ";" "\;" MOD_FILES "${MOD_FILES}") + +# Configure +if(CMAKE_CUDA_COMPILER) + # One would figure saving the below into a variable, and only appending the CUDA stuff if + # necessary, would work, but alas, due to quoting rules, I can't figure out the magic combination + # of those that actually work, so we just duplicate the command string verbatim. + execute_process( + COMMAND + "${CMAKE_COMMAND}" -S "${CMAKE_CURRENT_BINARY_DIR}/build_cmake" -B "${TEST_DIR}" + -DMOD_FILES=${MOD_FILES} -DCMAKE_BUILD_TYPE=Debug -DCORENEURON=${CORENEURON} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_CUDA_COMPILER=${CMAKE_CUDA_COMPILER} + RESULT_VARIABLE res_configure) +else() + execute_process( + COMMAND + "${CMAKE_COMMAND}" -S "${CMAKE_CURRENT_BINARY_DIR}/build_cmake" -B "${TEST_DIR}" + -DMOD_FILES=${MOD_FILES} -DCMAKE_BUILD_TYPE=Debug -DCORENEURON=${CORENEURON} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + RESULT_VARIABLE res_configure) +endif() + +if(NOT res_configure EQUAL 0) + cleanup() + message(FATAL_ERROR "Configure failed") +endif() + +# Build +set(BUILD_COMMAND "${CMAKE_COMMAND}" --build "${TEST_DIR}" --parallel -v) +execute_process(COMMAND ${BUILD_COMMAND} RESULT_VARIABLE res_build) +if(NOT res_build EQUAL 0) + message(FATAL_ERROR "Build failed") +endif() + +# Run executable +set(EXE_COMMAND "${TEST_DIR}/special" -nopython -nobanner -nogui -c "quit()") +execute_process(COMMAND ${EXE_COMMAND} RESULT_VARIABLE res_run) +if(NOT res_run EQUAL 0) + cleanup() + message(FATAL_ERROR "Executable failed") +endif() + +# Cleanup at the end +cleanup()