diff --git a/.github/workflows/check_cmake_format.yml b/.github/workflows/check_cmake_format.yml index cd6952e..a874036 100644 --- a/.github/workflows/check_cmake_format.yml +++ b/.github/workflows/check_cmake_format.yml @@ -4,7 +4,7 @@ on: [pull_request, push] jobs: build: name: check-cmake-format - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Run on external PRs, but not internal PRs as they'll be run by the push if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository diff --git a/.github/workflows/check_code_formatting.yml b/.github/workflows/check_code_formatting.yml index ae4cda6..0b6bc4f 100644 --- a/.github/workflows/check_code_formatting.yml +++ b/.github/workflows/check_code_formatting.yml @@ -4,7 +4,7 @@ on: [pull_request, push] jobs: build: name: check-code-formatting - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Run on external PRs, but not internal PRs as they'll be run by the push if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository diff --git a/.github/workflows/check_python_flake8.yml b/.github/workflows/check_python_flake8.yml index 9fdcd9d..2273e16 100644 --- a/.github/workflows/check_python_flake8.yml +++ b/.github/workflows/check_python_flake8.yml @@ -4,7 +4,7 @@ on: [pull_request, push] jobs: build: name: check-python-flake8 - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Run on external PRs, but not internal PRs as they'll be run by the push if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository diff --git a/.github/workflows/check_python_format.yml b/.github/workflows/check_python_format.yml index 40eb00f..185daa1 100644 --- a/.github/workflows/check_python_format.yml +++ b/.github/workflows/check_python_format.yml @@ -4,7 +4,7 @@ on: [pull_request, push] jobs: build: name: check-python-format - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Run on external PRs, but not internal PRs as they'll be run by the push if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository diff --git a/.github/workflows/check_python_test.yml b/.github/workflows/check_python_test.yml index ce42f39..71944b8 100644 --- a/.github/workflows/check_python_test.yml +++ b/.github/workflows/check_python_test.yml @@ -4,7 +4,7 @@ on: [pull_request, push] jobs: build: name: check-python-test - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Run on external PRs, but not internal PRs as they'll be run by the push if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository diff --git a/README.md b/README.md index 08cb3d1..fa3ea8c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# BlueBrain HPC Team C++ Development Guidelines +# NEURON Team Development Guidelines + +**IMPORTANT NOTE**: this is a fork of the archived [Blue Brain Project HPC Team coding conventions](https://github.com/BlueBrain/hpc-coding-conventions), adapted for use in NEURON. This document describes both C++ development guidelines adopted by -HPC team, and the tools and processes required to +the NEURON team, and the tools and processes required to ensure they are properly followed over time. ## Documentation @@ -54,7 +56,7 @@ Optionally, it will also look for: You can import this CMake project into your Git repository using a git submodule: ``` -git submodule add https://github.com/BlueBrain/hpc-coding-conventions.git +git submodule add https://github.com/neuronsimulator/coding-conventions.git git submodule update --init --recursive ``` @@ -384,9 +386,13 @@ This project provides helper functions to deal with these dependencies: Should you want to contribute to the naming conventions, please refer to the dedicated [contributing document](./cpp/formatting/CONTRIBUTING.md) first. +## Funding + +The development of this software was supported by funding to the Laboratory of Neural Microcircuitry (LNMC), a research center of the École polytechnique fédérale de Lausanne (EPFL), with funding from European Union's Horizon Europe Grant no. 101147319 (EBRAINS 2.0: A Research Infrastructure to Advance Neuroscience and Brain Health). -## Funding & Acknowledgment +## Past Funding The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government's ETH Board of the Swiss Federal Institutes of Technology. -Copyright © 2019-2022 Blue Brain Project/EPFL +Copyright © 2019-2024 Blue Brain Project/EPFL +Copyright © 2025-2026 EPFL diff --git a/cpp/lib.py b/cpp/lib.py index bf3add0..0bb6a68 100644 --- a/cpp/lib.py +++ b/cpp/lib.py @@ -8,6 +8,8 @@ import copy from fnmatch import fnmatch import functools +import importlib.metadata +import importlib.util import logging import operator import os @@ -19,11 +21,9 @@ import subprocess import sys import tempfile -from typing import List import urllib.request import venv - THIS_SCRIPT_DIR = Path(__file__).resolve().parent DEFAULT_RE_EXTRACT_VERSION = "([0-9]+\\.[0-9]+(\\.[0-9]+)?[ab]?)" @@ -36,22 +36,39 @@ def forget_python_pkg(package): Args: package: name of the Python package to exclude """ - import pkg_resources - try: - dist = pkg_resources.get_distribution(package) - except pkg_resources.DistributionNotFound: + dist = importlib.metadata.distribution(package) + except importlib.metadata.PackageNotFoundError: return + + dist_location = None + if dist.files: + # Get the parent directory of the first file in the distribution + first_file = next(iter(dist.files)) + dist_location = str(first_file.locate().parent.parent) + else: + # Fallback: try to get location from sys.path and package name + try: + spec = importlib.util.find_spec(package) + if spec and spec.origin: + # Get the parent directory containing the package + dist_location = str(Path(spec.origin).parent.parent) + except (AttributeError, ImportError): + pass + + if dist_location is None: + return + PYTHONPATH = os.environ.get("PYTHONPATH") - if PYTHONPATH is not None and dist.location in PYTHONPATH: + if PYTHONPATH is not None and dist_location in PYTHONPATH: logging.debug( "Remove incompatible version of %s in PYTHONPATH: %s", package, - dist.location, + dist_location, ) - os.environ["PYTHONPATH"] = PYTHONPATH.replace(dist.location, "") + os.environ["PYTHONPATH"] = PYTHONPATH.replace(dist_location, "") try: - sys.path.remove(dist.location) + sys.path.remove(dist_location) except ValueError: pass @@ -274,7 +291,6 @@ def __init__(self, path: str): assert isinstance(path, Path) self._path = path os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" - self.ensure_requirement("setuptools", restart=True) @property def path(self) -> str: @@ -300,7 +316,7 @@ def interpreter(self) -> str: """ return self.bin_dir.joinpath("python") - def pip_cmd(self, *args) -> List[str]: + def pip_cmd(self, *args) -> list[str]: """ Execute a pip command @@ -393,16 +409,26 @@ def is_requirement_met(self, requirement) -> bool: False otherwise """ try: - import pkg_resources + import packaging.requirements + + # Parse the requirement string + req = packaging.requirements.Requirement(str(requirement)) + + # Check if the package is installed + try: + dist = importlib.metadata.distribution(req.name) + except importlib.metadata.PackageNotFoundError: + return False + + # Check if the version satisfies the requirement + if req.specifier and not req.specifier.contains(dist.version): + return False - pkg_resources.require(str(requirement)) return True except ImportError: - self._install_requirement("setuptools", restart=True) - except pkg_resources.ContextualVersionConflict as conflict: - forget_python_pkg(conflict.req.name) - return False - except (pkg_resources.VersionConflict, pkg_resources.DistributionNotFound): + self._install_requirement("packaging", restart=True) + except Exception: + # Handle any parsing or version checking errors return False def ensure_requirement(self, requirement, restart=True): @@ -542,9 +568,12 @@ def requirement(self): name = pip_pkg else: name = self.name - import pkg_resources - return pkg_resources.Requirement.parse(f"{name} {self.user_config['version']}") + import packaging.requirements + + return packaging.requirements.Requirement( + f"{name} {self.user_config['version']}" + ) @abc.abstractmethod def configure(self): @@ -767,7 +796,9 @@ def find_tool_in_path(self, search_paths=None): if not paths: raise FileNotFoundError(f"Could not find tool {self}") all_paths = [(p, self.find_version(p)) for p in paths] - paths = list(filter(lambda tpl: tpl[1] in self.requirement, all_paths)) + paths = list( + filter(lambda tpl: self.requirement.specifier.contains(tpl[1]), all_paths) + ) paths = list(sorted(paths, key=lambda tup: tup[1])) # sort by version if not paths: raise FileNotFoundError( @@ -813,9 +844,11 @@ def find_version(self, path: str) -> str: pkg_name = self.name if isinstance(self.config["capabilities"].pip_pkg, str): pkg_name = self.config["capabilities"].pip_pkg - import pkg_resources - return pkg_resources.get_distribution(pkg_name).version + import packaging.requirements + + req = packaging.requirements.Requirement(pkg_name) + return importlib.metadata.version(req.name) cmd = [path] + self._config["version_opt"] log_command(cmd)