diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bcbadad51..91532521f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: + branches: - master - live-debug* - release/** @@ -41,7 +41,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - uses: actions/checkout@v4 with: - fetch-depth: 2 + fetch-depth: 2 - name: Install Python3 dependencies working-directory: ${{runner.workspace}}/nmodl run: | diff --git a/.github/workflows/nmodl-ci.yml b/.github/workflows/nmodl-ci.yml index 1843739ca..903107f64 100644 --- a/.github/workflows/nmodl-ci.yml +++ b/.github/workflows/nmodl-ci.yml @@ -191,7 +191,7 @@ jobs: path: ${{runner.workspace}}/nmodl/build/Testing/*/Test.xml # This step will set up an SSH connection on tmate.io for live debugging. - # To enable it, you have to: + # To enable it, you have to: # * add 'live-debug-tests' to your PR title # * push something to your PR branch (note that just re-running disregards the title update) - name: live debug session on failure (manual steps required, check `nmodl-ci.yml`) diff --git a/.github/workflows/nmodl-doc.yml b/.github/workflows/nmodl-doc.yml index 28fcd606f..da1c062b3 100644 --- a/.github/workflows/nmodl-doc.yml +++ b/.github/workflows/nmodl-doc.yml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: + branches: - master - release/** pull_request: @@ -37,7 +37,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: ${{ env.DESIRED_CMAKE_VERSION }} - + - name: Install apt packages run: | sudo apt-get update @@ -55,11 +55,17 @@ jobs: with: fetch-depth: 0 - - name: Install Python3 dependencies + - name: Documentation + id: documentation working-directory: ${{runner.workspace}}/nmodl run: | - pip3 install -U pip setuptools - pip3 install --user -r requirements.txt + echo "------- Build Documentation -------"; + bash docs/generate_docs.sh public $(command -v python${PYTHON_VERSION}) + touch public/docs/.nojekyll + echo "" > public/docs/index.html + echo "status=done" >> $GITHUB_OUTPUT + env: + CCACHE_DIR: ${{runner.workspace}}/ccache # This step will set up an SSH connection on tmate.io for live debugging. # To trigger it, simply add 'live-debug-docs' to your last pushed commit message. @@ -67,37 +73,10 @@ jobs: if: failure() && contains(github.event.head_commit.message, 'live-debug-docs') uses: mxschmitt/action-tmate@v3 - - name: Restore compiler cache - uses: actions/cache@v4 - with: - path: | - ${{runner.workspace}}/ccache - key: docs-${{github.ref}}-${{github.sha}} - restore-keys: | - docs-${{github.ref}}- - docs- - - - name: Documentation - id: documentation - working-directory: ${{runner.workspace}}/nmodl - run: | - echo "------- Build Documentation -------"; - ccache -z - ccache -s - python3 setup.py build_ext --inplace docs -j 2 -G Ninja \ - -- -DCMAKE_CXX_COMPILER_LAUNCHER=ccache; - ccache -s - cd _skbuild/linux-x86_64-3.8/setuptools/sphinx; - rm -rf doctest doctrees && touch .nojekyll; - echo "" > index.html; - echo "status=done" >> $GITHUB_OUTPUT - env: - CCACHE_DIR: ${{runner.workspace}}/ccache - - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4 if: steps.documentation.outputs.status == 'done' && startsWith(github.ref, 'refs/heads/master') with: branch: gh-pages # The branch the action should deploy to. - folder: ${{runner.workspace}}/nmodl/_skbuild/linux-x86_64-3.8/setuptools/sphinx # The folder the action should deploy. + folder: ${{runner.workspace}}/nmodl/public/docs clean: false # Automatically remove deleted files from the deploy branch diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b628c4d3..4754ca822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,12 @@ set(NMODL_EXTRA_CXX_FLAGS "" CACHE STRING "Add extra compile flags for NMODL sources") separate_arguments(NMODL_EXTRA_CXX_FLAGS) +option(LINK_AGAINST_PYTHON "Should the Python library be linked or not" ON) +option(NMODL_BUILD_WHEEL "Flag to signal we are building a wheel" OFF) +if(NMODL_BUILD_WHEEL) + set(LINK_AGAINST_PYTHON OFF) + set(NMODL_ENABLE_TESTS OFF) +endif() # ============================================================================= # Settings to enable project as submodule @@ -166,12 +172,6 @@ endif() message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.8 REQUIRED) cpp_cc_strip_python_shims(EXECUTABLE "${PYTHON_EXECUTABLE}" OUTPUT PYTHON_EXECUTABLE) -include(cmake/hpc-coding-conventions/cpp/cmake/bbp-find-python-module.cmake) -cpp_cc_find_python_module(jinja2 2.9.3 REQUIRED) -cpp_cc_find_python_module(pytest 3.3.0 REQUIRED) -cpp_cc_find_python_module(sympy 1.3 REQUIRED) -cpp_cc_find_python_module(textwrap 0.9 REQUIRED) -cpp_cc_find_python_module(yaml 3.12 REQUIRED) # ============================================================================= # Compiler specific flags for external submodules diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e61710636..6f21b8aaa 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -210,9 +210,8 @@ the Python API: 1. setup a sandbox environment with either *virtualenv*, *pyenv*, or *pipenv*. For instance with *virtualenv*: ``python -m venv .venv && source .venv/bin/activate`` -2. build the Python package with the command: ``python setup.py build`` -3. install *pytest* Python package: ``pip install pytest`` -4. execute the unit-tests: ``pytest`` +2. build the Python wheel with the command: ``python -m pip wheel . --no-deps`` +3. execute the unit-tests for the wheel: ``bash packaging/test_wheel.bash $(command -v python) WHEEL``, where ``WHEEL`` is the path to the wheel generated in the previous step. Memory Leaks and clang-tidy ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/INSTALL.rst b/INSTALL.rst index 72fff92bd..9c4fc6af4 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -45,8 +45,8 @@ of all dependencies we recommend using `homebrew `__: brew install flex bison cmake python3 -The necessary Python packages can then easily be added using the pip3 -command. +All of the Python dependencies (build, run, and development) can be installed +using: .. code:: sh @@ -74,7 +74,8 @@ installed along with the system toolchain: apt-get install flex bison gcc python3 python3-pip -The Python dependencies are installed using: +All of the Python dependencies (build, run, and development) can be installed +using: .. code:: sh @@ -124,8 +125,8 @@ to cmake as: -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -Using Python setuptools -~~~~~~~~~~~~~~~~~~~~~~~ +Using the Python build system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you are mainly interested in the NMODL Framework parsing and analysis tools and wish to use them from Python, we recommend building and @@ -139,6 +140,19 @@ This should build the NMODL framework and install it into your pip user ``site-packages`` folder such that it becomes available as a Python module. +Building a wheel +~~~~~~~~~~~~~~~~ + +You can also build a wheel you can test and install in another environment using: + +.. code:: sh + + pip3 wheel . --no-deps [-C OPTION1=VALUE1 -C OPTION2=VALUE2...] [--wheel-dir DIRECTORY] + +where the various ``OPTION`` values describe the build options (for a list of +all available options, please consult the `reference `_). +Notably, due to a bug in CMake, on MacOS one should pass ``-C build-dir=DIRECTORY`` to the above. + When building without linking against libpython ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -210,6 +224,16 @@ example in your Python 3 interpeter as follows: SUFFIX hh } +You can also run all of the Python tests for a given wheel using: + +.. code:: sh + + bash packaging/test_wheel.bash PYTHON_EXECUTABLE WHEEL + +where ``PYTHON_EXECUTABLE`` should be replaced by the path to the Python +executable, and ``WHEEL`` should be replaced by the path to the wheel you wish +to test. + NMODL is now setup correctly! Generating Documentation @@ -219,9 +243,14 @@ In order to build the documentation you must have additionally ``pandoc`` installed. Use your system’s package manager to do this (e.g. ``sudo apt-get install pandoc``). -You can build the entire documentation simply by using sphinx from -``setup.py``: +You can build the entire documentation simply by using the ``generate_docs.sh`` +script: .. code:: sh - python3 setup.py build_ext --inplace docs + bash docs/generate_docs.sh DIRECTORY [PYTHON_EXECUTABLE] + +where ``DIRECTORY`` is where you want to put the output files. The HTML +documentation will then be available in ``DIRECTORY/docs``, and the temporary +build will be stored in ``DIRECTORY/build``. You can also specify the path to +the Python executable if it is not picked up automatically. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0f46c8548..663a792dc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -88,7 +88,7 @@ stages: sudo apt-get install -y python3.8 python3.8-dev python3.8-venv ninja-build sudo apt-get remove -y python3-importlib-metadata python3.8 -m pip install --upgrade pip setuptools - python3.8 -m pip install --user -r $(Build.Repository.LocalPath)/requirements.txt + python3.8 -m pip install --user -r requirements.txt # we manually get version 3.15.0 to make sure that changes in the cmake # files do not require unsupported versions of cmake in our package. wget --quiet --output-document=- "https://github.com/Kitware/CMake/releases/download/$CMAKE_VER/$CMAKE_PKG.tar.gz" | tar xzpf - @@ -149,7 +149,7 @@ stages: - script: | brew install flex bison cmake python@3 python3 -m pip install --upgrade pip setuptools - python3 -m pip install --user -r $(Build.Repository.LocalPath)/requirements.txt + python3 -m pip install --user -r requirements.txt displayName: 'Install Dependencies' - script: | export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; @@ -222,10 +222,16 @@ stages: else export TAG="" fi + # the following 2 lines are a workaround for PEP 621 not allowing a + # dynamic `name` in `pyproject.toml` + python3 -m pip install --user tomli tomli-w + python3 packaging/change_name.py pyproject.toml "NMODL${TAG}" + export SETUPTOOLS_SCM_PRETEND_VERSION="$(git describe --tags | cut -d '-' -f 1,2 | tr - .)" docker run --rm \ -w /root/nmodl \ -v $PWD:/root/nmodl \ -e NMODL_NIGHTLY_TAG=$TAG \ + -e SETUPTOOLS_SCM_PRETEND_VERSION=$SETUPTOOLS_SCM_PRETEND_VERSION \ 'bluebrain/nmodl:wheel' \ packaging/build_wheels.bash linux $(python.version) condition: succeeded() @@ -295,7 +301,11 @@ stages: else export NMODL_NIGHTLY_TAG="" fi - packaging/build_wheels.bash osx $(python.version) + # the following 2 lines are a workaround for PEP 621 not allowing a + # dynamic `name` in `pyproject.toml` + python3 -m pip install tomli tomli-w + python3 packaging/change_name.py pyproject.toml "NMODL${NMODL_NIGHTLY_TAG}" + SETUPTOOLS_SCM_PRETEND_VERSION="$(git describe --tags | cut -d '-' -f 1,2 | tr - .)" packaging/build_wheels.bash osx $(python.version) condition: succeeded() displayName: 'Build macos Wheel' - task: PublishBuildArtifacts@1 diff --git a/docs/conf.py b/docs/conf.py index e2695fb9e..ff9c2afbc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,29 +12,14 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # - - - -import os -import subprocess -import sys import textwrap -# The project needs to be built before documentation in the usual build folder -sys.path.insert(0, os.path.abspath('..')) - import nmodl # isort:skip -os.environ['PYTHONPATH'] = ':'.join(sys.path) - -# Run doxygen -subprocess.call('doxygen Doxyfile', shell=True) - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -134,11 +119,11 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ["_static"] +# html_static_path = ["_static"] -# A list of paths that contain extra files not directly related to the -# documentation, such as robots.txt or .htaccess. Relative paths are taken -# as relative to the configuration directory. They are copied to the output +# A list of paths that contain extra files not directly related to the +# documentation, such as robots.txt or .htaccess. Relative paths are taken +# as relative to the configuration directory. They are copied to the output # directory. They will overwrite any existing file of the same name. html_extra_path = ["sphinx_doxygen"] diff --git a/docs/generate_docs.sh b/docs/generate_docs.sh new file mode 100755 index 000000000..c0a756d32 --- /dev/null +++ b/docs/generate_docs.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# script for generating documentation for NMODL + +set -eu + +if [ $# -lt 1 ] +then + echo "Usage: $(basename "$0") output_dir [python_exe]" + exit 1 +fi + +# the dir where we put the temporary build and the docs +output_dir="$1" +# path to the Python executable +python_exe="${2:-"$(command -v python3)"}" + +if ! [ -d "${output_dir}" ] +then + mkdir -p "${output_dir}" +fi + +build_dir="build" +docs_dir="docs" + +echo "== Building documentation files in: ${output_dir}/${docs_dir} ==" +echo "== Temporary project build directory is: ${output_dir}/${build_dir} ==" + +venv_name="${output_dir}/env" +${python_exe} -m venv "${venv_name}" +. "${venv_name}/bin/activate" +python_exe="$(command -v python)" +${python_exe} -m pip install -U pip +${python_exe} -m pip install ".[docs]" -C build-dir="${output_dir}/${build_dir}" + +# the abs dir where this script is located (so we can call it from wherever) +script_dir="$(cd "$(dirname "$0")"; pwd -P)" + +cd "${script_dir}" +doxygen Doxyfile +cd - +sphinx-build docs/ "${output_dir}/${docs_dir}" + +deactivate diff --git a/packaging/build_requirements.txt b/packaging/build_requirements.txt deleted file mode 100644 index 9f6f031d0..000000000 --- a/packaging/build_requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Jinja2>=2.9.3 -PyYAML>=3.13 -pytest -sympy>=1.3 -find_libpython -scikit-build diff --git a/packaging/build_wheels.bash b/packaging/build_wheels.bash index 961033f3f..5dfb0fc49 100755 --- a/packaging/build_wheels.bash +++ b/packaging/build_wheels.bash @@ -12,8 +12,8 @@ set -xe # - python (>=3.8) # - C/C++ compiler -if [ ! -f setup.py ]; then - echo "Error: setup.py not found. Please launch $0 from the nmodl root dir" +if ! [ -f pyproject.toml ]; then + echo "Error: pyproject.toml not found. Please launch $0 from the nmodl root dir" exit 1 fi @@ -47,14 +47,13 @@ build_wheel_linux() { (( $skip )) && return 0 echo " - Installing build requirements" - pip install pip auditwheel setuptools - pip install -r packaging/build_requirements.txt + pip install pip auditwheel echo " - Building..." - rm -rf dist _skbuild + rm -rf dist _build # Workaround for https://github.com/pypa/manylinux/issues/1309 git config --global --add safe.directory "*" - python setup.py bdist_wheel + python -m pip wheel . --wheel-dir dist/ --no-deps echo " - Repairing..." auditwheel repair dist/*.whl @@ -70,11 +69,13 @@ build_wheel_osx() { (( $skip )) && return 0 echo " - Installing build requirements" - pip install --upgrade delocate -r packaging/build_requirements.txt + pip install --upgrade delocate echo " - Building..." - rm -rf dist _skbuild - python setup.py bdist_wheel + rm -rf dist _build + # the custom `build-dir` is a workaround for this issue: + # https://gitlab.kitware.com/cmake/cmake/-/issues/20107 + python -m pip wheel . --wheel-dir dist/ -C build-dir=_build --no-deps echo " - Repairing..." delocate-wheel -w wheelhouse -v dist/*.whl # we started clean, there's a single wheel @@ -82,34 +83,31 @@ build_wheel_osx() { deactivate } -# platform for which wheel to be build -platform=$1 +# platform for which wheel we are building +platform="$1" -# python version for which wheel to be built; 3* (default) means all python 3 versions -python_wheel_version=3* -if [ "$2" ]; then - python_wheel_version=$2 -fi +# python version for which wheel we are building; 3* (default) means all python 3 versions +python_wheel_version="${2:-3*}" # MAIN -case "$1" in +case "${platform}" in linux) - python_wheel_version=${python_wheel_version//[-._]/} - for py_bin in /opt/python/cp${python_wheel_version}*/bin/python; do + python_wheel_version="${python_wheel_version//[-._]/}" + for py_bin in /opt/python/cp${python_wheel_version}-cp${python_wheel_version}/bin/python; do build_wheel_linux "$py_bin" done ;; osx) - for py_bin in /Library/Frameworks/Python.framework/Versions/${python_wheel_version}*/bin/python3; do + for py_bin in /Library/Frameworks/Python.framework/Versions/${python_wheel_version}/bin/python3; do build_wheel_osx "$py_bin" done ;; *) - echo "Usage: $(basename $0) [version]" + echo "Usage: $(basename "$0") [version]" exit 1 ;; diff --git a/packaging/change_name.py b/packaging/change_name.py new file mode 100755 index 000000000..478eb30c3 --- /dev/null +++ b/packaging/change_name.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" +Barebones utility for changing the name of the package in `pyproject.toml`. +""" +import re +from argparse import ArgumentParser +from pathlib import Path + +import tomli +import tomli_w + + +def main(): + parser = ArgumentParser( + description="Script for changing the name of a project in a pyproject.toml file", + ) + parser.add_argument("pyproject_file", help="The path to the pyproject.toml file") + parser.add_argument("name", help="The new name of the project") + args = parser.parse_args() + + if not Path(args.pyproject_file).exists(): + raise FileNotFoundError(f"File {args.pyproject_file} not found") + + with open(args.pyproject_file, "rb") as f: + toml_dict = tomli.load(f) + + # check name is conforms to the naming convention + # see: + # https://packaging.python.org/en/latest/specifications/name-normalization/ + + if not re.match( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", + args.name, + flags=re.IGNORECASE, + ): + raise ValueError( + f"The package name {args.name} does not conform to the standard Python package naming convention" + ) + + toml_dict["project"]["name"] = args.name + + with open(args.pyproject_file, "wb") as f: + tomli_w.dump(toml_dict, f) + + +if __name__ == "__main__": + main() diff --git a/packaging/test_wheel.bash b/packaging/test_wheel.bash index 71abad95a..ac99d7bdf 100755 --- a/packaging/test_wheel.bash +++ b/packaging/test_wheel.bash @@ -23,16 +23,14 @@ test_wheel () { # sample mod file for nrnivmodl check TEST_DIR="$(mktemp -d)" OUTPUT_DIR="$(mktemp -d)" - cp "${this_dir}/../nmodl/ext/example/"*.mod "$TEST_DIR/" + cp "${this_dir}/../python/nmodl/ext/example/"*.mod "$TEST_DIR/" cp "${this_dir}/../test/integration/mod/cabpump.mod" "${this_dir}/../test/integration/mod/var_init.inc" "$TEST_DIR/" - cd "${this_dir}" for mod in "${TEST_DIR}/"*.mod do nmodl -o "${OUTPUT_DIR}" "${mod}" sympy --analytic $python_exe -c "import nmodl; driver = nmodl.NmodlDriver(); driver.parse_file('${mod}')" done $python_exe -m pytest -vvv "${this_dir}/../test/" - cd - } echo "== Testing $python_wheel using $python_exe ($python_ver) ==" @@ -51,7 +49,7 @@ fi # install nmodl $python_exe -m pip install -U pip -$python_exe -m pip install "${python_wheel}" pytest +$python_exe -m pip install "${python_wheel}[test]" $python_exe -m pip show nmodl || $python_exe -m pip show nmodl-nightly # run tests diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..0d057c2f4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[project] +name = "NMODL" +authors = [ + {name = "Blue Brain Project", email = "bbp-ou-hpc@groupes.epfl.ch"}, +] +description = "NEURON Modeling Language Source-to-Source Compiler Framework" +license = {file = "LICENSE"} +readme = {file = "README.rst", content-type = "text/x-rst"} +dynamic = ["version"] +dependencies = [ + "find_libpython", + "sympy>=1.3", + "importlib-metadata;python_version<'3.9'", + "importlib-resources;python_version<'3.9'", +] +scripts = {nmodl = "nmodl._binwrapper:main"} +requires-python = ">=3.8" + +optional-dependencies.test = ["pytest>=3.3.0", "pytest-cov", "numpy"] +optional-dependencies.docs = [ + "jupyter-client", + "jupyter", + "myst_parser<2.0.0", + "mistune<3", + "nbconvert", + "nbsphinx>=0.3.2", + "sphinxcontrib-applehelp<1.0.3", # After this version it needs a toml file to work, no more setup.py + "sphinxcontrib-htmlhelp<=2.0.0", # After this version it needs a toml file to work, no more setup.py + "sphinx<6", + "sphinx-rtd-theme", # needs sphinx < 7 + "docutils<0.20", # needed by sphinx +] + +[build-system] +requires = [ + "scikit-build-core", + "setuptools-scm>=8.0", + "Jinja2>=2.9.3", + "PyYAML>=3.13", +] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +wheel.packages = ["python/nmodl"] +logging.level = "DEBUG" + +[tool.scikit-build.cmake] +verbose = true +version = ">=3.15.0" + +[tool.scikit-build.cmake.define] +NMODL_BUILD_WHEEL = "ON" + +[tool.setuptools_scm] + +[tool.pytest.ini_options] +testpaths = "test/unit/pybind" diff --git a/nmodl/__init__.py b/python/nmodl/__init__.py similarity index 100% rename from nmodl/__init__.py rename to python/nmodl/__init__.py diff --git a/pywheel/shim/_binwrapper.py b/python/nmodl/_binwrapper.py similarity index 86% rename from pywheel/shim/_binwrapper.py rename to python/nmodl/_binwrapper.py index f85086431..036334ceb 100755 --- a/pywheel/shim/_binwrapper.py +++ b/python/nmodl/_binwrapper.py @@ -7,7 +7,6 @@ import stat import sys - if sys.version_info >= (3, 9): from importlib.metadata import metadata, PackageNotFoundError from importlib.resources import files @@ -18,7 +17,7 @@ from find_libpython import find_libpython -def _config_exe(exe_name): +def main(): """Sets the environment to run the real executable (returned)""" try: @@ -42,12 +41,7 @@ def _config_exe(exe_name): str(NMODL_PREFIX.parent) + ":" + os.environ.get("PYTHONPATH", "") ) - return os.path.join(NMODL_BIN, exe_name) - - -if __name__ == "__main__": - """Set the pointed file as executable""" - exe = _config_exe(os.path.basename(sys.argv[0])) + exe = NMODL_BIN / os.path.basename(sys.argv[0]) st = os.stat(exe) os.chmod(exe, st.st_mode | stat.S_IEXEC) os.execv(exe, sys.argv) diff --git a/nmodl/ast.py b/python/nmodl/ast.py similarity index 100% rename from nmodl/ast.py rename to python/nmodl/ast.py diff --git a/nmodl/dsl.py b/python/nmodl/dsl.py similarity index 100% rename from nmodl/dsl.py rename to python/nmodl/dsl.py diff --git a/nmodl/ext/example/exp2syn.mod b/python/nmodl/ext/example/exp2syn.mod similarity index 100% rename from nmodl/ext/example/exp2syn.mod rename to python/nmodl/ext/example/exp2syn.mod diff --git a/nmodl/ext/example/expsyn.mod b/python/nmodl/ext/example/expsyn.mod similarity index 100% rename from nmodl/ext/example/expsyn.mod rename to python/nmodl/ext/example/expsyn.mod diff --git a/nmodl/ext/example/hh.mod b/python/nmodl/ext/example/hh.mod similarity index 100% rename from nmodl/ext/example/hh.mod rename to python/nmodl/ext/example/hh.mod diff --git a/nmodl/ext/example/passive.mod b/python/nmodl/ext/example/passive.mod similarity index 100% rename from nmodl/ext/example/passive.mod rename to python/nmodl/ext/example/passive.mod diff --git a/nmodl/ext/viz/css/tree.css b/python/nmodl/ext/viz/css/tree.css similarity index 100% rename from nmodl/ext/viz/css/tree.css rename to python/nmodl/ext/viz/css/tree.css diff --git a/nmodl/ext/viz/index.html b/python/nmodl/ext/viz/index.html similarity index 100% rename from nmodl/ext/viz/index.html rename to python/nmodl/ext/viz/index.html diff --git a/nmodl/ext/viz/js/d3.min.js b/python/nmodl/ext/viz/js/d3.min.js similarity index 100% rename from nmodl/ext/viz/js/d3.min.js rename to python/nmodl/ext/viz/js/d3.min.js diff --git a/nmodl/ext/viz/js/tree.js b/python/nmodl/ext/viz/js/tree.js similarity index 100% rename from nmodl/ext/viz/js/tree.js rename to python/nmodl/ext/viz/js/tree.js diff --git a/nmodl/ode.py b/python/nmodl/ode.py similarity index 100% rename from nmodl/ode.py rename to python/nmodl/ode.py diff --git a/nmodl/symtab.py b/python/nmodl/symtab.py similarity index 100% rename from nmodl/symtab.py rename to python/nmodl/symtab.py diff --git a/nmodl/visitor.py b/python/nmodl/visitor.py similarity index 100% rename from nmodl/visitor.py rename to python/nmodl/visitor.py diff --git a/pywheel/shim/nmodl b/pywheel/shim/nmodl deleted file mode 120000 index cf9ff4fba..000000000 --- a/pywheel/shim/nmodl +++ /dev/null @@ -1 +0,0 @@ -_binwrapper.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 804efaa69..665e4f40c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,26 @@ +# build time dependencies +scikit-build-core +setuptools-scm>=8.0 Jinja2>=2.9.3 PyYAML>=3.13 -pytest +# runtime dependencies +find_libpython +sympy>=1.3 +importlib-metadata;python_version<'3.9' +importlib-resources;python_version<'3.9' +# dependencies for test +pytest>=3.3.0 pytest-cov -sympy numpy -find_libpython -scikit-build -importlib_resources;python_version<'3.9' -importlib_metadata;python_version<'3.9' \ No newline at end of file +# dependencies for docs +jupyter-client +jupyter +myst_parser<2.0.0 +mistune<3 +nbconvert +nbsphinx>=0.3.2 +sphinxcontrib-applehelp<1.0.3 +sphinxcontrib-htmlhelp<=2.0.0 +sphinx<6 +sphinx-rtd-theme +docutils<0.20 diff --git a/setup.cfg b/setup.cfg index 9de58dd72..54a600626 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[tool:pytest] -testpaths = test/unit/pybind - [doctest] builder = doctest diff --git a/setup.py b/setup.py deleted file mode 100644 index c55bdc2a7..000000000 --- a/setup.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright 2023 Blue Brain Project, EPFL. -# See the top-level LICENSE file for details. -# -# SPDX-License-Identifier: Apache-2.0 - -import inspect -import os -import subprocess -import sys - -from setuptools import Command -from skbuild import setup - -""" -A generic wrapper to access nmodl binaries from a python installation -Please create a softlink with the binary name to be called. -""" -import stat -from find_libpython import find_libpython - - -# Main source of the version. Dont rename, used by Cmake -try: - v = ( - subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE) - .stdout.strip() - .decode() - ) - __version__ = v[: v.rfind("-")].replace("-", ".") if "-" in v else v - # allow to override version during development/testing - if "NMODL_WHEEL_VERSION" in os.environ: - __version__ = os.environ['NMODL_WHEEL_VERSION'] -except Exception as e: - raise RuntimeError("Could not get version from Git repo") from e - - -class lazy_dict(dict): - """When the value associated to a key is a function, then returns - the function call instead of the function. - """ - - def __getitem__(self, item): - value = dict.__getitem__(self, item) - if inspect.isfunction(value): - return value() - return value - - -def get_sphinx_command(): - """Lazy load of Sphinx distutils command class - """ - # If nbconvert is installed to .eggs on the fly when running setup.py then - # templates from it will not be found. This is a workaround. - if 'JUPYTER_PATH' not in os.environ: - import nbconvert - os.environ['JUPYTER_PATH'] = os.path.realpath(os.path.join(os.path.dirname(nbconvert.__file__), '..', 'share', 'jupyter')) - print("Setting JUPYTER_PATH={}".format(os.environ['JUPYTER_PATH'])) - - from sphinx.setup_command import BuildDoc - - return BuildDoc - - -class Docs(Command): - description = "Generate & optionally upload documentation to docs server" - user_options = [] - finalize_options = lambda self: None - initialize_options = lambda self: None - - def run(self, *args, **kwargs): - self.run_command("doctest") - self.run_command("buildhtml") - - -install_requirements = [ - "PyYAML>=3.13", - "sympy>=1.3", - "find_libpython", - "importlib_resources;python_version<'3.9'", - "importlib_metadata;python_version<'3.9'", -] - - -cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable] -if "bdist_wheel" in sys.argv: - cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE") - cmake_args.append("-DNMODL_ENABLE_TESTS=FALSE") - -# For CI, we want to build separate wheel -package_name = "NMODL" -if "NMODL_NIGHTLY_TAG" in os.environ: - package_name += os.environ["NMODL_NIGHTLY_TAG"] - -# Parse long description from README.rst -with open('README.rst', 'r', encoding='utf-8') as f: - long_description = f.read() - -setup( - name=package_name, - version=__version__, - author="Blue Brain Project", - author_email="bbp-ou-hpc@groupes.epfl.ch", - description="NEURON Modeling Language Source-to-Source Compiler Framework", - long_description=long_description, - long_description_content_type='text/markdown', - packages=["nmodl"], - scripts=["pywheel/shim/nmodl"], - include_package_data=True, - cmake_minimum_required_version="3.15.0", - cmake_args=cmake_args, - cmdclass=lazy_dict( - docs=Docs, doctest=get_sphinx_command, buildhtml=get_sphinx_command, - ), - zip_safe=False, - # myst_parser 2.0.0 want sphinx >= 6 but it leads to incompatibily with sphinxcontrib-applehelp and - # sphinxcontrib-htmlhelp that want PEP-517 support instead of setup.py (name clash with '-' and '.') - # So we pin low versions of packages - setup_requires=[ - "jinja2>=2.9.3", - "jupyter-client", - "jupyter", - "myst_parser<2.0.0", - "mistune<3", - "nbconvert", - "nbsphinx>=0.3.2", - "pytest>=3.7.2", - "sphinxcontrib-applehelp<1.0.3", # After this version it needs a toml file to work, no more setup.py - "sphinxcontrib-htmlhelp<=2.0.0", # After this version it needs a toml file to work, no more setup.py - "sphinx<6", - "sphinx-rtd-theme", # needs sphinx < 7 - "docutils<0.20", # needed by sphinx - ] - + install_requirements, - install_requires=install_requirements, -) diff --git a/src/language/code_generator.cmake b/src/language/code_generator.cmake index 0c6ccae18..a3dea0767 100644 --- a/src/language/code_generator.cmake +++ b/src/language/code_generator.cmake @@ -34,9 +34,9 @@ set(CODE_GENERATOR_JINJA_FILES set(CODE_GENERATOR_PY_FILES ${PROJECT_SOURCE_DIR}/src/language/argument.py ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${PROJECT_SOURCE_DIR}/src/language/language_parser.py ${PROJECT_SOURCE_DIR}/src/language/node_info.py ${PROJECT_SOURCE_DIR}/src/language/nodes.py - ${PROJECT_SOURCE_DIR}/src/language/language_parser.py ${PROJECT_SOURCE_DIR}/src/language/utils.py ) diff --git a/src/pybind/CMakeLists.txt b/src/pybind/CMakeLists.txt index 9600515e1..0d7516874 100644 --- a/src/pybind/CMakeLists.txt +++ b/src/pybind/CMakeLists.txt @@ -70,14 +70,14 @@ endif() # ============================================================================= file( GLOB NMODL_PYTHON_FILES - RELATIVE "${NMODL_PROJECT_SOURCE_DIR}/nmodl/" - CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") + RELATIVE "${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/" + CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/*.py") foreach(file IN LISTS NMODL_PYTHON_FILES) - cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file} OUTPUT + cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/${file} OUTPUT ${CMAKE_BINARY_DIR}/lib/nmodl/${file}) endforeach() -file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) +file(COPY ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) # ============================================================================= # Install python binding components