pax_global_header00006660000000000000000000000064143420664740014524gustar00rootroot0000000000000052 comment=c539260e20d94033ce2eaa69e0aab1c78451d4d6 bluebrain-hpc-coding-conventions-1.0.0+git20221201/000077500000000000000000000000001434206647400214325ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/.bbp-project.yaml000066400000000000000000000003561434206647400246070ustar00rootroot00000000000000tools: ClangFormat: enable: True version: ~=14.0 CMakeFormat: enable: True Flake8: enable: True include: &pyfiles match: - bin/.* - .*\.py Black: enable: True include: *pyfiles bluebrain-hpc-coding-conventions-1.0.0+git20221201/.ci/000077500000000000000000000000001434206647400221035ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/.ci/check_code_formatting.sh000077500000000000000000000011461434206647400267450ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail VENV=build/venv-code-formatting if [[ ! -d $VENV ]]; then python3 -mvenv "$VENV" "$VENV/bin/pip" install \ jinja2 \ pyyaml fi set +u # ignore errors in virtualenv's activate source "$VENV/bin/activate" set -u cd cpp/formatting make distclean all if ! git diff --exit-code README.md ;then cat >&2 <10 proj) | | --------------------- | ------------------ | ------------------ | ------------------ | ------------------- | | ClangFormat | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | WIP | | ClangTidy | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | WIP | | Naming Conventions | :heavy_check_mark: | :heavy_check_mark: | :no_entry_sign: N/A | :no_entry_sign: N/A | | Writing Documentation | :heavy_check_mark: | :heavy_check_mark: | | | | Project template | WIP | | | | | Good Practices | | | | | | Memory Check | | | | | | UT Code Coverage | | | | | ## CMake Project This directory provides a CMake project that allows one to use the tools and the processes described in this document. This project requires CMake version 3.10 or higher. ### Requirements This CMake project expects for the following utilities to be available: * [Python 3.5 or higher](https://python.org) * [ClangFormat 9](https://releases.llvm.org/9.0.0/tools/clang/docs/ClangFormat.html) * [ClangTidy 7](https://releases.llvm.org/7.0.0/tools/clang/tools/extra/docs/clang-tidy/index.html) * [cmake-format](https://github.com/cheshirekow/cmake_format) Python package version 0.6 * [pre-commit](https://pre-commit.com/) Python package * [git](https://git-scm.com/) version control system 2.17 or higher. Optionally, it will also look for: * [valgrind](http://valgrind.org/) memory checker * code coverage utilities like gcov, lcov, or gcov ### Installation 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 update --init --recursive ``` Note: if your project already has a `cmake` sub-directory, it is recommended to create the submodule in this directory instead of top-level. Then simply add the following line in the top `CMakeLists.txt`, after your project declaration: ``` project(mylib CXX) # [...] add_subdirectory(hpc-coding-conventions/cpp) ``` After cloning or updating this git submodule, run CMake to take benefits of the latest changes. This will setup or update git [pre-commit](https://pre-commit.com) hooks of this repository. CMake variables defined by this project are prefixed by `${PROJECT_NAME}` by default. The CMake variable `CODING_CONV_PREFIX` allows to specify another prefix. It must be defined before including this CMake project, for instance: ```cmake project(mylib CXX) # [...] set(CODING_CONV_PREFIX Foo) add_subdirectory(hpc-coding-conventions/cpp) ``` ### Usage #### Code Formatting Create file `.bbp-project.yaml` at the root of your project and enable the formatting tools, you want to enable, for instance: ```yaml tools: ClangFormat: enable: True CMakeFormat: enable: True ``` You can then use the `bin/format` utility. #### Version deduction `bin/format` relies on `PATH` environment variable to locate the proper tools. You can override the default required versions specified in `bbp-project.yaml`: ```yaml tools: ClangFormat: enable: True version: ~=15.0.0 ``` It is also possible to override the path to a tool in the YAML config file: ```yaml tools: CMakeFormat: path: /path/to/bin/cmake-format ``` ##### Usage * To format the entire codebase: `/path/to/hpc-coding-conventions/bin/format` * To check the formatting: `/path/to/hpc-coding-conventions/bin/format -n` * To format the CMake files only: `/path/to/hpc-coding-conventions/bin/format --lang CMake` * To check the formatting of C++ files in a specific locations: `/path/to/hpc-coding-conventions/bin/format -n --lang c++ src/path1/ /src/path2/foo.cpp` ##### Advanced configuration You can override the default file filters of a tool, for instance: ```yaml tools: ClangFormat: include: match: &cpp_patterns - src/steps/.*\.((h)|(cpp)|(hpp)) - test/unit/.*\.((h)|(cpp)|(hpp)) path: /foo/bin/clang-format ClangTidy: include: match: *cpp_patterns ``` ##### Custom ClangFormat configuration These coding conventions come with a predefined configuration of ClangFormat that is by default copied in the top directory of the project. It is recommended to not add this `.clang-format` to git so that it is fully driven by this project. It will get updated along with the evolution of these guidelines. Thus it is recommended to inform git to ignore this file by adding it to the top `.gitignore`. A project can however override the predefined configuration of ClangFormat in two ways: 1. Create a `.clang-format.changes` containing only the required modifications. 1. Add `.clang-format` to the git repository. This project will never try to modify it. ##### Custom CMakeFormat configuration Like ClangFormat, these coding conventions already provide a CMakeFormat configuration that the user can customize with a file named `.cmake-format.changes.yaml` placed at the project's root directory. This file can be used to specify the signature of owned CMake functions and macros, for instance: ```yaml additional_commands: add_mpi_test: kwargs: NAME: 1 NUM_PROCS: 1 COMMAND: '*' ``` will allow CMakeFormat to properly format functions calls like below: ```cmake add_mpi_test( NAME OpSplitOmega_h_2D NUM_PROCS 2 COMMAND $ --num-iterations 100 square.msh) ``` ##### Continuous Integration Define `${PROJECT}_TEST_FORMATTING:BOOL` CMake variable to enforce formatting during the `test` make target. #### Static Analysis To activate static analysis of C++ files with clang-tidy within CMake, enable the CMake variable `${PROJECT}_STATIC_ANALYSIS` where `${PROJECT}` is the name given to the CMake `project` function. For instance, given a project `foo`: `cmake -DFoo_STATIC_ANALYSIS:BOOL=ON ` Whenever a C++ file is compiled by CMake, clang-tidy will be called. You can also use utility: `bin/clang-tidy` ##### Usage This will provide a `clang-tidy` *make* target that will perform static analysis of all C++ files. Target fails as soon as one defect is detected among the files. It will also activate static analysis report during the compilation phase. ##### Advanced configuration The following CMake cache variables can be used to customize the static analysis of the code: * `${PROJECT}_ClangTidy_DEPENDENCIES`: list of CMake targets to build before check C/C++ code. Default value is `""` These variables are meant to be overridden inside your CMake project. They are CMake _CACHE_ variables whose value must be forced **before including this CMake project**. ##### Custom ClangTidy configuration These coding conventions come with a `.clang-tidy` file providing a predefined list of ClangTidy checks. By default, this file is copied in the top directory of the project. It is recommended to not add this `.clang-tidy` to git so that it is fully driven by this project. It will get updated along with the evolution of these guidelines. Thus it is recommended to inform git to ignore this file by adding it to the top `.gitignore`. A project can however override the predefined configuration of ClangFormat in two ways: 1. Create a `.clang-tidy.changes` containing only the required modifications. 1. Add `.clang-tidy` to the git repository. This project will never try to modify it. #### Pre-Commit utility Enable CMake option `${PROJECT}_GIT_HOOKS` to enable automatic checks before committing or pushing with git. the git operation will fail if one of the registered checks fails. The following checks are available: * `check-clang-format`: check C++ formatting * `check-cmake-format`: check CMake formatting * `clang-tidy`: execute static-analysis * `courtesy-msg`: print a courtesy message to the console. This check never fails. The default message is a reminder to test and format the changes when pushing a contribution with git. A project can overwrite the message displayed by adding the CMake template named `.git-push-message.cmake.in` at the root of the project directory. To enable these checks, use CMake variables `${PROJECT}_GIT_COMMIT_HOOKS` and `${PROJECT}_GIT_PUSH_HOOKS` to specify which checks should be executed for each specific git operation. For instance: `cmake -Dfoo_GIT_COMMIT_HOOKS=clang-tidy \ -Dfoo_GIT_PUSH_HOOKS=check-clang-format,courtesy-msg ` This feature requires the `pre-commit` utility. #### Bob `bob.cmake` is a CMake utility file part of hpc-coding-conventions that provides a set of convenient macros and functions used to: * specify your project options and dependencies * specify the proper compilation flags * install the proper CMake export flags so that your project can be loaded by another project with the `find_package` CMake function. ##### Compilation flags By default, CMake relies on the `CMAKE_BUILD_TYPE` variable to set the proper compilation flags. Because _bob_ is now taking care of it, you must configure your project with `CMAKE_BUILD_TYPE` empty. _bob_ sets the compilation flags according to a set of CMake variables: * `${PROJECT_NAME}_CXX_OPTIMIZE:BOOL`: Compile C++ with optimization (default is ON) * `${PROJECT_NAME}_CXX_SYMBOLS:BOOL`: Compile C++ with debug symbols (default is ON) * `${PROJECT_NAME}_CXX_WARNINGS:BOOL`: Compile C++ with warnings" (default is ON) * `${PROJECT_NAME}_EXTRA_CXX_FLAGS:STRING`: Additional C++ compilation flags * `${PROJECT_NAME}_CXX_FLAGS:STRING`: bypass variables above and use the specified compilation flags. `CMAKE_BUILD_TYPE` is ignored. * `${PROJECT_NAME}_NORMAL_CXX_FLAGS:BOOL`: Allow `CMAKE_CXX_FLAGS` to follow _normal_ CMake behavior and bypass all variables above. Default `CMAKE_CXX_FLAGS` variable value is taken into account. ##### Integration The top-level CMakelists.txt of your project may look like: ```cmake cmake_minimum_required(VERSION 3.10) project(HelloWorld VERSION 1.0.0 LANGUAGES CXX) add_subdirectory(hpc-coding-conventions/cpp) bob_begin_package() bob_begin_cxx_flags() bob_cxx17_flags() # specify custom compilation flags find_package(OpenMP) if(OpenMP_FOUND) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_FLAGS "${CMAKE_FLAGS} ${OpenMP_FLAGS}") else() message(WARNING "OpenMP support is disabled because it could not be found.") endif() bob_end_cxx_flags() # specify your targets: add_library(...) add_executable(...) bob_end_package() ``` #### Embedded third parties External libraries required to build or test your C++ project can be either directly added to the git repository or as a git submodule. The standard root location for this kind of files is the `3rdparty/` directory but can be overriden with the `${PROJECT_NAME}_3RDPARTY_DIR` CMake variable. Adding single-file/header-only C++ libraries directly to the git repository of your project is acceptable in general, like catch2 or the JSON library of Niels Lohmann for instance. More significant dependencies should be considered as pure external dependencies. But it can also be very convenient to have them as git submodules, and be able to switch between the two. This project provides helper functions to deal with these dependencies: ###### cpp_cc_git_submodule ````cmake # # cpp_cc_git_submodule(source_dir # [DISABLED] # SUBDIR path # [BUILD] [] # [PACKAGE] [] # GIT_ARGS []) # # Add a CMake option in the cache to control whether the # submodule is used or not (default ON). The option is named after the source # directory passed in first argument, for instance: # cpp_cc_git_submodule(src/eigen) # adds the following CMake cached option: # ${PROJECT_NAME}_3RDPARTY_USE_SRC_EIGEN:BOOL=ON # # If enabled, then the submodule is fetched if missing in the working copy. # # If the DISABLED argument is provided, then the default value for the CMake # option is OFF. # # If the BUILD argument is provided then the directory is added to the build # through the add_subdirectory CMake function. Arguments following the BUILD # arguments are passed to the add_subdirectory function call. # # The optional SUBDIR argument is used by the BUILD argument to determine # the path to the directory added to the build. The path specified is relative # to the path to the git submodule. # # If the PACKAGE argument is provided and the CMake option to determine whether # the git submodule should be used or not is FALSE, then a call to the find_package # function is made with the arguments specified to the PACKAGE option. # # Default options passed to the `git submodule update` command are # `--init --recursive --depth 1` to perform a shallow clone of the submodule. # If the GIT_ARGS argument is provided, then its value supersedes the default options. # ```` ## Contributing Should you want to contribute to the naming conventions, please refer to the dedicated [contributing document](./cpp/formatting/CONTRIBUTING.md) first. ## Funding & Acknowledgment 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 bluebrain-hpc-coding-conventions-1.0.0+git20221201/bbp-project.yaml000066400000000000000000000035421434206647400245310ustar00rootroot00000000000000tools: # a tool is implicitly enabled when specified here unless explicitely disabled # not all sections are mandatory, not mentioning a tool means it is not enabled. ClangFormat: enable: False # Any Python version specifiers as described in PEP 440 # https://peps.python.org/pep-0440/#compatible-release # For instance: # # Exact match required: == 13.0.0 # At least 13.0.1, but less than 13.1: >= 13.0.1, == 13.0.* # Same as above: ~= 13.0.1 version: ~=13.0 # appended to clang-format command line before the file to process. Can be: # - None # - a string # - a list of string option: include: match: - .*\.[it]?cc?$ - .*\.hh?$ - .*\.[chit]((pp)|(xx))$ CMakeFormat: enable: False version: ~=0.6 include: match: - .*\.cmake$ - .*CMakeLists.txt$ ClangTidy: # static analysis with ClangTidy is explicitely disabled enable: False version: ">=7" option: -extra-arg=-Wno-unknown-warning-option # can specify path to JSON Clang database here, or through CLI which takes precedence compile_commands_file: include: match: .*\.c((c)|(pp)|(xx))$ Flake8: enable: False version: ">=4" option: - --import-order-style - google # Additional Python packages to install requirements: - flake8-import-order include: match: - .*\.py$ Black: enable: False version: ~=22.3 # will come later # PreCommit: # enable: False # version: ">=2.10" # hooks: # commit: # - check-clang-format # - check-cmake-format # push: # - clang-tidy # - courtesy-msg # Global settings to every tool, can be locally overriden within the # section config of a tool global: exclude: match: - third[-_]party/.* - third[-_]parties/.* bluebrain-hpc-coding-conventions-1.0.0+git20221201/bin/000077500000000000000000000000001434206647400222025ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/bin/.bbp-task000077500000000000000000000005271434206647400237150ustar00rootroot00000000000000#!/usr/bin/env python3 import os from pathlib import Path import sys sys.path.append(str(Path(__file__).resolve().parent.parent)) from cpp.lib import BBPProject # noqa: E402 def main(args=None): task = os.path.basename(__file__) return BBPProject.run_task(task) if __name__ == "__main__": sys.exit(0 if main() == 0 else 1) bluebrain-hpc-coding-conventions-1.0.0+git20221201/bin/clang-tidy000077700000000000000000000000001434206647400256572.bbp-taskustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/bin/format000077700000000000000000000000001434206647400251142.bbp-taskustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/bin/static-analysis000077700000000000000000000000001434206647400267342.bbp-taskustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/000077500000000000000000000000001434206647400222145ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/CMakeLists.txt000066400000000000000000000121371434206647400247600ustar00rootroot00000000000000list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE) set(CMAKE_EXPORT_COMPILE_COMMANDS ON PARENT_SCOPE) if(NOT CODING_CONV_PREFIX) set(CODING_CONV_PREFIX "${PROJECT_NAME}") set(CODING_CONV_PREFIX "${PROJECT_NAME}" PARENT_SCOPE) endif(NOT CODING_CONV_PREFIX) include(cmake/bob.cmake) include(cmake/3rdparty.cmake) include(cmake/build-time-copy.cmake) include(cmake/bbp-find-python-module.cmake) function(cpp_cc_enable_precommit) find_package(PythonInterp 3.5 REQUIRED) find_package(PreCommit REQUIRED) if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) execute_process(COMMAND ${PreCommit_EXECUTABLE} install) endif() if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-push) execute_process(COMMAND ${PreCommit_EXECUTABLE} install --hook-type pre-push) endif() if(${CODING_CONV_PREFIX}_GIT_COMMIT_HOOKS MATCHES "courtesy-msg" OR ${CODING_CONV_PREFIX}_GIT_PUSH_HOOKS MATCHES "courtesy-msg") if(EXISTS ${PROJECT_SOURCE_DIR}/.git-push-message.cmake.in) configure_file(${PROJECT_SOURCE_DIR}/.git-push-message.cmake.in ${PROJECT_BINARY_DIR}/git-push-message.cmake @ONLY) else() configure_file(cmake/git-push-message.cmake.in ${PROJECT_BINARY_DIR}/git-push-message.cmake @ONLY) endif() endif() execute_process( COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/bbp-setup-pre-commit-config.py" --commit-checks=${${CODING_CONV_PREFIX}_GIT_COMMIT_HOOKS} --push-checks=${${CODING_CONV_PREFIX}_GIT_PUSH_HOOKS} ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) add_custom_target(git-pre-commits ${PreCommit_EXECUTABLE} run --all-files) endfunction(cpp_cc_enable_precommit) function(cpp_cc_disable_precommit) if(EXISTS ${PROJECT_SOURCE_DIR}/.pre-commit-config.yaml) find_package(PythonInterp 3.5 REQUIRED) execute_process( COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/bbp-setup-pre-commit-config.py" --commit-checks= --push-checks= ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) endif() file(REMOVE ${PROJECT_BINARY_DIR}/git-push-message.cmake) endfunction(cpp_cc_disable_precommit) function(cpp_cc_setup_tool_config name path) execute_process(COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/bbp-${name}.py --executable ${path} config RESULT_VARIABLE ${name}_config) if(NOT ${name}_config EQUAL 0) message(SEND_ERROR "Could not set up ${name} configuration") endif() endfunction() function(cpp_cc_add_tool_target name) add_custom_target( ${name}-${PROJECT_NAME} ${ARGN} JOB_POOL console WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) if(TARGET ${name}) add_dependencies(${name} ${name}-${PROJECT_NAME}) else() add_custom_target( ${name} DEPENDS ${name}-${PROJECT_NAME} JOB_POOL console) endif() endfunction() function(cpp_cc_enable_static_analysis) find_package(PythonInterp 3.5 REQUIRED) set(${CODING_CONV_PREFIX}_ClangTidy_DEPENDENCIES "" CACHE STRING "list of CMake targets to build before checking C/C++ code") mark_as_advanced(${CODING_CONV_PREFIX}_ClangTidy_DEPENDENCIES) set(clang_tidy_command ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/../bin/clang-tidy" -v -p ${PROJECT_BINARY_DIR}/compile_commands.json) cpp_cc_add_tool_target(clang-tidy ${clang_tidy_command}) if(${CODING_CONV_PREFIX}_TEST_STATIC_ANALYSIS) add_test(NAME ClangTidy_${PROJECT_NAME} COMMAND ${clang_tidy_command}) endif() if(${CODING_CONV_PREFIX}_ClangTidy_DEPENDENCIES) add_dependencies(clang-tidy_${PROJECT_NAME} ${${CODING_CONV_PREFIX}_ClangTidy_DEPENDENCIES}) endif() endfunction(cpp_cc_enable_static_analysis) bob_option(${CODING_CONV_PREFIX}_TEST_FORMATTING "Add CTest formatting test" OFF) bob_option(${CODING_CONV_PREFIX}_GIT_HOOKS "Enable automatic checks when committing and pushing changes" OFF) bob_input(${CODING_CONV_PREFIX}_GIT_COMMIT_HOOKS "" STRING "Comma-separated list of checks to perform when committing changes") bob_input(${CODING_CONV_PREFIX}_GIT_PUSH_HOOKS "courtesy-msg" STRING "Comma-separated list of checks to perform when pushing changes") bob_option(${CODING_CONV_PREFIX}_STATIC_ANALYSIS "Enable C++ static analysis during compilation" OFF) bob_option(${CODING_CONV_PREFIX}_TEST_STATIC_ANALYSIS "Add CTest static analysis test" OFF) if(${CODING_CONV_PREFIX}_TEST_FORMATTING) add_test( NAME check_formatting_${PROJECT_NAME} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/../bin/format" --dry-run -v) endif() if(${CODING_CONV_PREFIX}_GIT_HOOKS) cpp_cc_enable_precommit() else() cpp_cc_disable_precommit() endif() if(${CODING_CONV_PREFIX}_STATIC_ANALYSIS) cmake_minimum_required(VERSION 3.6) cpp_cc_enable_static_analysis() set(CMAKE_CXX_CLANG_TIDY ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/../bin/clang-tidy" -p ${PROJECT_BINARY_DIR}/compile_commands.json) endif(${CODING_CONV_PREFIX}_STATIC_ANALYSIS) bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/DevelopmentLifecycle.md000066400000000000000000000024561434206647400266470ustar00rootroot00000000000000# Development Lifecycle ## Writing a contribution ### Checks before submission C++ developers should ensure that their contributions: * are unit-tested and unit-tests pass * pass static analysis checks * follow naming conventions * follow documentation conventions * follow formatting conventions To save both time and effort, they can: * Configure their favorite IDE so that it complies with the formatting conventions of the project. * Use dedicated *make* targets to easily run tests, format code, and run code static analyzer. * Use git precommit hooks to prevent committing unformatted code. ## Submitting a contribution ### Continuous Integration Continuous integration may control most of the checks described above. It can also perform additional checks that should fail if a new error is spotted: * execute Valgrind memory checker. * compute code coverage of unit-tests and/or documentation snippets and reject the merge request if it decreased or is below a certain value.. ### Code Review A code change cannot be accepted unless it has been approved by one or several core maintainers of the project the change is contributing to. A reviewer may ensure that the contribution: * is well designed and efficient * is understandable and documented enough * follows the naming conventions of the project bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/Documentation.md000066400000000000000000000323151434206647400253530ustar00rootroot00000000000000# C++ Project Documentation This document provides instructions and hints to document C++ projects. ## How to document code? ### C++ API Public APIs may be documented with Doxygen, with comments specified in headers so that they remain visible to the developers consuming your APIs. ### C++ Internal Code Documentation of internal code is highly recommended to help the maintainers of the project. Using Doxygen is not mandatory though. But if used, then the documentations of public and internal code should be well separated, such that it remains easy to the reader to distinguish public symbols from internal ones. Documentation of internal symbols can be specified in cpp files where symbols are defined, presumably more accessible to the maintainers. See [the following discussion](https://stackoverflow.com/questions/355619/where-to-put-the-doxygen-comment-blocks-for-an-internal-library-in-h-or-in-cpp) on stackoverflow. #### Documentation style Doxygen supports [multiple syntax](http://www.doxygen.nl/manual/docblocks.html) to document the code. There is no preferred syntax but Doxygen comments of a particular project may use the same one. The recommended one is: ```c++ /** * \brief A brief function description. * * A more elaborated function description. * \param num_mols Number of molecules * \return description of value returned */ int a_function(int num_mols); ``` #### Document files Document header files, and source files if possible by adding a `\file` paragraph in the file prelude. ```c++ /** * \file * A brief file description * A more elaborated file description */ ``` #### Only document the declarations * Document how to use public symbols in the declaration, not the definition. * Specify any additional technical or implementation detail in the definition using the `\internal` Doxygen command. (see [command documentation](http://www.doxygen.nl/manual/commands.html#cmdinternal)) ```c++ /** * Helper function to create a vertex unique identifier. * \param type vertex type * \param id vertex identifier * \return vertex unique identifier */ vertex_uid_t make_id(vertex_t type, vertex_id_t id); // [...] /** * \internal the implementation uses C++11 aggregate initialization. */ vertex_uid_t make_id(vertex_t type, vertex_id_t id) { return {type, id}; } ``` This rule is optional for internal symbols. #### Group member methods together Doxygen supports [grouping of symbols](http://www.doxygen.nl/manual/grouping.html) so that they appear in dedicated subsections in the generated documentation. ```c++ /** * \brief Undirected Connectivity Graph */ class Graph { public: /** * \name Ctor & dtor. * \{ */ /** * load or create a graph from the filesystem * \a path is created if nonexistent. * \param path directory on the filesystem. */ explicit Graph(const std::string& path); ~Graph(); /** * \} */ /** * \name data accessors and modifiers * \{ */ /** * allow the manipulation of the graph's edges */ Edges& edges(); /** * allow the manipulation of the graph's vertices */ Vertices& vertices(); /** * \} */ }; ``` ### Python code Python code may be documented with docstrings formatted using the [Google style](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings). ## Tooling ### Sphinx as the only documentation generator The standard documentation tool for C++ projects is Doxygen whereas Sphinx is more widely used in the Python community. Breathe and Exhale are Sphinx extensions that allow a seamless integration of Doxygen in the Sphinx documentation pipeline. Furthermore, most C++ projects within the HPC team provide Python bindings, which is another reason to use Sphinx as the standard tool to generate documentation of C++ projects. ### Sphinx documentation pipeline `sphinx-build` is the main command line utility to generate documentation. This process gathers a collection of documents in reStructedText to generate the documentation in the desired format, in HTML for instance. There are many Sphinx extensions to emit reStructuredText from other sources. Here is a non-exhaustive list of recommended extensions: #### myst_parser MyST-Parser is a Docutils bridge to markdown-it-py, a Python package for parsing the CommonMark Markdown flavor. For instance you can have a `readme.rst` file that reads the top-level README.md of your project that looks like: ```rst Introduction ============ .. include:: ../README.md :parser: myst_parser.sphinx_ ``` #### breathe Doxygen is known to generated LaTeX or HTML, but it can also generate an XML document containing the same level of information. Breathe is a Sphinx extension to generate reStructuredText files from such XML file. #### exhale Exhale is a Sphinx extension that does not really emit reStructuredText but allow instead to configure and run Doxygen to generate the XML file used by Breathe. #### autodoc Autodoc is a Sphinx extension that imports Python modules, and emits reStructuredText from the docstrings of the symbols. #### napoleon Napoleon is a Sphinx extension that allows autodoc to parse docstrings formatted with the NumPy and Google coding styles. #### doctest When enabled, Sphinx will execute the code snippets embedded in the documentation and fail if they produce unexpected output. For instance, let us consider the following `hello` Python module, part of a package to document: ```python __version__ = '1.0.0' def hello_world(recipient="World"): """Write greeting message to standard output Args: recipient(str): person included in the message >>> hello_world() Hello World! >>> hello_world("Alice") Hello Alice! """ print("Hello", recipient) ``` * If _autodoc_ extension is enabled and properly configured, then Sphinx will load this module, and extract the docstring from the `hello_world` function. * If _napoleon_ extension is enabled, then Sphinx will be able to properly extract the description and the information of the function parameter, formatted using the Google style, to produce a nice HTML document with the code snippet embedded. * If this _doctest_ extension is enabled, the document generation with run the code snippet, and will fail because the output is not the one expected. You realize that you are missing a trailing "!" in the written message. This is a very interesting feature that ensure that the documentation remains up to date, and continuously tested. #### coverage This extension provides the symbols of the Python package that have not been called by the code snippets. The report is written a text file named `doctest/output.txt`. ### Getting Started #### Generate documentation skeleton At the project root directory: ```bash $ mkdir doc $ cd doc $ sphinx-quickstart --sep --makefile --no-batchfile --dot _ \ --suffix .rst --language en --master index \ --ext-autodoc --ext-doctest --ext-coverage --ext-mathjax --ext-viewcode Welcome to the Sphinx 1.8.5 quickstart utility. Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets). Selected root path: . The project name will occur in several places in the built documentation. > Project name: MyProject > Author name(s): BlueBrain HPC Team > Project release []: Creating file ./source/conf.py. Creating file ./source/index.rst. Creating file ./Makefile. Finished: An initial directory structure has been created. You should now populate your master file ./source/index.rst and create other documentation source files. Use the Makefile to build the docs, like so: make builder where "builder" is one of the supported builders, e.g. html, latex or linkcheck. $ git add * $ git commit -m 'Create Sphinx documentation skeleton with sphinx-quickstart' ``` #### Add Python package to the PYTHONPATH used by sphinx This section is only relevant for C++/Python hybrid project distributing a Python package. It is suggested to use setuptools to retrieve the packag version number so that versioning becomes automatic. For instance, for Python package *foo*: ```diff diff --git a/docs/conf.py b/docs/conf.py index 41dc1a7..4b51de3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,7 @@ # import sys # sys.path.insert(0, os.path.abspath('.')) +from pkg_resources import get_distribution # -- Project information ----------------------------------------------------- @@ -23,11 +24,10 @@ copyright = '2019, Foo' author = 'Foo' -# The short X.Y version -version = '' # The full version, including alpha/beta/rc tags -release = '' - +release = get_distribution('foo').version +# The short X.Y.Z version +version = '.'.join(release.split('.')[:3]) ``` #### Generate skeleton or reStructuredText files for Python package The `sphinx-apidoc` utility analyzes Python packages and generates one reStructuredText file per Python module, containing _automodule_ directives. For instance, `sphinx-apidoc -o docs hello` command generates file `docs/hello.rst` that looks like: ```rst Module contents ================ .. automodule:: hello :members: :undoc-members: :show-inheritance: ``` This is enough to generate documentation of the `hello` module. To generate documentation of symbols imported by `hello` module, consider using the `:imported-members:` option of the `automodule` command. #### Integrate documentation of C++ code ```diff diff --git a/doc/source/conf.py b/doc/source/conf.py index 4b51de3..f1109d9 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -39,6 +39,8 @@ release = '0.0.1' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'breathe', + 'exhale', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', @@ -47,6 +49,27 @@ extensions = [ 'sphinx.ext.githubpages', ] +# Setup the breathe extension +breathe_projects = { + "Basalt C++ Library": "./doxyoutput/xml" +} +breathe_default_project = "Basalt C++ Library" + +# Setup the exhale extension +exhale_args = { + # These arguments are required + "containmentFolder": "./cpp_api", + "rootFileName": "library_root.rst", + "rootFileTitle": "C++ API", + "doxygenStripFromPath": "..", + # Suggested optional arguments + "createTreeView": True, + # TIP: if using the sphinx-bootstrap-theme, you need + # "treeViewIsBootstrap": True, + "exhaleExecutesDoxygen": True, + # additional Doxygen config + "exhaleDoxygenStdin": "INPUT = ../include" +} + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/source/index.rst b/doc/source/index.rst index d218dc5..8c1cdd0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -11,6 +11,7 @@ Welcome to Basalt's documentation! :caption: Contents: modules.rst + cpp_api/library_root ``` ### Toward a decent `setup.py` This section is only relevant for C++/Python hybrid project distributing a Python package. Sphinx provides a `build_sphinx` setuptools target to generate documentation with the command: `python setup.py build_sphinx`. #### Setup dependencies Add all Python packages required to build the documentation to the `tests_require` [*setup* keyword](https://setuptools.readthedocs.io/en/latest/setuptools.html#new-and-changed-setup-keywords) For instance: ```python doc_requirements = [ "exhale", "m2r", "sphinx-rtd-theme", "sphinx>=2" ] setup( # [...] tests_require=doc_requirements, ) ``` #### Test command on steroids By default, the command `python setup.py test` builds the package and run the unit-tests. To also execute the code snippets embedded in the documentation during the `test` command, you can: 1. Define a new `doctest` command that only check the code snippets. In `setup.py`: ```python 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 """ from sphinx.setup_command import BuildDoc return BuildDoc setup( # [...] cmdclass=lazy_dict( doctest=get_sphinx_command ) ``` In `setup.cfg`: ``` [doctest] builder = doctest config-dir = docs ``` 1. Overwrite the `test` command so that it also calls the `doctest` command. In `setup.py`: ```python from setuptools.command.test import test class CustomTest(test): """Custom disutils command that acts like as a replacement for the "test" command. It first executes the standard "test" command, and then run the "doctest" to also validate code snippets in the sphinx documentation. """ def run(self): super().run() subprocess.check_call([sys.executable, __file__, "doctest"]) setup( # [...] cmdclass=lazy_dict( test=CustomTest, doctest=get_sphinx_command ) ``` bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/Makefile000066400000000000000000000001511434206647400236510ustar00rootroot00000000000000subdirs = formatting all clean distclean: $(foreach dir,$(subdirs),$(MAKE) -C $(dir) $(MAKEFLAGS) $@;) bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/NamingConventions.md000066400000000000000000000060701434206647400262000ustar00rootroot00000000000000# C++ Naming Conventions This document provides a set of recommendations regarding how things should be named in C++ projects. Because there is no automatic way to ensure that a code change follows this guideline, it is the responsibility of the code reviewers to enforce these rules over time. Besides, CLion 2019.1.1 allows coherent [C++ naming of symbols](https://blog.jetbrains.com/clion/2019/03/clion-2019-1-embedded-dev-clangformat-memory-view/#code_style). Should a project decide to not apply some or all of the recommendations below, it may provide written guidelines to the contributors with the rules that should be followed instead, in a `CONTRIBUTING.md` document for instance. ## File Names Follow [File Names](https://google.github.io/styleguide/cppguide.html#General_Naming_Rules) convention of Google C++ Style Guide but use the following extension instead: * C++ files: `.cpp` * C++ header: `.h` * C++ template definitions: `.ipp` ## Use descriptive variable names See [General Naming Rules](https://google.github.io/styleguide/cppguide.html#General_Naming_Rules) in Google C++ Style Guide. Generally use `snake_case` style as it is compliant with spell checkers. ### Banish universal variables This is probably the most important point of this document. Symbols names should speak for themselves to highlight they particular usage. ```cpp // no int count; // yes int num_molecules; ``` ### Concision does not necessarily mean "smallest as possible" ```cpp // no int bufsz; // yes int buffer_size; ``` ### The naming is domain specific There are no strict rules, and every project may adapt the naming conventions according to the domains they apply to. For instance, the _degrees of freedom_ of a system in physics or statistics is a quantity commonly used. In this regards, projects in such domains may use the `dof` abbreviation instead of naming every variable `degrees_of_freedom`. The full term can still be mentioned in comment though. Besides, new contributors can get up to speed quicker if they can refer to a glossary. ### Exception Function parameters and local variables used only within less than 3 lines can break this rule, for instance: ```cpp struct Dimension { const int width; const int height; Dimension(int w, int h) : width(w) , height(h) {} }; ``` ## Functions and variables start with a lower case ```cpp void my_function() { int my_var; }; ``` ## Types names use camel case ```cpp class MyClass using MyClassVector = std::vector ``` ## Template parameter names use camel case: `InputIterator` ```cpp template void advance(InputIterator& it, Distance n); ``` ## Constants are all upper case with underscores ```cpp const double AVOGADRO = 6.022140857e23; const int LIGHT_SPEED = 299792458L; ``` ## Namespace names are all lower-case See [Namespace Names](https://google.github.io/styleguide/cppguide.html#Namespace_Names) in Google C++ Style Guide. ### code comments See [Comment Style](https://google.github.io/styleguide/cppguide.html#Comment_Style) in Google C++ Style Guide. bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/Tooling.md000066400000000000000000000070451434206647400241570ustar00rootroot00000000000000# Tooling ## C++ Compiler ### C++ Standard The minimal C++ standard officially supported at BBP is C++17, to be reviewed annually based on compiler support. An open source project may support a standard as low as C++14 if this is a requirement for its community. ### Compiler Versions Supporting C++17 * GCC 7.4 * CLang 8 * Intel 19.1.0 ### Compilation flags Here is the list of recommended compilation flags you may use during the development phase in order to spot as much errors as possible. For convenience, you may use the CMake function `bob_begin_cxx_flags` provided by this CMake project to set the compilation flags accordingly. * Clang: `-Werror -Weverything`. And then disable those you dislike: * `-Wno-disabled-macro-expansion` * `-Wno-documentation-unknown-command` * `-Wno-padded` * `-Wno-unused-member-function` * GCC: * `-Wall` * `-Wcast-align` * `-Wconversion` * `-Wdouble-promotion` * `-Werror` * `-Wextra` * `-Wformat=2` * `-Wnon-virtual-dtor` * `-Wold-style-cast` * `-Woverloaded-virtual` * `-Wshadow` * `-Wsign-conversion` * `-Wunused` * `-Wuseless-cast` * GCC 6 and greater: * `-Wduplicated-cond` * `-Wmisleading-indentation` * `-Wnull-dereference` * GCC 7 and greater: * `-Wlogical-op` * `-Wduplicated-branches` * `-Wrestrict` * GCC 8 and greater: * `-Wclass-memaccess` * `-Wstringop-truncation` Continuous integration should compile your code with as many compilers as possible to get best feedback. ## Code formatting C++ code can be formatted to meet the formatting conventions with [ClangFormat](https://releases.llvm.org/9.0.0/tools/clang/docs/ClangFormat.html) utility. The ClangFormat configuration file to comply to these conventions can be found [here](./.clang-format). ClangFormat 9 is recommended but this coding conventions also support versions 7 and 8. It is recommended to use the CMake project provided by this repository to automate the source code formatting. ## Static Analysis You may use C++ linter tools to identify well known design mistakes like ClangTidy. A generic configuration file can be found [here](./.clang-tidy) Only ClangTidy 7 is supported, the LLVM stable version by the time of the writing of this document. It is recommended to use the CMake project provided by this repository to automate the static analysis of the source code. ## GitHook [pre-commit](https://pre-commit.com/) allows you to identify common mistakes before committing changes. ## External C++ Libraries ### As git modules or directly bundled The libraries below can be used directly in your C++ project, either as git modules or directly bundled as most of them provides single-header releases. * [CLI11](https://github.com/CLIUtils/CLI11): a header-only command line parser for C++11 * [cereal](https://github.com/USCiLab/cereal) A C++ header-only library for serialization * [{fmt}](https://github.com/fmtlib/fmt) a Python like formatting library. * [google-benchmark](https://github.com/google/benchmark) A microbenchmark support library * [spdlog](https://github.com/gabime/spdlog) Fast C++ logging library * [pybind11](https://github.com/pybind/pybind11) Lightweight header-only library to create Python bindings of existing C++ code. * [nlohmann/json](https://github.com/nlohmann/json) JSON for Modern C++ * [gsl-lite](https://github.com/martinmoene/gsl-lite) A single-file header-only version of ISO C++ Guideline Support Library (GSL) ### As external dependencies * [pugixml](https://pugixml.org): Light-weight, simple and fast XML parser for C++ with XPath support. A good replacement for libxml2. bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/__init__.py000066400000000000000000000000001434206647400243130ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-format-10000066400000000000000000000052471434206647400247370ustar00rootroot00000000000000--- AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignConsecutiveMacros: true AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BreakBeforeBraces: Custom BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DerivePointerBinding: true ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 40 PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 60 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PointerBindsToType: true ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false # '< ' style SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false # '(' style SpacesInSquareBrackets: false Standard: c++17 TabWidth: 4 UseTab: Never ... bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-format-12000066400000000000000000000052471434206647400247410ustar00rootroot00000000000000--- AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignConsecutiveMacros: true AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BreakBeforeBraces: Custom BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DerivePointerBinding: true ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 40 PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 60 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PointerBindsToType: true ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false # '< ' style SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false # '(' style SpacesInSquareBrackets: false Standard: c++14 TabWidth: 4 UseTab: Never ... bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-format-13000066400000000000000000000056611434206647400247420ustar00rootroot00000000000000--- AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignConsecutiveMacros: true AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BreakBeforeBraces: Custom BreakBeforeBinaryOperators: false BreakBeforeConceptDeclarations: true BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DerivePointerBinding: true EmptyLineBeforeAccessModifier: Leave EmptyLineAfterAccessModifier: Leave ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentAccessModifiers: false IndentCaseLabels: false IndentPPDirectives: None IndentRequires: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false LambdaBodyIndentation: Signature Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 40 PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 60 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PointerBindsToType: true PPIndentWidth: -1 ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false # '< ' style SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false # '(' style SpacesInSquareBrackets: false Standard: c++14 TabWidth: 4 UseTab: Never ... bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-format-14000066400000000000000000000060461434206647400247410ustar00rootroot00000000000000--- AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignConsecutiveMacros: true AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BreakBeforeBraces: Custom BreakBeforeBinaryOperators: false BreakBeforeConceptDeclarations: true BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DerivePointerBinding: true EmptyLineBeforeAccessModifier: Leave EmptyLineAfterAccessModifier: Leave ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentAccessModifiers: false IndentCaseLabels: false IndentPPDirectives: None IndentRequires: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false LambdaBodyIndentation: Signature Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 40 PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 60 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PointerBindsToType: true PPIndentWidth: -1 PackConstructorInitializers: Never QualifierAlignment: Left ReflowComments: true RemoveBracesLLVM: false SeparateDefinitionBlocks: Always SortIncludes: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false # '< ' style SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false # '(' style SpacesInSquareBrackets: false Standard: c++14 TabWidth: 4 UseTab: Never ... bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-format-7000066400000000000000000000051611434206647400246600ustar00rootroot00000000000000--- AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BreakBeforeBraces: Custom BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DerivePointerBinding: true ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 40 PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 60 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PointerBindsToType: true ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false # '< ' style SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false # '(' style SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never ... bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-format-9000066400000000000000000000052161434206647400246630ustar00rootroot00000000000000--- AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignConsecutiveMacros: true AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BasedOnStyle: WebKit BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BreakBeforeBraces: Custom BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DerivePointerBinding: true ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 40 PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 60 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PointerBindsToType: true ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false # '< ' style SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false # '(' style SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never ... bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/clang-tidy-7000066400000000000000000000024721434206647400243430ustar00rootroot00000000000000Checks: > boost-*, bugprone-*, cert-*, clang-analyzer-core*, cppcoreguidelines-*, google-*, hicpp-*, llvm-*, misc-*, modernize-*, mpi-*, performance-*, portability-*, readability-*, -clang-diagnostic-ignored-optimization-argument, -google-runtime-references, -modernize-make-unique, WarningsAsErrors: '*' AnalyzeTemporaryDtors: false FormatStyle: none CheckOptions: - key: google-readability-braces-around-statements.ShortStatementLines value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - key: google-readability-namespace-comments.ShortNamespaceLines value: '10' - key: google-readability-namespace-comments.SpacesBeforeComments value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence value: reasonable - key: modernize-loop-convert.NamingStyle value: CamelCase - key: modernize-pass-by-value.IncludeStyle value: llvm - key: modernize-replace-auto-ptr.IncludeStyle value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake-format.yaml000066400000000000000000000013131434206647400254440ustar00rootroot00000000000000line_width: 100 tab_size: 2 max_subgroups_hwrap: 2 max_pargs_hwrap: 6 separate_ctrl_name_with_space: false separate_fn_name_with_space: false dangle_parens: false dangle_align: prefix min_prefix_chars: 4 max_prefix_chars: 10 max_lines_hwrap: 2 line_ending: unix command_case: canonical keyword_case: unchanged always_wrap: [] enable_sort: true autosort: false hashruler_min_length: 10 per_command: {} layout_passes: {} bullet_char: '*' enum_char: . enable_markup: true first_comment_is_literal: false literal_comment_pattern: null fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$ ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$ canonicalize_hashrulers: true emit_byteorder_mark: false input_encoding: utf-8 output_encoding: utf-8 bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/000077500000000000000000000000001434206647400232745ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/3rdparty.cmake000066400000000000000000000120171434206647400260470ustar00rootroot00000000000000find_package(Git QUIET) if(NOT ${CODING_CONV_PREFIX}_3RDPARTY_DIR) set(${CODING_CONV_PREFIX}_3RDPARTY_DIR 3rdparty) set(${CODING_CONV_PREFIX}_3RDPARTY_DIR 3rdparty PARENT_SCOPE) endif() # initialize submodule with given path # # cpp_cc_init_git_submodule(path GIT_ARGS []) # # Default options passed to the `git submodule update` command are `--init --recursive --depth 1` to # perform a shallow clone of the submodule. If the GIT_ARGS argument is provided, then its value # supersedes the default options. # function(cpp_cc_init_git_submodule path) cmake_parse_arguments(PARSE_ARGV 1 opt "" "" "GIT_ARGS") if(NOT opt_GIT_ARGS) set(opt_GIT_ARGS --init --recursive) # RHEL7-family distributions ship with an old git that does not support the --depth argument to # git submodule update if(GIT_VERSION_STRING VERSION_GREATER_EQUAL "1.8.4") list(APPEND opt_GIT_ARGS --depth 1) endif() endif() if(NOT ${GIT_FOUND}) message( FATAL_ERROR "git not found and ${path} submodule not cloned (use git clone --recursive)") endif() message( STATUS "Fetching git submodule ${path}: running git submodule update ${opt_GIT_ARGS} -- ${path}" ) execute_process( COMMAND ${GIT_EXECUTABLE} submodule update ${opt_GIT_ARGS} -- ${path} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE git_submodule_status) if(NOT git_submodule_status EQUAL 0) message(FATAL_ERROR "Could not clone git submodule ${path}") endif() endfunction() # use a git submodule # # cpp_cc_git_submodule(source_dir [DISABLED] [QUIET] SUBDIR path [BUILD] [] [PACKAGE] # [] GIT_ARGS []) # # Add a CMake option in the cache to control whether the submodule is used or not (default ON). The # option is named after the source directory passed in first argument, for instance: # cpp_cc_git_submodule(src/eigen) adds the following CMake cached option: # ${PROJECT_NAME}_3RDPARTY_USE_SRC_EIGEN:BOOL=ON # # If enabled, then the submodule is fetched if missing in the working copy. # # If the DISABLED argument is provided, then the default value for the CMake option is OFF. # # If the QUIET argument is provided then no status message will be printed. # # If the BUILD argument is provided then the directory is added to the build through the # add_subdirectory CMake function. Arguments following the BUILD arguments are passed to the # add_subdirectory function call. # # The optional SUBDIR argument is used by the BUILD argument to determine the path to the directory # added to the build. The path specified is relative to the path to the git submodule. # # If the PACKAGE argument is provided and the CMake option to determine whether the git submodule # should be used or not is FALSE, then a call to the find_package function is made with the # arguments specified to the PACKAGE option. # # Default options passed to the `git submodule update` command are `--init --recursive --depth 1` to # perform a shallow clone of the submodule. If the GIT_ARGS argument is provided, then its value # supersedes the default options. # function(cpp_cc_git_submodule name) cmake_parse_arguments(PARSE_ARGV 1 opt "DISABLED;QUIET" "SUBDIR" "PACKAGE;BUILD;GIT_ARGS") string(MAKE_C_IDENTIFIER "USE_${name}" option_suffix) string(TOUPPER "3RDPARTY_${option_suffix}" option_suffix) if(opt_DISABLED) set(default OFF) else() set(default ON) endif() option(${CODING_CONV_PREFIX}_${option_suffix} "Use the git submodule ${name}" ${default}) set(old_cmake_module_path "${CMAKE_MODULE_PATH}") # See if we have been told to skip initialising the submodule (and possibly to use # find_package(...) if(NOT ${CODING_CONV_PREFIX}_${option_suffix}) if(opt_PACKAGE) find_package(${opt_PACKAGE}) elseif(PACKAGE IN_LIST opt_KEYWORDS_MISSING_VALUES) message(SEND_ERROR "PACKAGE argument requires at least one argument") endif() else() set(submodule_path "${${CODING_CONV_PREFIX}_3RDPARTY_DIR}/${name}") if(opt_SUBDIR) set(submodule_path "${submodule_path}/${opt_SUBDIR}") endif() file(GLOB submodule_contents "${PROJECT_SOURCE_DIR}/${submodule_path}/*") if("${submodule_contents}" STREQUAL "") # The directory was empty if(opt_GIT_ARGS) cpp_cc_init_git_submodule("${submodule_path}" GIT_ARGS ${opt_GIT_ARGS}) else() cpp_cc_init_git_submodule("${submodule_path}") endif() endif() if(NOT opt_QUIET) message(STATUS "3rd party project: using ${name} from \"${submodule_path}\"") endif() if(opt_BUILD) add_subdirectory(${submodule_path} ${opt_BUILD}) elseif("BUILD" IN_LIST opt_KEYWORDS_MISSING_VALUES) add_subdirectory(${submodule_path}) endif() endif() if(NOT "${old_cmake_module_path}" STREQUAL "${CMAKE_MODULE_PATH}") list(APPEND additions ${CMAKE_MODULE_PATH}) list(REMOVE_ITEM additions ${old_cmake_module_path}) message(STATUS "cpp_cc_git_submodule adding ${additions} to CMAKE_MODULE_PATH") set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) endif() endfunction() bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/FindPreCommit.cmake000066400000000000000000000027741434206647400270100ustar00rootroot00000000000000# .rst: FindPreCommit.cmake # ------------------------- # # The module defines the following variables: # # * ``PreCommit_EXECUTABLE`` Path to pre-commit executable. # * ``PreCommit_FOUND`` True if pre-commit executable was found. # * ``PreCommit_VERSION`` The version of pre-commit found. # * ``PreCommit_VERSION_MAJOR`` The pre-commit major version # * ``PreCommit_VERSION_MINOR`` The pre-commit minor version # * ``PreCommit_VERSION_PATCH`` The pre-commit patch version # find_program(PreCommit_EXECUTABLE pre-commit DOC "Path to pre-commit Python utility") mark_as_advanced(PreCommit_EXECUTABLE) if(PreCommit_EXECUTABLE) execute_process( COMMAND ${PreCommit_EXECUTABLE} --version OUTPUT_VARIABLE full_version_text ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # full_version_text sample: "pre-commit 1.14.2" if(full_version_text MATCHES "^pre-commit .*") string(REGEX REPLACE "pre-commit ([0-9.]+)" "\\1" PreCommit_VERSION "${full_version_text}") # PreCommit_VERSION sample: "1.14.2" # Extract version components string(REPLACE "." ";" list_versions "${PreCommit_VERSION}") list(GET list_versions 0 PreCommit_VERSION_MAJOR) list(GET list_versions 1 PreCommit_VERSION_MINOR) list(GET list_versions 2 PreCommit_VERSION_PATCH) unset(list_versions) endif() unset(full_version_text) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args( PreCommit FOUND_VAR PreCommit_FOUND REQUIRED_VARS PreCommit_EXECUTABLE VERSION_VAR PreCommit_VERSION) bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/bbp-find-python-module.cmake000066400000000000000000000053461434206647400305710ustar00rootroot00000000000000# * Macro to find a python module # # Usage: cpp_cc_find_python_module (module [VERSION] [REQUIRED]) # # Copyright 2005-2018 Airbus-EDF-IMACS-Phimeca # # Distributed under the OSI-approved BSD License (the "License"); see accompanying file # Copyright.txt for details in: # # https://github.com/openturns/otsubsetinverse/blob/master/cmake/FindPythonModule.cmake macro(cpp_cc_find_python_module module) string(TOUPPER ${module} module_upper) if(NOT ${module_upper}_FOUND) # parse arguments set(${module}_FIND_OPTIONAL TRUE) if(${ARGC} EQUAL 2) if(${ARGV1} MATCHES REQUIRED) set(${module}_FIND_OPTIONAL FALSE) else() set(${module}_FIND_VERSION ${ARGV1}) endif() elseif(${ARGC} EQUAL 3) if(${ARGV2} MATCHES REQUIRED) set(${module}_FIND_OPTIONAL FALSE) endif() set(${module}_FIND_VERSION ${ARGV1}) endif() # A module's location is usually a directory, but for binary modules it's a .so file. execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-c" "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" RESULT_VARIABLE _${module}_status OUTPUT_VARIABLE _${module}_location ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _${module}_status) set(${module_upper}_LOCATION ${_${module}_location} CACHE STRING "Location of Python module ${module}") # retrieve version execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-c" "import ${module}; print(${module}.__version__)" RESULT_VARIABLE _${module}_status OUTPUT_VARIABLE _${module}_version ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) set(_${module_upper}_VERSION_MATCH TRUE) if(NOT _${module}_status) set(${module_upper}_VERSION_STRING ${_${module}_version}) if(${module}_FIND_VERSION) if(${module}_FIND_VERSION VERSION_GREATER ${module_upper}_VERSION_STRING) set(_${module_upper}_VERSION_MATCH FALSE) endif() endif() mark_as_advanced(${module_upper}_VERSION_STRING) endif() endif() if(NOT ${module_upper}_LOCATION) if(NOT ${module}_FIND_OPTIONAL) message(FATAL_ERROR "Missing python module \"${module}\"") endif() elseif(NOT _${module_upper}_VERSION_MATCH) if(NOT ${module}_FIND_OPTIONAL) message( FATAL_ERROR "Found module \"${module}\", but version mismatch. Asked for \"${${module}_FIND_VERSION}\" but found \"${${module_upper}_VERSION_STRING}\"" ) endif() else() set(${module_upper}_FOUND TRUE) endif() mark_as_advanced(${module_upper}_LOCATION) endif(NOT ${module_upper}_FOUND) endmacro(cpp_cc_find_python_module) bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/bbp-setup-pre-commit-config.py000077500000000000000000000143461434206647400310770ustar00rootroot00000000000000import argparse from collections import namedtuple import copy import logging import os import sys import yaml try: from yaml import CLoader as Loader except ImportError: from yaml import Loader HPC_PRE_COMMITS_REPO_URL = "https://github.com/BlueBrain/hpc-pre-commits" DEFAULT_HPC_PRE_COMMIT_REPO = dict( repo=HPC_PRE_COMMITS_REPO_URL, rev="master", hooks=[] ) class PreCommitConfig: def __init__(self, file): self._file = file if os.path.exists(file): with open(file) as istr: self._config = yaml.load(istr, Loader=Loader) or {} else: self._config = {} self._bbp_repo = self._initialize_bbp_repo() self._previous_config = copy.deepcopy(self._config) def _initialize_bbp_repo(self): repos = self.config.setdefault("repos", []) for repo in repos: if repo["repo"] == HPC_PRE_COMMITS_REPO_URL: for k, v in DEFAULT_HPC_PRE_COMMIT_REPO.items(): repo.setdefault(k, v) return repo bbp_repo = DEFAULT_HPC_PRE_COMMIT_REPO repos.append(bbp_repo) return bbp_repo @property def config(self): return self._config def _enable_hook(self, new_hook): logging.info(f"Enable hook {new_hook['name']}") for hook in self._bbp_repo["hooks"]: if (hook["id"], hook.get("name")) == (new_hook["id"], new_hook.get("name")): hook.update(**new_hook) break else: self._bbp_repo["hooks"].append(new_hook) def enable_cmake_hook(self, name, stages, args, extra=None): config = dict( id="hpc-pc-cmake-build", args=args, name=name, stages=stages, ) if extra: config.update(extra) self._enable_hook(config) def enable_cmake_target_hook(self, stages, build_dir, target, **kwargs): self.enable_cmake_hook( target, stages, ["--build", build_dir, "--target", target], **kwargs ) def enable_cmake_script_hook(self, name, stages, file, **kwargs): self.enable_cmake_hook(name, stages, ["-P", file], **kwargs) def disable_cmake_hook(self, name): for i, hook in enumerate(self._bbp_repo["hooks"]): if (name, "hpc-pc-cmake-build") == (hook.get("name"), hook["id"]): self._bbp_repo["hooks"].pop(i) break def save(self): if self._previous_config != self.config: logging.info("Updating pre-commit config file: %s", self._file) with open(self._file, "w") as ostr: yaml.dump(self.config, ostr, default_flow_style=False) else: logging.info("pre-commit config is up to date: %s", self._file) class CMakeTargetHook(namedtuple("CMakeTargetHook", ["build_dir", "target"])): def enable(self, config, stages): config.enable_cmake_target_hook( stages, self.build_dir, self.target, extra=dict(always_run=True) ) def disable(self, config): config.disable_cmake_hook(self.target) @property def name(self): return self.target class CMakeScriptHook(namedtuple("CMakeScriptHook", ["name", "file"])): def enable(self, config, stages): config.enable_cmake_script_hook( self.name, stages, self.file, extra=dict(always_run=True, verbose=True) ) def disable(self, config): config.disable_cmake_hook(self.name) def _parse_cli(args=None): parser = argparse.ArgumentParser(description="Ensure CMake files formatting") parser.add_argument( "--commit-checks", help="Comma-separated list of checks to perform when committing changes", default="", ) parser.add_argument( "--push-checks", help="Comma-separated list of checks to perform when pushing changes", default="", ) parser.add_argument("source_dir", help="CMake source directory") parser.add_argument("build_dir", help="CMake binary directory") return parser.parse_args(args=args) def fix_hook_file(hook): if not os.path.exists(hook): return with open(hook) as istr: lines = istr.readlines() shebang = f"#!/usr/bin/env {sys.executable}\n" if lines[0] == "#!/usr/bin/env python\n": logging.warning(f"Patching git hook script: {hook}") lines[0] = shebang with open(hook, "w") as ostr: ostr.writelines(lines) def main(**kwargs): args = _parse_cli(**kwargs) PRE_COMMIT_CONFIG = os.path.join(args.source_dir, ".pre-commit-config.yaml") ALL_HOOKS = [ CMakeTargetHook(args.build_dir, "check-clang-format"), CMakeTargetHook(args.build_dir, "check-cmake-format"), CMakeTargetHook(args.build_dir, "clang-tidy"), CMakeScriptHook( "courtesy-msg", os.path.join(args.build_dir, "git-push-message.cmake") ), ] config = PreCommitConfig(PRE_COMMIT_CONFIG) hooks = dict((hook.name, hook) for hook in ALL_HOOKS) assert len(hooks) == len(ALL_HOOKS), "hooks must have unique names" # Build a dictionary from the two CLI arguments # For example: {"clang-tidy": ["commit"], "courtesy-msg": ["commit", "push"]} hook_stages = {} for names, stage in [(args.commit_checks, "commit"), (args.push_checks, "push")]: for name in names.split(","): name = name.strip() if name: hook_stages.setdefault(name, []).append(stage) # Enable hooks mentioned in `hook_stages` for name, stages in hook_stages.items(): hook = hooks.pop(name, None) if hook is None: logging.warning( f"Unknown check named: '{name}', " f"available checks: {', '.join(hooks.keys())}" ) continue hook.enable(config, stages) # Disable the other ones for hook in hooks.values(): hook.disable(config) config.save() fix_hook_file(os.path.join(args.source_dir, ".git", "hooks", "pre-commit")) fix_hook_file(os.path.join(args.source_dir, ".git", "hooks", "pre-push")) if __name__ == "__main__": level = logging.INFO if "VERBOSE" in os.environ else logging.WARN logging.basicConfig(level=level, format="%(message)s") main() bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/bob.cmake000066400000000000000000000624051434206647400250470ustar00rootroot00000000000000function(bob_always_full_rpath) # CMake RPATH "always full" configuration, see: # https://cmake.org/Wiki/CMake_RPATH_handling#Always_full_RPATH use, i.e. don't skip the full # RPATH for the build tree set(CMAKE_SKIP_BUILD_RPATH False PARENT_SCOPE) # when building, don't use the install RPATH already (but later on when installing) set(CMAKE_BUILD_WITH_INSTALL_RPATH False PARENT_SCOPE) # the RPATH to be used when installing, but only if it's not a system directory list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) if("${isSystemDir}" STREQUAL "-1") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib" PARENT_SCOPE) endif() # add the automatically determined parts of the RPATH which point to directories outside the build # tree to the install RPATH set(CMAKE_INSTALL_RPATH_USE_LINK_PATH True PARENT_SCOPE) endfunction(bob_always_full_rpath) function(bob_cmake_arg2 var type default) if(NOT ${var} STREQUAL "${default}") if(${CODING_CONV_PREFIX}_CMAKE_ARGS) set(sep " ") else() set(sep "") endif() set(${CODING_CONV_PREFIX}_CMAKE_ARGS "${${CODING_CONV_PREFIX}_CMAKE_ARGS}${sep}-D${var}:${type}=\"${${var}}\"" CACHE STRING "CMake arguments that would replicate this configuration" FORCE) endif() endfunction() function(bob_cmake_arg var type default) message(STATUS "${var}: ${${var}}") bob_cmake_arg2("${var}" "${type}" "${default}") endfunction() function(bob_option var desc default) option(${var} "${desc}" "${default}") bob_cmake_arg(${var} BOOL "${default}") endfunction() function(bob_input var default type desc) set(${var} "${default}" CACHE ${type} "${desc}") bob_cmake_arg(${var} ${type} "${default}") endfunction() macro(bob_begin_package) set(${CODING_CONV_PREFIX}_CMAKE_ARGS "" CACHE STRING "CMake arguments that would replicate this configuration" FORCE) message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}") if(${CODING_CONV_PREFIX}_VERSION) message(STATUS "${CODING_CONV_PREFIX}_VERSION: ${${CODING_CONV_PREFIX}_VERSION}") endif() option(USE_XSDK_DEFAULTS "enable the XDSK v0.3.0 default configuration" OFF) bob_cmake_arg(USE_XSDK_DEFAULTS BOOL OFF) if(NOT MEMORYCHECK_COMMAND) # try to force BUILD_TESTING to be OFF by default if memory check is not activated set(BUILD_TESTING OFF CACHE BOOL "Build and run tests") endif() include(CTest) enable_testing() option(BUILD_SHARED_LIBS "Build shared libraries" ON) # If not building shared libs, then prefer static dependency libs if(NOT BUILD_SHARED_LIBS) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".so" ".dylib") endif() if(USE_XSDK_DEFAULTS) string(STRIP "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") endif() bob_cmake_arg(CMAKE_BUILD_TYPE STRING "") endif() bob_always_full_rpath() bob_cmake_arg(BUILD_TESTING BOOL OFF) bob_cmake_arg(BUILD_SHARED_LIBS BOOL ON) bob_cmake_arg(CMAKE_INSTALL_PREFIX PATH "") option(${CODING_CONV_PREFIX}_NORMAL_CXX_FLAGS "Allow CMAKE_CXX_FLAGS to follow \"normal\" CMake behavior" ${USE_XSDK_DEFAULTS}) endmacro(bob_begin_package) function(bob_get_commit) execute_process( COMMAND git rev-parse HEAD WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE NO_SHA1 OUTPUT_VARIABLE SHA1 ERROR_VARIABLE SHA1_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE) if(NO_SHA1) message(WARNING "bob_get_commit: no Git hash!\n" ${SHA1_ERROR}) else() set(${CODING_CONV_PREFIX}_COMMIT "${SHA1}" PARENT_SCOPE) endif() endfunction(bob_get_commit) function(bob_get_semver) execute_process( COMMAND git describe --exact-match HEAD WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE NOT_TAG OUTPUT_VARIABLE TAG_NAME ERROR_VARIABLE TAG_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT_TAG) if(${CODING_CONV_PREFIX}_VERSION) set(SEMVER ${${CODING_CONV_PREFIX}_VERSION}) execute_process( COMMAND git log -1 --format=%h WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE NO_SHA1 OUTPUT_VARIABLE SHORT_SHA1 ERROR_VARIABLE SHA1_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE) if(NO_SHA1) message(WARNING "bob_get_semver no Git hash!\n" ${SHA1_ERROR}) else() set(SEMVER "${SEMVER}-sha.${SHORT_SHA1}") endif() else() message(FATAL_ERROR "bob_get_semver needs either ${CODING_CONV_PREFIX}_VERSION or a Git tag\n" ${TAG_ERROR}) endif() else() if(TAG_NAME MATCHES "^v([0-9]+[.])?([0-9]+[.])?([0-9]+)$") string(SUBSTRING "${TAG_NAME}" 1 -1 SEMVER) if(${CODING_CONV_PREFIX}_VERSION AND (NOT (SEMVER VERSION_EQUAL ${CODING_CONV_PREFIX}_VERSION) )) message( FATAL_ERROR "bob_get_semver: tag is ${TAG_NAME} but ${CODING_CONV_PREFIX}_VERSION=${${CODING_CONV_PREFIX}_VERSION} !" ) endif() else() if(${CODING_CONV_PREFIX}_VERSION) set(SEMVER "${${CODING_CONV_PREFIX}_VERSION}-tag.${TAG_NAME}") else() message( FATAL_ERROR "bob_get_semver needs either ${CODING_CONV_PREFIX}_VERSION or a Git tag of the form v1.2.3" ) endif() endif() endif() if(${CODING_CONV_PREFIX}_KEY_BOOLS) set(SEMVER "${SEMVER}+") foreach(KEY_BOOL IN LISTS ${CODING_CONV_PREFIX}_KEY_BOOLS) if(${KEY_BOOL}) set(SEMVER "${SEMVER}1") else() set(SEMVER "${SEMVER}0") endif() endforeach() endif() set(${CODING_CONV_PREFIX}_SEMVER "${SEMVER}" PARENT_SCOPE) message(STATUS "${CODING_CONV_PREFIX}_SEMVER = ${SEMVER}") endfunction(bob_get_semver) function(bob_cxx_pedantic_flags) # Append to variable CMAKE_CXX_FLAGS the set of warnings recommended for development, based on # compiler family and version. # # Flags are appended to a custom variable instead of CMAKE_CXX_FLAGS if passed in parameter, for # instance: bob_cxx_pedantic_flags(PEDANTIC_FLAGS) # set(flags "") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(flags "${flags} -Werror -Weverything") set(flags "${flags} -Wno-disabled-macro-expansion") set(flags "${flags} -Wno-documentation-unknown-command") set(flags "${flags} -Wno-padded") set(flags "${flags} -Wno-unused-member-function") if(APPLE) set(flags "${flags} -Wno-undef") if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "8.0.0") set(flags "${flags} -fcomment-block-commands=file") endif() else() if(CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "5.0.0") set(flags "${flags} -fcomment-block-commands=file") endif() if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.0.0") set(flags "${flags} -fcomment-block-commands=file") endif() endif() elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") set(flags "${flags} -Wall") set(flags "${flags} -Wcast-align") set(flags "${flags} -Wconversion") set(flags "${flags} -Wdouble-promotion") set(flags "${flags} -Werror") set(flags "${flags} -Wextra") set(flags "${flags} -Wformat=2") set(flags "${flags} -Wnon-virtual-dtor") set(flags "${flags} -Wold-style-cast") set(flags "${flags} -Woverloaded-virtual") set(flags "${flags} -Wshadow") set(flags "${flags} -Wsign-conversion") set(flags "${flags} -Wunused") set(flags "${flags} -Wuseless-cast") if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "6.0") set(flags "${flags} -Wduplicated-cond") set(flags "${flags} -Wmisleading-indentation") set(flags "${flags} -Wnull-dereference") endif() if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0") set(flags "${flags} -Wduplicated-branches") set(flags "${flags} -Wlogical-op") set(flags "${flags} -Wrestrict") endif() if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "8.0") # set(flags "${flags} -Wclass-memaccess") set(flags "${flags} -Wstringop-truncation") endif() elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Intel") else() message(WARNING "Unexpected compiler type ${CMAKE_CXX_COMPILER_ID}") endif() if(ARGC EQUAL 0) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flags}" PARENT_SCOPE) else() set(${ARGV0} "${${ARGV0}} ${flags}" PARENT_SCOPE) endif() endfunction(bob_cxx_pedantic_flags) function(bob_begin_cxx_flags) if(${CODING_CONV_PREFIX}_NORMAL_CXX_FLAGS) set(BOB_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" PARENT_SCOPE) bob_cmake_arg2(CMAKE_CXX_FLAGS STRING "") else() string(STRIP "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE) message(FATAL_ERROR "can't set CMAKE_BUILD_TYPE and use bob_*_cxx_flags") endif() option(${CODING_CONV_PREFIX}_CXX_OPTIMIZE "Compile C++ with optimization" ON) option(${CODING_CONV_PREFIX}_CXX_SYMBOLS "Compile C++ with debug symbols" ON) option(${CODING_CONV_PREFIX}_CXX_WARNINGS "Compile C++ with warnings" ON) bob_cmake_arg(${CODING_CONV_PREFIX}_CXX_OPTIMIZE BOOL ON) bob_cmake_arg(${CODING_CONV_PREFIX}_CXX_SYMBOLS BOOL ON) set(${CODING_CONV_PREFIX}_ARCH "" CACHE STRING "Argument to -march or -arch") bob_cmake_arg(${CODING_CONV_PREFIX}_ARCH STRING "native") # CDash's simple output parser interprets the variable name WARNINGS as a warning... message(STATUS "${CODING_CONV_PREFIX}_CXX_W**NINGS: ${${CODING_CONV_PREFIX}_CXX_WARNINGS}") bob_cmake_arg2(${CODING_CONV_PREFIX}_CXX_WARNINGS BOOL ON) set(FLAGS "") if(${CODING_CONV_PREFIX}_CXX_OPTIMIZE) set(FLAGS "${FLAGS} -O3 -DNDEBUG") if(${CODING_CONV_PREFIX}_ARCH) if(${CODING_CONV_PREFIX}_USE_CUDA) set(FLAGS "${FLAGS} -arch=${${CODING_CONV_PREFIX}_ARCH}") else() set(FLAGS "${FLAGS} -march=${${CODING_CONV_PREFIX}_ARCH}") endif() endif() else() set(FLAGS "${FLAGS} -O0") endif() if(${CODING_CONV_PREFIX}_CXX_SYMBOLS) set(FLAGS "${FLAGS} -g") endif() # ----------- if(${CODING_CONV_PREFIX}_CXX_WARNINGS) bob_cxx_pedantic_flags(FLAGS) endif() set(BOB_CMAKE_CXX_FLAGS "${FLAGS}" PARENT_SCOPE) endif() endfunction(bob_begin_cxx_flags) macro(bob_cxx_standard_flags standard) if(NOT standard VERSION_EQUAL 98 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(${CODING_CONV_PREFIX}_CXX_WARNINGS) set(BOB_CMAKE_CXX_FLAGS "${BOB_CMAKE_CXX_FLAGS} -Wno-c++98-compat-pedantic -Wno-c++98-compat" PARENT_SCOPE) endif() endif() set(CMAKE_CXX_STANDARD "${standard}" PARENT_SCOPE) set(CXX_STANDARD_REQUIRED "TRUE" PARENT_SCOPE) set(CMAKE_CXX_EXTENSIONS "NO" PARENT_SCOPE) endmacro(bob_cxx_standard_flags standard) function(bob_cxx11_flags) bob_cxx_standard_flags(11) endfunction(bob_cxx11_flags) function(bob_cxx14_flags) bob_cxx_standard_flags(14) endfunction(bob_cxx14_flags) function(bob_cxx17_flags) bob_cxx_standard_flags(17) endfunction(bob_cxx17_flags) function(bob_cxx20_flags) bob_cxx_standard_flags(20) endfunction(bob_cxx20_flags) function(bob_end_cxx_flags) if(${CODING_CONV_PREFIX}_NORMAL_CXX_FLAGS) message(STATUS "CMAKE_CXX_FLAGS: ${BOB_CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "${BOB_CMAKE_CXX_FLAGS}" PARENT_SCOPE) else() set(${CODING_CONV_PREFIX}_CXX_FLAGS "" CACHE STRING "Override all C++ compiler flags") bob_cmake_arg(${CODING_CONV_PREFIX}_CXX_FLAGS STRING "") set(${CODING_CONV_PREFIX}_EXTRA_CXX_FLAGS "" CACHE STRING "Extra C++ compiler flags") bob_cmake_arg(${CODING_CONV_PREFIX}_EXTRA_CXX_FLAGS STRING "") if(${CODING_CONV_PREFIX}_CXX_FLAGS) set(FLAGS "${${CODING_CONV_PREFIX}_CXX_FLAGS}") else() set(FLAGS "${CMAKE_CXX_FLAGS} ${BOB_CMAKE_CXX_FLAGS} ${${CODING_CONV_PREFIX}_EXTRA_CXX_FLAGS}") endif() message(STATUS "CMAKE_CXX_FLAGS: ${FLAGS}") set(CMAKE_CXX_FLAGS "${FLAGS}" PARENT_SCOPE) endif() endfunction(bob_end_cxx_flags) macro(bob_add_dependency) set(options PUBLIC PRIVATE) set(oneValueArgs NAME) set(multiValueArgs COMPONENTS TARGETS INCLUDE_DIR_VARS LIBRARY_VARS) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT ARG_NAME) message(FATAL_ERROR "bob_add_dependency: no NAME argument given") endif() if(ARG_PUBLIC AND ARG_PRIVATE) message(FATAL_ERROR "bob_add_dependency: can't specify both PUBLIC and PRIVATE") endif() if(ARG_COMPONENTS) set(ARG_COMPONENTS COMPONENTS ${ARG_COMPONENTS}) endif() if(USE_XSDK_DEFAULTS) option(TPL_ENABLE_${ARG_NAME} "Whether to use ${ARG_NAME}" "${${CODING_CONV_PREFIX}_USE_${ARG_NAME}_DEFAULT}") bob_cmake_arg(TPL_ENABLE_${ARG_NAME} BOOL "${${CODING_CONV_PREFIX}_USE_${ARG_NAME}_DEFAULT}") set(${CODING_CONV_PREFIX}_USE_${ARG_NAME} "${TPL_ENABLE_${ARG_NAME}}") if(TPL_ENABLE_${ARG_NAME}) set(TPL_${ARG_NAME}_LIBRARIES "" CACHE STRING "${ARG_NAME} libraries") bob_cmake_arg(TPL_${ARG_NAME}_LIBRARIES STRING "") set(TPL_${ARG_NAME}_INCLUDE_DIRS "" CACHE STRING "${ARG_NAME} include directories") bob_cmake_arg(TPL_${ARG_NAME}_INCLUDE_DIRS STRING "") set(tgt "${CODING_CONV_PREFIX}-${ARG_NAME}") add_library(${tgt} INTERFACE) target_include_directories(${tgt} INTERFACE "${TPL_${ARG_NAME}_INCLUDE_DIRS}") target_link_libraries(${tgt} INTERFACE "${TPL_${ARG_NAME}_LIBRARIES}") endif() else() option(${CODING_CONV_PREFIX}_USE_${ARG_NAME} "Whether to use ${ARG_NAME}" ${${CODING_CONV_PREFIX}_USE_${ARG_NAME}_DEFAULT}) bob_cmake_arg(${CODING_CONV_PREFIX}_USE_${ARG_NAME} BOOL "${${CODING_CONV_PREFIX}_USE_${ARG_NAME}_DEFAULT}") if(${CODING_CONV_PREFIX}_USE_${ARG_NAME}) set(${ARG_NAME}_PREFIX "${${ARG_NAME}_PREFIX_DEFAULT}" CACHE PATH "${ARG_NAME} install directory") bob_cmake_arg(${ARG_NAME}_PREFIX PATH "${${ARG_NAME}_PREFIX_DEFAULT}") if(${ARG_NAME}_PREFIX) # if ${ARG_NAME}_PREFIX is set, don't find it anywhere else: set(ARG_PREFIX PATHS "${${ARG_NAME}_PREFIX}" NO_DEFAULT_PATH) else() # allow CMake to search other prefixes if ${ARG_NAME}_PREFIX is not set set(ARG_PREFIX) endif() set(${ARG_NAME}_find_package_args "${${ARG_NAME}_REQUIRED_VERSION}" ${ARG_COMPONENTS} ${ARG_PREFIX}) find_package(${ARG_NAME} ${${ARG_NAME}_find_package_args} REQUIRED) if(${ARG_NAME}_CONFIG) message(STATUS "${ARG_NAME}_CONFIG: ${${ARG_NAME}_CONFIG}") endif() if(${ARG_NAME}_VERSION) message(STATUS "${ARG_NAME}_VERSION: ${${ARG_NAME}_VERSION}") endif() set(tgt "${CODING_CONV_PREFIX}-${ARG_NAME}") add_library(${tgt} INTERFACE) if(ARG_TARGETS) target_link_libraries(${tgt} INTERFACE ${ARG_TARGETS}) endif() if(ARG_LIBRARY_VARS) foreach(library_var IN LISTS ARG_LIBRARY_VARS) target_link_libraries(${tgt} INTERFACE ${${library_var}}) endforeach() endif() if(ARG_INCLUDE_DIR_VARS) foreach(include_dir_var IN LISTS ARG_INCLUDE_DIR_VARS) foreach(include_dir IN LISTS ${include_dir_var}) get_filename_component(abs_include_dir "${include_dir}" ABSOLUTE) target_include_directories(${tgt} INTERFACE "${abs_include_dir}") endforeach() endforeach() endif() install( TARGETS ${tgt} EXPORT ${tgt}-target RUNTIME DESTINATION bin ARCHIVE DESTINATION lib RUNTIME DESTINATION lib) install(EXPORT ${tgt}-target DESTINATION lib/cmake/${CODING_CONV_PREFIX}) set(${CODING_CONV_PREFIX}_EXPORTED_TARGETS ${${CODING_CONV_PREFIX}_EXPORTED_TARGETS} ${tgt}) if(ARG_PUBLIC) set(${CODING_CONV_PREFIX}_DEPS ${${CODING_CONV_PREFIX}_DEPS} ${ARG_NAME}) endif() endif() endif() endmacro(bob_add_dependency) function(bob_link_dependency tgt type dep) if(${CODING_CONV_PREFIX}_USE_${dep}) target_link_libraries(${tgt} ${type} ${CODING_CONV_PREFIX}-${dep}) endif() endfunction(bob_link_dependency) macro(bob_private_dep pkg_name) bob_add_dependency(PRIVATE NAME "${pkg_name}") endmacro(bob_private_dep) macro(bob_public_dep pkg_name) bob_add_dependency(PUBLIC NAME "${pkg_name}") endmacro(bob_public_dep) function(bob_target_includes lib_name) # find local headers even with #include <> target_include_directories(${lib_name} PUBLIC $) # find generated configuration headers target_include_directories(${lib_name} PUBLIC $) endfunction(bob_target_includes) function(bob_library_includes lib_name) bob_target_includes("${lib_name}") # ensure downstream users include installed headers target_include_directories(${lib_name} INTERFACE $) endfunction(bob_library_includes) function(bob_export_target tgt_name) get_target_property(tgt_type "${tgt_name}" TYPE) if(${tgt_type} STREQUAL "EXECUTABLE") install(TARGETS ${tgt_name} DESTINATION bin) else() if(USE_XSDK_DEFAULTS) install(TARGETS ${tgt_name} DESTINATION lib) else() install( TARGETS ${tgt_name} EXPORT ${tgt_name}-target DESTINATION lib) install( EXPORT ${tgt_name}-target NAMESPACE ${CODING_CONV_PREFIX}:: DESTINATION lib/cmake/${CODING_CONV_PREFIX}) set(${CODING_CONV_PREFIX}_EXPORTED_TARGETS ${${CODING_CONV_PREFIX}_EXPORTED_TARGETS} ${tgt_name} PARENT_SCOPE) endif() endif() endfunction(bob_export_target) macro(bob_end_subdir) set(${CODING_CONV_PREFIX}_EXPORTED_TARGETS ${${CODING_CONV_PREFIX}_EXPORTED_TARGETS} PARENT_SCOPE) set(${CODING_CONV_PREFIX}_DEPS ${${CODING_CONV_PREFIX}_DEPS} PARENT_SCOPE) set(${CODING_CONV_PREFIX}_DEP_PREFIXES ${${CODING_CONV_PREFIX}_DEP_PREFIXES} PARENT_SCOPE) endmacro(bob_end_subdir) function(bob_config_header HEADER_PATH) get_filename_component(HEADER_NAME "${HEADER_PATH}" NAME) string(REPLACE "." "_" INCLUDE_GUARD "${HEADER_NAME}") string(TOUPPER "${INCLUDE_GUARD}" INCLUDE_GUARD) set(HEADER_CONTENT "#ifndef ${INCLUDE_GUARD} #define ${INCLUDE_GUARD} ") if(${CODING_CONV_PREFIX}_KEY_BOOLS) foreach(KEY_BOOL IN LISTS ${CODING_CONV_PREFIX}_KEY_BOOLS) if(${KEY_BOOL}) string(TOUPPER "${KEY_BOOL}" MACRO_NAME) set(HEADER_CONTENT "${HEADER_CONTENT} #define ${MACRO_NAME}") endif() endforeach() endif() if(${CODING_CONV_PREFIX}_KEY_INTS) foreach(KEY_INT IN LISTS ${CODING_CONV_PREFIX}_KEY_INTS) string(TOUPPER "${KEY_INT}" MACRO_NAME) set(HEADER_CONTENT "${HEADER_CONTENT} #define ${MACRO_NAME} ${${KEY_INT}}") endforeach() endif() if(${CODING_CONV_PREFIX}_KEY_STRINGS) foreach(KEY_STRING IN LISTS ${CODING_CONV_PREFIX}_KEY_STRINGS) string(TOUPPER "${KEY_STRING}" MACRO_NAME) set(val "${${KEY_STRING}}") # escape escapes string(REPLACE "\\" "\\\\" val "${val}") # escape quotes string(REPLACE "\"" "\\\"" val "${val}") set(HEADER_CONTENT "${HEADER_CONTENT} #define ${MACRO_NAME} \"${val}\"") endforeach() endif() set(HEADER_CONTENT "${HEADER_CONTENT} #endif ") file(WRITE "${HEADER_PATH}" "${HEADER_CONTENT}") endfunction() function(bob_get_link_libs tgt var) get_target_property(tgt_type "${tgt}" TYPE) set(sublibs) if(NOT tgt_type STREQUAL "INTERFACE_LIBRARY") get_target_property(tgt_libs "${tgt}" LINK_LIBRARIES) if(tgt_libs) set(sublibs ${sublibs} ${tgt_libs}) endif() endif() get_target_property(tgt_iface_libs "${tgt}" INTERFACE_LINK_LIBRARIES) if(tgt_iface_libs) set(sublibs ${sublibs} ${tgt_iface_libs}) endif() set(link_libs) foreach(lib IN LISTS sublibs) if(TARGET ${lib}) get_target_property(subtgt_type "${lib}" TYPE) if(subtgt_type MATCHES "STATIC_LIBRARY|SHARED_LIBRARY") get_target_property(sublibtgt_loc "${lib}" LOCATION) if(sublibtgt_loc) set(link_libs ${link_libs} ${sublibtgt_loc}) endif() endif() if(subtgt_type MATCHES "UNKNOWN_LIBRARY") foreach(prop in ITEMS IMPORTED_LOCATION IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_DEBUG) get_target_property(sublibtgt_import_loc "${lib}" ${prop}) if(sublibtgt_import_loc) set(link_libs ${link_libs} ${sublibtgt_import_loc}) endif() endforeach() endif() bob_get_link_libs(${lib} subtgt_link_libs) set(link_libs ${link_libs} ${subtgt_link_libs}) else() set(link_libs ${link_libs} ${lib}) endif() endforeach() if(link_libs) list(REVERSE link_libs) list(REMOVE_DUPLICATES link_libs) list(REVERSE link_libs) endif() set(${var} ${link_libs} PARENT_SCOPE) endfunction() function(bob_install_provenance) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}_cmake_args.txt "${${CODING_CONV_PREFIX}_CMAKE_ARGS}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}_cmake_args.txt DESTINATION lib/cmake/${CODING_CONV_PREFIX}) get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) string(STRIP "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE) string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type_upper) foreach(lang IN LISTS languages) file( WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}_${lang}_compile_line.txt "${CMAKE_${lang}_COMPILER} ${CMAKE_${lang}_FLAGS} ${CMAKE_${lang}_FLAGS_${build_type_upper}}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}_${lang}_compile_line.txt DESTINATION lib/cmake/${CODING_CONV_PREFIX}) endforeach() foreach(tgt IN LISTS ${CODING_CONV_PREFIX}_EXPORTED_TARGETS) get_target_property(tgt_type "${tgt}" TYPE) if(tgt_type MATCHES "STATIC_LIBRARY|SHARED_LIBRARY") bob_get_link_libs(${tgt} link_libs) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}_${tgt}_libs.txt "${link_libs}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}_${tgt}_libs.txt DESTINATION lib/cmake/${CODING_CONV_PREFIX}) endif() endforeach() endfunction(bob_install_provenance) function(bob_end_package) include(CMakePackageConfigHelpers) set(INCLUDE_INSTALL_DIR include) set(LIB_INSTALL_DIR lib) set(LATEST_FIND_DEPENDENCY "#The definition of this macro is really inconvenient prior to CMake #commit ab358d6a859d8b7e257ed1e06ca000e097a32ef6 #we'll just copy the latest code into our Config.cmake file macro(latest_find_dependency dep) if (NOT \${dep}_FOUND) set(cmake_fd_quiet_arg) if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) set(cmake_fd_quiet_arg QUIET) endif() set(cmake_fd_required_arg) if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) set(cmake_fd_required_arg REQUIRED) endif() get_property(cmake_fd_alreadyTransitive GLOBAL PROPERTY _CMAKE_\${dep}_TRANSITIVE_DEPENDENCY ) find_package(\${dep} \${ARGN} \${cmake_fd_quiet_arg} \${cmake_fd_required_arg} ) if(NOT DEFINED cmake_fd_alreadyTransitive OR cmake_fd_alreadyTransitive) set_property(GLOBAL PROPERTY _CMAKE_\${dep}_TRANSITIVE_DEPENDENCY TRUE) endif() if (NOT \${dep}_FOUND) set(\${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE \"\${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency \${dep} could not be found.\") set(\${CMAKE_FIND_PACKAGE_NAME}_FOUND False) return() endif() set(cmake_fd_required_arg) set(cmake_fd_quiet_arg) set(cmake_fd_exact_arg) endif() endmacro(latest_find_dependency)") set(FIND_DEPS_CONTENT) foreach(dep IN LISTS ${CODING_CONV_PREFIX}_DEPS) string(REPLACE ";" " " FIND_DEP_ARGS "${${dep}_find_package_args}") set(FIND_DEPS_CONTENT "${FIND_DEPS_CONTENT} latest_find_dependency(${dep} ${FIND_DEP_ARGS})") endforeach() set(CONFIG_CONTENT "set(${CODING_CONV_PREFIX}_VERSION ${${CODING_CONV_PREFIX}_VERSION}) ${LATEST_FIND_DEPENDENCY} ${FIND_DEPS_CONTENT} set(${CODING_CONV_PREFIX}_EXPORTED_TARGETS \"${${CODING_CONV_PREFIX}_EXPORTED_TARGETS}\") foreach(tgt IN LISTS ${CODING_CONV_PREFIX}_EXPORTED_TARGETS) include(\${CMAKE_CURRENT_LIST_DIR}/\${tgt}-target.cmake) endforeach()") foreach(TYPE IN ITEMS "BOOL" "INT" "STRING") if(${CODING_CONV_PREFIX}_KEY_${TYPE}S) foreach(KEY_${TYPE} IN LISTS ${CODING_CONV_PREFIX}_KEY_${TYPE}S) set(val "${${KEY_${TYPE}}}") # escape escapes string(REPLACE "\\" "\\\\" val "${val}") # escape quotes string(REPLACE "\"" "\\\"" val "${val}") set(CONFIG_CONTENT "${CONFIG_CONTENT} set(${KEY_${TYPE}} \"${val}\")") endforeach() endif() endforeach() set(CONFIG_CONTENT "${CONFIG_CONTENT} ") install(FILES "${PROJECT_BINARY_DIR}/${CODING_CONV_PREFIX}Config.cmake" DESTINATION lib/cmake/${CODING_CONV_PREFIX}) if(PROJECT_VERSION) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}Config.cmake "${CONFIG_CONTENT}") write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/${CODING_CONV_PREFIX}ConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) install(FILES "${PROJECT_BINARY_DIR}/${CODING_CONV_PREFIX}ConfigVersion.cmake" DESTINATION lib/cmake/${CODING_CONV_PREFIX}) endif() bob_install_provenance() endfunction(bob_end_package) bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/build-time-copy.cmake000066400000000000000000000034551434206647400273100ustar00rootroot00000000000000# Create a build rule that copies a file. # # cpp_cc_build_time_copy(INPUT OUTPUT [NO_TARGET]) # # This creates a custom target that is always built and depends on `output_path` and a rule to # create `output_path` by copying `input_path`. This means that changes to the input file # (`input_path`, presumably in the source tree) can be propagated to `output_path` (presumably in # the build tree) automatically and without re-running CMake. The existence of a custom command that # produces `output_path` makes it trivial for other targets to declare that they depend on this # file, provided that they are working in the same directory (CMakeLists.txt -- see documentation of # DEPENDS arguments to add_custom_command and add_custom_target). If the NO_TARGET flag is passed # then no target is added, and the caller takes responsibility for declaring a dependency on the # output file and causing it to be built. This can be a good idea to avoid the number of top level # targets growing too large, which causes the Make build system to be very slow. function(cpp_cc_build_time_copy) cmake_parse_arguments(opt "NO_TARGET" "INPUT;OUTPUT" "" ${ARGN}) if(NOT DEFINED opt_INPUT) message(ERROR "build_time_copy missing required keyword argument INPUT.") endif() if(NOT DEFINED opt_OUTPUT) message(ERROR "build_time_copy missing required keyword argument OUTPUT.") endif() add_custom_command( OUTPUT "${opt_OUTPUT}" DEPENDS "${opt_INPUT}" COMMAND ${CMAKE_COMMAND} -E copy "${opt_INPUT}" "${opt_OUTPUT}") if(NOT opt_NO_TARGET) string(SHA256 target_name "${opt_INPUT};${opt_OUTPUT}") set(target_name "build-time-copy-${target_name}") if(NOT TARGET "${target_name}") add_custom_target(${target_name} ALL DEPENDS "${opt_OUTPUT}") endif() endif() endfunction() bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/cpplib.py000066400000000000000000000355461434206647400251340ustar00rootroot00000000000000import argparse from fnmatch import fnmatch import functools import logging import os import re import shlex import shutil import subprocess import sys THIS_SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) def log_command(*commands): if len(commands) == 1: logging.info(" ".join([shlex.quote(e) for e in commands[0]])) else: logging.info(" " + " |\n ".join([" ".join(cmd) for cmd in commands])) def source_dir(git="git"): """ Args: git: name or path to Git utility Returns: absolute path to the root of a repository. The parent repository if hpc-coding-conventions is used as a git module, this repository otherwise. Alternative to "git rev-parse --show-superproject-working-tree" but this solution requires git 2.13 or higher """ def git_rev_parse(*args, **kwargs): cmd = list((git, "rev-parse") + args) log_command(cmd) output = subprocess.check_output(cmd, **kwargs).decode("utf-8").strip() return os.path.realpath(output) git_dir = git_rev_parse("--git-dir", cwd=THIS_SCRIPT_DIR) if os.path.dirname(git_dir) not in THIS_SCRIPT_DIR: # This project is used as a git module module_dir = git_rev_parse("--show-toplevel", cwd=THIS_SCRIPT_DIR) git_dir = git_rev_parse("--git-dir", cwd=os.path.dirname(module_dir)) return os.path.dirname(git_dir) class cached_property(object): """ A property that is only computed once per instance and then replaces itself with an ordinary attribute. Deleting the attribute resets the property. Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 """ # noqa def __init__(self, func): self.__doc__ = getattr(func, "__doc__") self.func = func def __get__(self, obj, cls): if obj is None: return self value = obj.__dict__[self.func.__name__] = self.func(obj) return value def is_file_tracked(file, git="git", cwd=None): """ Args: file: relative path to file within a git repository cwd: optional path to change before executing the command Returns: true if the given file is tracked by a git repository, false otherwise """ ret = subprocess.call( [git, "ls-files", "--error-unmatch", file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd, ) return ret == 0 def do_merge_yaml(*files, **kwargs): """Merge YAML files. The last argument is the destination file""" try: import yaml except ImportError: logging.error( "Cannot find Python yaml module, which is needed to merge YAML files." ) sys.exit(1) succeeded = True transformers = kwargs.get("transformers", {}) out = files[-1] ins = files[:-1] outdated = not os.path.exists(out) or os.path.getmtime(out) < max( (os.path.getmtime(f) for f in ins) ) if outdated: data = {} for file in ins: with open(file) as istr: content = yaml.safe_load(istr) if not isinstance(content, dict): logging.error( "while reading YAML file %s: expected dictionary but got %s", file, type(content).__name__, ) succeeded = False continue for key, value in content.items(): transform_func = transformers.get(key) if transform_func: data[key] = transform_func(data.get(key), value) else: data[key] = value logging.info("writing file %s", out) if succeeded: with open(out, "w") as ostr: yaml.dump(data, ostr, default_flow_style=False) else: logging.info("file %s is up to date, nothing to do.", out) return succeeded def merge_clang_tidy_checks(orig_checks, new_checks): """Merge 2 'Checks' ClangTidy configuration key values""" if orig_checks is None: return new_checks orig_checks = [check.strip() for check in orig_checks.split(",")] new_checks = [check.strip() for check in new_checks.split(",")] for new_check in new_checks: if new_check.startswith("-"): name = new_check[1:] # remove check when check=google-runtime-references to_=-google-* orig_checks = list( check for check in orig_checks if not fnmatch(check, name) ) # remove check when check=-google-runtime-references # to_=-google-* (simplification) orig_checks = list( check for check in orig_checks if not fnmatch(check, new_check) ) else: # remove check when check=-google-runtime-references to_=google-* orig_checks = list( check for check in orig_checks if not fnmatch(check, "-" + new_check) ) # remove check when check=google-runtime-references # to_=google-* (simplification) orig_checks = list( check for check in orig_checks if not fnmatch(check, new_check) ) orig_checks.append(new_check) return ",".join(orig_checks) do_merge_clang_tidy_yaml = functools.partial( do_merge_yaml, transformers=dict(Checks=merge_clang_tidy_checks) ) class Tool: """Wrapper class for the tools supported by this project i.e clang-format cmake-format, and clang-tidy """ def __init__(self, name_or_path): """ Args: name_or_path: clang-format, clang-format-13, or /path/to/llvm/bin/clang-format-13 """ self._name_or_path = name_or_path DEFAULT_TOOL_CONFIG = dict( config=".{self}", custom_config="{self.config}.changes", merge_yaml_func=do_merge_yaml, ) TOOL_CONFIG = { "cmake-format": dict( config=".{self}.yaml", custom_config=".{self}.changes.yaml", ), "clang-tidy": dict(merge_yaml_func=do_merge_clang_tidy_yaml), } def config_key(self, key): """ retrieve the value of a config key. Looks first into TOOL_CONFIG, then DEFAULT_TOOL_CONFIG """ tc = Tool.TOOL_CONFIG.get(str(self), {}) return tc.get(key, Tool.DEFAULT_TOOL_CONFIG[key]) @cached_property def name(self): """ Returns: tool name, i.e "clang-format" """ tool = os.path.basename(self._name_or_path) if re.match(".*-[0-9]+", tool): tool, _ = tool.rsplit("-", 1) return tool def __str__(self): return self.name @property def config(self): """ Returns: path to the tool config file name, i.e ".clang-format" """ return self.config_key("config").format(self=self) @property def custom_config(self): """ Returns: path to custom tool config file that can contain only modifications of the default config, i.e ".clang-format.changes" """ return self.config_key("custom_config").format(self=self) @cached_property def version(self): """ Returns: tool version, i.e "13.0.0" """ cmd = [self._name_or_path, "--version"] log_command(cmd) proc = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True, encoding="utf-8", ) output = proc.stdout.strip() match = re.search("([0-9]+\\.[0-9]+\\.[0-9]+)", output) if match: ver = match.group(1) return ver raise RuntimeError( f"Could not extract {self.name} version from output: '{output}'" ) @cached_property def bbp_config(self): """ Returns: absolute path to the proper config file in the hpc-coding-conventions project """ version = self.version major_ver, _ = self.version.split(".", 1) config_lib_dir = os.path.dirname(THIS_SCRIPT_DIR) test_ver = int(major_ver) while test_ver >= 0: candidate = os.path.join(config_lib_dir, f"{self.name}-{test_ver}") if os.path.exists(candidate): return candidate test_ver -= 1 candidate = os.path.join(config_lib_dir, self.config[1:]) if os.path.exists(candidate): return candidate raise RuntimeError( f"Could not find appropriate config file for {self} {version}" ) def setup_config(self, source_dir, git="git"): """ Build the tool config file and write it in the project source directory Args: source_dir: project top directory where the config file should be written """ config = os.path.join(source_dir, self.config) if not is_file_tracked( self.config, git=git, cwd=source_dir ) or not os.path.exists(config): custom_config = os.path.join(source_dir, self.custom_config) if os.path.exists(custom_config): logging.info(f"Merging custom {self} YAML changes ") self.config_key("merge_yaml_func")( self.bbp_config, custom_config, config ) else: bbp_config = self.bbp_config bbp_config_base = os.path.basename(bbp_config) if not os.path.exists(config) or os.path.getmtime( config ) < os.path.getmtime(bbp_config): logging.info( f"Copying BBP config {bbp_config_base} to {source_dir}" ) shutil.copy(bbp_config, config) else: logging.info( f"{self} config is up to date with BBP {bbp_config_base} config" ) else: logging.info(f"{self} config is tracked by git, nothing to do.") def make_file_filter(excludes_re, files_re): """ Returns: a Python function used to filter the C++ files that needs to be formatted. """ def _func(cpp_file): for exclude_re in excludes_re: if exclude_re.match(cpp_file): return True for file_re in files_re: if file_re.match(cpp_file): return False return True return _func def collect_files(source_dir, filter_file): """ Args: cli_args: parsed CLI options filter_file: a function returning `True` if the given file should be excluded, `False` otherwise. Returns: Generator of path to files in source_dir """ cmd = ["git", "ls-tree", "-r", "-z", "--name-only", "--full-name", "HEAD"] log_command(cmd) files = subprocess.check_output(cmd, cwd=source_dir).decode("utf-8").split("\0") files = [x for x in files if not filter_file(x)] files = [os.path.join(source_dir, x) for x in files] return files def parse_cli( choices=None, description=None, parser_args=None, args=None, executable=None ): """Common CLI parser for all tool wrapper scripts bbp-{tool}.py""" if executable is None: # deduce tool name from the Python script name i.e # /path/to/bbp-clang-format.py => clang-format script_name = os.path.basename(sys.argv[0]) if re.match("bbp-.*\\.py", script_name): executable = script_name[4:-3] else: raise Exception(f"Could not extract tool name from script: {sys.argv[0]}") choices = (choices or []).append("config") parser = argparse.ArgumentParser( description=description or "Wrapper for checker utility" ) parser.add_argument( "-S", dest="source_dir", metavar="PATH", help="Path to CMake source directory, default is the parent repository root", ) parser.add_argument( "--executable", help="Path to executable to run", default=executable ) parser.add_argument( "--excludes-re", nargs="*", default=[], help="list of regular expressions of files to exclude", ) parser.add_argument( "--files-re", nargs="*", default=[], help="List of regular expressions of files to include", ) parser.add_argument( "--files-by-suffix", nargs="*", help="List of suffixes of the files to include" ) parser.add_argument( "--make-unescape-re", action="store_true", help="Unescape make-escaped regular-expression arguments", ) parser.add_argument( "--git-executable", default="git", help="Path to git executable [default is %(default)s]", ) for name, kwargs in parser_args or []: parser.add_argument(name, **kwargs) parser.add_argument("action", choices=choices) parser.add_argument("options", nargs="*", help="Options given to executable") result = parser.parse_args(args=args) if not result.source_dir: result.source_dir = source_dir(result.git_executable) if result.make_unescape_re: def make_unescape_re(pattern): if pattern.endswith("$$"): pattern = pattern[:-1] pattern = pattern.replace("\\\\", "\\") return pattern def make_unescape_res(patterns): return [make_unescape_re(pattern) for pattern in patterns] result.files_re = make_unescape_res(result.files_re) result.excludes_re = make_unescape_res(result.excludes_re) if result.files_by_suffix: result.files_re += [f".*\\.{suffix}$" for suffix in result.files_by_suffix] result.options = [opt for opt in result.options if opt] Tool(result.executable).setup_config(result.source_dir, result.git_executable) if result.action == "config": sys.exit(0) return result def main(args=None): parser = argparse.ArgumentParser(description="Utility program") subparsers = parser.add_subparsers(help="sub-command help") merge_yaml = subparsers.add_parser("merge-yaml", help="Merge yaml files") merge_yaml.add_argument("files", nargs="+", help="input files then output file") merge_yaml.set_defaults(func=do_merge_yaml) merge_yaml = subparsers.add_parser( "merge-clang-tidy-config", help="Merge ClangTidy configuration files" ) merge_yaml.add_argument("files", nargs="+", help="input files then output file") merge_yaml.set_defaults(func=do_merge_clang_tidy_yaml) result = parser.parse_args(args=args) return result.func(*result.files) if __name__ == "__main__": level = logging.INFO if "VERBOSE" in os.environ else logging.WARN logging.basicConfig(level=level, format="%(levelname)s: %(message)s") sys.exit(0 if main() else 1) bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/git-push-message.cmake.in000066400000000000000000000014321434206647400300650ustar00rootroot00000000000000# This file is the template of a CMake script executed by the pre-commit utility # whenever the hook named `courtesy-msg` is enabled # (see "Pre-commit utility" section in cpp/README.md) # # To execute your own script (with another message for instance), write a CMake template # in ${PROJECT_SOURCE_DIR}/.git-push-message.cmake.in message(STATUS "\n\ This is a courtesy message to remind you to properly test and format your changes\n\ before opening a Pull Request.\n\ To format your changes:\n\ pushd /path/to/build/dir\n\ cmake -D@CODING_CONV_PREFIX@_FORMATTING=ON -D@CODING_CONV_PREFIX@_TEST_FORMATTING=ON .\n\ make clang_format cmake_format\n\ To run the tests and check the formatting:\n\ make test\n\ You should also read the contributing guidelines this project may provide") bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/cmake/sanitizers.cmake000066400000000000000000000270401434206647400264740ustar00rootroot00000000000000set(${CODING_CONV_PREFIX}_SANITIZERS "" CACHE STRING "Comma-separated list of runtime sanitizers to enable. Possible values: address, leak, undefined" ) set(${CODING_CONV_PREFIX}_SANITIZERS_UNDEFINED_EXCLUSIONS "" CACHE STRING "Undefined behaviour sanitizer checks **not** to enable if ${CODING_CONV_PREFIX}_SANITIZERS contains 'undefined'" ) # Find the path of a sanitizer runtime library. This is mainly intended for internal use. # # cpp_cc_find_sanitizer_runtime(NAME [] OUTPUT []) function(cpp_cc_find_sanitizer_runtime) cmake_parse_arguments("" "" "NAME;OUTPUT" "" ${ARGN}) set(name_template ${CMAKE_SHARED_LIBRARY_PREFIX}clang_rt.) if(APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") string(APPEND name_template ${_NAME}_osx_dynamic) else() string(APPEND name_template ${_NAME}-${CMAKE_SYSTEM_PROCESSOR}) endif() string(APPEND name_template ${CMAKE_SHARED_LIBRARY_SUFFIX}) execute_process( COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=${name_template} RESULT_VARIABLE clang_status OUTPUT_VARIABLE runtime_library ERROR_VARIABLE clang_stderr OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) if(${clang_status}) message( FATAL_ERROR "Failed to find ${_NAME} runtime library (stdout: ${runtime_library}, stderr: ${clang_stderr})" ) endif() message(STATUS "Sanitizer runtime library: ${runtime_library}") set(${_OUTPUT} "${runtime_library}" PARENT_SCOPE) endfunction() # Assemble compiler flags, environment variables and command prefixes needed to enable runtime # sanitizers (address, leak, undefined behaviour, thread, memory...) # # cpp_cc_enable_sanitizers() # # Sets: # # * ${CODING_CONV_PREFIX}_SANITIZER_COMPILER_FLAGS: compiler flags that should be passed to the # compiler and linker. # * ${CODING_CONV_PREFIX}_SANITIZER_ENABLE_ENVIRONMENT: environment variables that should be set to # **enable** sanitizers at runtime. # * ${CODING_CONV_PREFIX}_SANITIZER_DISABLE_ENVIRONMENT: environment variables that should be set to # **disable** sanitizers at runtime. This might be useful if, for example, some part of the # instrumented application is used during the build and you don't want memory leaks to cause build # failures. # * ${CODING_CONV_PREFIX}_SANITIZER_PRELOAD_VAR: the environment variable used to load the sanitizer # runtime library. This is typically LD_PRELOAD or DYLD_INSERT_LIBRARIES. # * ${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_PATH: the sanitizer runtime library. This sometimes # needs to be added to ${CODING_CONV_PREFIX}_SANITIZER_PRELOAD_VAR. # * ${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_DIR: the directory where the sanitizer runtime library # sits. This is provided separately from the ENVIRONMENT variables to avoid assumptions about the # sanitizers being the only thing modifying LD_LIBRARY_PATH # # The caller is responsible for using these variables in a manner adapted to their application. function(cpp_cc_enable_sanitizers) message(STATUS "Enabling sanitizers: ${${CODING_CONV_PREFIX}_SANITIZERS}") # comma-separated string -> CMake list string(REPLACE "," ";" sanitizers "${${CODING_CONV_PREFIX}_SANITIZERS}") if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(known_undefined_checks undefined) else() set(known_undefined_checks undefined float-divide-by-zero unsigned-integer-overflow implicit-integer-sign-change implicit-signed-integer-truncation implicit-unsigned-integer-truncation local-bounds nullability-arg nullability-assign nullability-return) endif() # Use the shared library version of the sanitizer runtime so that we can LD_PRELOAD it when # launching via Python and so on set(compiler_flags -fno-omit-frame-pointer -shared-libsan) if("undefined" IN_LIST sanitizers) if(NOT sanitizers STREQUAL "undefined") message( FATAL_ERROR "Enabling the undefined behaviour sanitizer at the same time as other sanitizers is not currently supported (got: ${${CODING_CONV_PREFIX}_SANITIZERS})" ) endif() # Enable the undefined behaviour sanitizer set(undefined_checks ${known_undefined_checks}) if(${CODING_CONV_PREFIX}_SANITIZERS_UNDEFINED_EXCLUSIONS) message( STATUS "Disabling UBSan checks: ${${CODING_CONV_PREFIX}_SANITIZERS_UNDEFINED_EXCLUSIONS}") list(REMOVE_ITEM undefined_checks ${${CODING_CONV_PREFIX}_SANITIZERS_UNDEFINED_EXCLUSIONS}) endif() foreach(undefined_check ${undefined_checks}) list(APPEND compiler_flags -fsanitize=${undefined_check}) endforeach() # If we were asked to disable checks that are not listed in known_undefined_checks then emit # -fno-sanitize=XXX for them list(REMOVE_ITEM ${CODING_CONV_PREFIX}_SANITIZERS_UNDEFINED_EXCLUSIONS ${known_undefined_checks}) foreach(undefined_check ${${CODING_CONV_PREFIX}_SANITIZERS_UNDEFINED_EXCLUSIONS}) list(APPEND compiler_flags -fno-sanitize=${undefined_check}) endforeach() string(JOIN " " compiler_flags_str ${compiler_flags}) # Figure out where the runtime library lives if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(runtime_library_name ubsan) else() set(runtime_library_name ubsan_standalone) endif() cpp_cc_find_sanitizer_runtime(NAME ${runtime_library_name} OUTPUT runtime_library) if(EXISTS "${PROJECT_SOURCE_DIR}/.sanitizers/undefined.supp") set(ubsan_opts "suppressions=${PROJECT_SOURCE_DIR}/.sanitizers/undefined.supp:") endif() if(LLVM_SYMBOLIZER_PATH) set(extra_env "UBSAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER_PATH}") endif() set(enable_env ${extra_env} "UBSAN_OPTIONS=${ubsan_opts}print_stacktrace=1:halt_on_error=1:report_error_type=1") set(disable_env ${extra_env} "UBSAN_OPTIONS=${ubsan_opts}print_stacktrace=0:halt_on_error=0") elseif("address" IN_LIST sanitizers) list(APPEND compiler_flags -fsanitize=address -fsanitize-address-use-after-scope) # Figure out where the runtime library lives cpp_cc_find_sanitizer_runtime(NAME asan OUTPUT runtime_library) if(APPLE) # Suppress and annoying "malloc: nano zone abandoned due to inability to preallocate reserved # vm space" message. See https://stackoverflow.com/a/70209891 list(APPEND extra_env MallocNanoZone=0) endif() if(LLVM_SYMBOLIZER_PATH) list(APPEND extra_env "ASAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER_PATH}") if("leak" IN_LIST sanitizers) list(APPEND extra_env "LSAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER_PATH}") endif() endif() set(enable_env ${extra_env} ASAN_OPTIONS=check_initialization_order=1:detect_stack_use_after_return=1) if("leak" IN_LIST sanitizers) string(APPEND enable_env ":detect_leaks=1") list(APPEND enable_env PYTHONMALLOC=malloc) if(EXISTS "${PROJECT_SOURCE_DIR}/.sanitizers/leak.supp") list(APPEND enable_env "LSAN_OPTIONS=suppressions=${PROJECT_SOURCE_DIR}/.sanitizers/leak.supp") endif() else() string(APPEND enable_env ":detect_leaks=0") endif() set(disable_env ${extra_env} ASAN_OPTIONS=detect_leaks=0) elseif("leak" IN_LIST sanitizers) message(FATAL_ERROR "LSan not yet supported in standalone mode") else() message(FATAL_ERROR "${sanitizers} sanitizers not yet supported") endif() if(APPLE) set(preload_var DYLD_INSERT_LIBRARIES) else() set(preload_var LD_PRELOAD) endif() get_filename_component(runtime_library_directory "${runtime_library}" DIRECTORY) set(${CODING_CONV_PREFIX}_SANITIZER_COMPILER_FLAGS "${compiler_flags}" PARENT_SCOPE) set(${CODING_CONV_PREFIX}_SANITIZER_ENABLE_ENVIRONMENT "${enable_env}" PARENT_SCOPE) set(${CODING_CONV_PREFIX}_SANITIZER_DISABLE_ENVIRONMENT "${disable_env}" PARENT_SCOPE) set(${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_DIR "${runtime_library_directory}" PARENT_SCOPE) set(${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_PATH "${runtime_library}" PARENT_SCOPE) set(${CODING_CONV_PREFIX}_SANITIZER_PRELOAD_VAR "${preload_var}" PARENT_SCOPE) endfunction(cpp_cc_enable_sanitizers) # Helper function that modifies targets (executables, libraries, ...) and tests (created by # add_test) for successful execution when sanitizers are enabled # # cpp_cc_configure_sanitizers(TARGET [ ...] TEST [ ...] [PRELOAD]) # # Arguments: # # * TARGET: list of targets to modify # * TEST: list of tests to modify # * PRELOAD: if passed, ${CODING_CONV_PREFIX}_SANITIZER_PRELOAD_VAR will be set to the sanitizer # runtime library and LD_LIBRARY_PATH will not be modified function(cpp_cc_configure_sanitizers) cmake_parse_arguments("" "PRELOAD" "" "TARGET;TEST" ${ARGN}) foreach(target ${_TARGET}) # Make sure that the RPATH to the sanitizer runtime is set, so the library/executable can be run # without setting $LD_LIBRARY_PATH set_property( TARGET ${target} APPEND PROPERTY BUILD_RPATH "${${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_DIR}") set_property( TARGET ${target} APPEND PROPERTY INSTALL_RPATH "${${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_DIR}") endforeach() foreach(test ${_TEST}) if(_PRELOAD AND ${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_PATH) set_property( TEST ${test} APPEND PROPERTY ENVIRONMENT ${CODING_CONV_PREFIX}_SANITIZER_PRELOAD_VAR=${${CODING_CONV_PREFIX}_SANITIZER_LIBRARY_PATH} ) endif() # This should be sanitizer-specific stuff like UBSAN_OPTIONS, so we don't need to worry about # merging it with an existing value. set_property( TEST ${test} APPEND PROPERTY ENVIRONMENT ${${CODING_CONV_PREFIX}_SANITIZER_ENABLE_ENVIRONMENT}) endforeach() endfunction(cpp_cc_configure_sanitizers) # Helper function strips away Python shims on macOS, so we can launch tests using the actual Python # binary. Without this, preloading the sanitizer runtimes does not work on macOS. See # https://jonasdevlieghere.com/sanitizing-python-modules/ and # https://tobywf.com/2021/02/python-ext-asan/ for more information. # # cpp_cc_strip_python_shims(EXECUTABLE OUTPUT ) # # Arguments: # # * EXECUTABLE: the Python executable/shim to try and resolve # * OUTPUT: output variable for the actual Python executable function(cpp_cc_strip_python_shims) cmake_parse_arguments("" "" "EXECUTABLE;OUTPUT" "" ${ARGN}) if(APPLE AND ${CODING_CONV_PREFIX}_SANITIZERS) set(python_script "import ctypes" "dyld = ctypes.cdll.LoadLibrary('/usr/lib/system/libdyld.dylib')" "namelen = ctypes.c_ulong(1024)" "name = ctypes.create_string_buffer(b'\\000', namelen.value)" "dyld._NSGetExecutablePath(ctypes.byref(name), ctypes.byref(namelen))" "print(name.value.decode())") string(JOIN "; " python_command ${python_script}) execute_process( COMMAND ${_EXECUTABLE} -c "${python_command}" RESULT_VARIABLE python_status OUTPUT_VARIABLE actual_executable ERROR_VARIABLE python_stderr OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) if(NOT python_status EQUAL 0) message(FATAL_ERROR "stdout: ${actual_executable}, stderr: ${python_stderr}") endif() if(NOT _EXECUTABLE STREQUAL actual_executable) message(STATUS "Resolved shim ${_EXECUTABLE} to ${actual_executable}") endif() else() set(actual_executable "${_EXECUTABLE}") endif() set(${_OUTPUT} "${actual_executable}" PARENT_SCOPE) endfunction() if(${CODING_CONV_PREFIX}_SANITIZERS) cpp_cc_enable_sanitizers() endif() bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/000077500000000000000000000000001434206647400243665ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/.gitignore000066400000000000000000000000321434206647400263510ustar00rootroot00000000000000README.html .clang-format bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/CONTRIBUTING.md000066400000000000000000000030121434206647400266130ustar00rootroot00000000000000# C++ Code Formatting Documentation Generation Utility This directory provides both documentation chunks and scripts to build an HTML document describing C++ code formatting conventions adopted by the BlueBrain HPC team. ## Requirements * A Python environment with *pyyaml* and *jinja* packages installed. A `Pipfile` is provided at repository top-level as a courtesy to setup such environment with *pipenv*. * ClangFormat 7 * Pandoc to generate HTML document (optional) ## How to build the documentation Execute command `make` to build both `README.html` and `README.md` ## How to edit the documentation? Use GitHub pull-request. Make sure the HTML documentation (or at least the Markdown one) builds properly. Generated `README.md` is part of the repository. Make sure to rebuild it and to include the changes in your pull-request. Edit `formatting.md.jinja` template or C++ code snippets in the [`snippets`](./snippets) directory. A C++ snippet has the following structure: ```cpp // TITLE // optional *markown* description // Multiline description is supported // clang-format: KEY // clang-format: KEY: VALUE template your cpp(code); ``` * TITLE: used as section in generated documentation * optional description is included before the code snippet * If a C++ snippet highlights a particular clang-format configuration key, then it should be named after it. * Additional Clang-Format key/value can be specified in description. If value is not specified, then it is retrieved from clang-format configuration. bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/Makefile000066400000000000000000000011241434206647400260240ustar00rootroot00000000000000# Targets: # # README.md: rebuild the Markdown documentation (default target) # # all: alias for README.md # # clean: remove top generated files # # distclean: clean + remove intermediate files # PANDOC ?= pandoc SNIPPETS = $(wildcard snippets/*.cpp) all: README.md clean: $(RM) README.md README.html distclean: clean $(RM) .clang-format # README.html .md.html: $< $(PANDOC) -s -c github-pandoc.css $< -o $@ README.html: github-pandoc.css # README.md README.md: README.md.jinja formatting.py $(SNIPPETS) ../../bin/format --lang c++ ./formatting.py $< $@ .SUFFIXES: .md .html bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/README.md000066400000000000000000000316211434206647400256500ustar00rootroot00000000000000# BlueBrain HPC Team C++ Code Formatting This document provides a set of examples to highlight C++ code formatting conventions adopted by BlueBrain HPC team. # Similar work * NSE Team also provides [such document](https://bbpteam.epfl.ch/project/spaces/pages/viewpage.action?spaceKey=BBPNSE&title=Code+style) # Conventions ## Ordering of Includes You may follow the conventions described in the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Names_and_Order_of_Includes) ## Access Modifiers Indentation inside `struct` and `class` access modifiers may be indented this way: ### Example ```cpp class Foo { public: Foo(); private: int data; }; ``` ### Clang-Format configuration * `AccessModifierOffset: -2` ## Horizontally align arguments after an open bracket ### Example ```cpp thisIsAVeryLongMethodName(thisIsTheFirstArgument, andHereIsTheSecondArgument, theThirdArgument, theFourthArgument, theFifthArgument); ``` ### Clang-Format configuration * `AlignAfterOpenBracket: Align` ## Do not align fields in array initialization ### Example ```cpp struct test demo[] = {{56, 23, "hello"}, {-1, 93463, "world"}, {7, 5, "!!"}}; ``` ### Clang-Format configuration * `AlignArrayOfStructures: None` ## Do not align consecutive assignments ### Example ```cpp int aaaa = 12; int b = 23; int ccc = 23; ``` ### Clang-Format configuration * `AlignConsecutiveAssignments: False` ## Do not align consecutive declarations ### Example ```cpp int aaaa = 12; float b = 23; std::string ccc = 23; ``` ### Clang-Format configuration * `AlignConsecutiveDeclarations: False` ## Align escaped newlines as far left as possible ### Example ```cpp #define MY_MACRO \ blablablablablablablabla blablablablablablablablablablablablablabla \ blablablablablablablablablablablablablablablabla ``` ### Clang-Format configuration * `AlignEscapedNewlinesLeft: True` ## Horizontally align operands of binary and ternary expressions. ### Example ```cpp int aaa = bbbbbbbbbbbbbbb + ccccccccccccccc; ``` ### Clang-Format configuration * `AlignOperands: True` ## Align trailing comments, prefixed with 2 spaces ### Example ```cpp int a; // My comment for a int b = 2; // comment b ``` ### Clang-Format configuration * `AlignTrailingComments: True` ## Never contract short blocks on a single line ### Example ```cpp if (a) { return; } ``` ### Clang-Format configuration * `AllowShortBlocksOnASingleLine: False` ## Never contract case labels on a single line ### Example ```cpp switch (a) { case 1: x = 1; break; case 2: return; case 3: return; // NOT THIS } ``` ### Clang-Format configuration * `AllowShortCaseLabelsOnASingleLine: False` ## Only empty methods can be put in a single line ### Example ```cpp class Foo { void f() { foo(); } void f() {} }; ``` ### Clang-Format configuration * `AllowShortFunctionsOnASingleLine: Empty` ## Conditions Break even for simple `if` statement and always put body inside braces. See clang-tidy check [readability-braces-around-statements](http://releases.llvm.org/7.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability-braces-around-statements.html#readability-braces-around-statements) ### Example ```cpp if (a) { return; } ``` ### Clang-Format configuration * `AllowShortIfStatementsOnASingleLine: False` ## Loop statements Break even for simple `for` and `while` statements and always put bodies inside braces. See clang-tidy check [readability-braces-around-statements](http://releases.llvm.org/7.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability-braces-around-statements.html#readability-braces-around-statements) ### Example ```cpp while (true) { continue; } ``` ### Clang-Format configuration * `AllowShortLoopsOnASingleLine: False` ## Break before multiline strings ### Example ```cpp AAAA = "bbbb" "cccc"; ``` ### Clang-Format configuration * `AlwaysBreakBeforeMultilineStrings: True` ## Break after template declaration ### Example ```cpp template T foo() {} template T foo(int aaaaaaaaaaaaaaaaaaaaa, int bbbbbbbbbbbbbbbbbbbbb) {} ``` ### Clang-Format configuration * `AlwaysBreakTemplateDeclarations: True` ## Opening brace is on same line than struct/class definition ### Example ```cpp class Foo {}; struct Bar { int i; }; ``` ### Clang-Format configuration * `BraceWrapping.AfterClass: False` ## Brace is on same line than control statements ### Example ```cpp if (foo()) { } else { } for (int i = 0; i < 10; ++i) { } ``` ### Clang-Format configuration * `BraceWrapping.AfterControlStatement: False` ## Brace on same line than *enum* definition ### Example ```cpp enum X : int { A, B, C }; ``` ### Clang-Format configuration * `BraceWrapping.AfterEnum: False` ## Brace on same line than `extern "C"` directive ### Example ```cpp extern "C" { int foo(); } ``` ### Clang-Format configuration * `BraceWrapping.AfterExternBlock: False` ## Brace on same line than *function* definitions ### Example ```cpp void foo() { bar(); bar2(); } ``` ### Clang-Format configuration * `BraceWrapping.AfterFunction: False` ## Brace on same line than *namespace* definition ### Example ```cpp namespace { int foo(); int bar(); } // namespace ``` ### Clang-Format configuration * `BraceWrapping.AfterNamespace: False` ## Brace on same line than *struct* definitions ### Example ```cpp struct foo { int x; }; ``` ### Clang-Format configuration * `BraceWrapping.AfterStruct: False` ## Brace on same line than *union* definitions ### Example ```cpp union foo { int x; } ``` ### Clang-Format configuration * `BraceWrapping.AfterUnion: False` ## Do not break before *catch* directive ### Example ```cpp try { foo(); } catch () { } ``` ### Clang-Format configuration * `BraceWrapping.BeforeCatch: False` ## Do not break before *else* block ### Example ```cpp if (foo()) { } else { } ``` ### Clang-Format configuration * `BraceWrapping.BeforeElse: False` ## Do not break before binary operators When a break is required, the binary operator is the last token of the line. ### Example ```cpp LooooooooooongType loooooooooooooooooooooongVariable = someLooooooooooooooooongFunction(); bool value = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > ccccccccccccccccccccccccccccccccccccccccc; ``` ### Clang-Format configuration * `BreakBeforeBinaryOperators: False` ## New line before C++20 `concept` directive ### Example ```cpp template concept Hashable = requires(T a) { { std::hash{}(a) } -> std::convertible_to; }; ``` ### Clang-Format configuration * `BreakBeforeConceptDeclarations: True` ## break before long ternary operators ### Example ```cpp veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongDescription ? firstValue : SecondValueVeryVeryVeryVeryLong; ``` ### Clang-Format configuration * `BreakBeforeTernaryOperators: True` ## No space inside C++11 braced lists ### Example ```cpp vector x{1, 2, 3, 4}; vector x{{}, {}, {}, {}}; f(MyMap[{composite, key}]) new int[3]{1, 2, 3}; ``` ### Clang-Format configuration * `Cpp11BracedListStyle: True` ## No line break restriction after access modifiers ### Example ```cpp struct foo { private: int i; protected: int j; /* comment */ public: foo() {} private: protected: }; ``` ### Clang-Format configuration * `EmptyLineBeforeAccessModifier: Leave` ## Recall namespace when closing it ### Example ```cpp namespace bbp { foo(); } ``` ## Do no indent preprocesor directives ### Example ```cpp #if FOO #if BAR #include #endif // BAR #endif // FOO ``` ### Clang-Format configuration * `IndentPPDirectives: None` ## line break before C++20 `requires` directive ### Example ```cpp template requires Iterator void sort(It begin, It end) { //.... } ``` ### Clang-Format configuration * `IndentRequires: False` ## Use 4 columns for indentation ### Example ```cpp void f() { someFunction(); if (true, false) { f(); } } ``` ### Clang-Format configuration * `IndentWidth: 4` ## Indent lambda body based on the signature of the lambda ### Example ```cpp callingSomeLongLongLongLongLongLongLongLongLongLongMethod( [](SomeReallyLongLambdaSignatureArgument foo, SomeReallyLongLambdaSignatureArgument bar) { return; }); ``` ### Clang-Format configuration * `LambdaBodyIndentation: Signature` ## Do not indent when entering a *namespace* ### Example ```cpp namespace out { int i; namespace in { int i; } } // namespace out ``` ### Clang-Format configuration * `NamespaceIndentation: None` ## The pack constructor initializers style to use. ### Example ```cpp Constructor() : aaaaaaaaaaaaaaaaaaaa() , bbbbbbbbbbbbbbbbbbbb() , ddddddddddddd() ``` ### Clang-Format configuration * `PackConstructorInitializers: Never` ## pointer and reference are part of the type ### Example ```cpp int* a; int& b = getB(); ``` ### Clang-Format configuration * `PointerAlignment: Left` ## Different ways to arrange specifiers and qualifiers (e.g. const/volatile). ### Example ```cpp const int a; const int* a; ``` ### Clang-Format configuration * `QualifierAlignment: Left` ## Specifies the use of empty lines to separate definition blocks, including classes, structs, enums, and functions. ### Example ```cpp #include struct Foo { int a, b, c; }; namespace Ns { class Bar { public: struct Foobar { int a; int b; }; private: int t; int method1() { // ... } enum List { ITEM1, ITEM2 }; template int method2(T x) { // ... } int i, j, k; int method3(int par) { // ... } }; class C {}; } // namespace Ns ``` ### Clang-Format configuration * `SeparateDefinitionBlocks: Always` ## Sort includes in different sections * System headers * Dependencies * Current project ### Example ```cpp #include #include #include #include #include "mylib/myclass.h" ``` ### Clang-Format configuration * `SortIncludes: True` ## Spacing related specifications * Use 4 tab size * Never use tab ### Example ```cpp // a space is inserted after C style casts (int) i; // a space is inserted after the template keyword template void foo(); // a space a space before assignment operators int a = 5; a += 42; // do not put spaces before C++11 braced list Foo foo{bar}; Foo{}; vector{1, 2, 3}; new int[3]{1, 2, 3}; // do not add space before constructor initializer colon Foo::Foo() : a(a) {} // do not add space before inheritance colon class Foo: Bar { } // put a space before opening parentheses after control statement // (for/if/while...) void f() { if (true) { f(); } } // do not add space before ranged-based for loop colon for (auto v: values) { } // do not have space between `()` void f() { int x[] = {foo(), bar()}; if (true) { f(); } } // put {{ cf['SpacesBeforeTrailingComments']}} spaces before trailing comment void f() { if (true) { // foo1 f(); // bar } // foo } // do not add space after `<` and before `>` static_cast(arg); std::function fct; // do not add space inside C-style cast x = (int32) y; // do not add space insert `(` and before `)` t f(Deleted&) & = delete; // do not add space after `[` and before `]` int a[5]; ``` ### Clang-Format configuration * `SpaceAfterCStyleCast: True` * `SpaceAfterTemplateKeyword: True` * `SpaceBeforeAssignmentOperators: True` * `SpaceBeforeCpp11BracedList: False` * `SpaceBeforeCtorInitializerColon: False` * `SpaceBeforeInheritanceColon: False` * `SpaceBeforeParens: ControlStatements` * `SpaceBeforeRangeBasedForLoopColon: False` * `SpaceInEmptyParentheses: False` * `SpacesBeforeTrailingComments: 2` * `SpacesInAngles: False` * `SpacesInCStyleCastParentheses: False` * `SpacesInContainerLiterals: False` * `SpacesInParentheses: False` * `SpacesInSquareBrackets: False` * `TabWidth: 4` * `UseTab: Never` ## One parameter per line if they don't all fit ### Example ```cpp HighFive::DataSet dump(HighFive::File& file, const std::string& path, const std::vector& data, HighFive::Mode mode = HighFive::Mode::Create); void andALongFunctionName(the_long_parameter1, the_long_parameter2); ``` ### Clang-Format configuration * `AllowAllParametersOfDeclarationOnNextLine: False` * `BinPackParameters: False` # IDE Configuration ## JetBrains CLion CLion allows you to disable their formatting engine to get setting from CLangFormat: in `Settings > Editor > Code Style > C/C++`, enable `Enable ClangFormat support (only for C/C++/ObjectiveC)` checkbox.bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/README.md.jinja000066400000000000000000000020741434206647400267420ustar00rootroot00000000000000# BlueBrain HPC Team C++ Code Formatting This document provides a set of examples to highlight C++ code formatting conventions adopted by BlueBrain HPC team. # Similar work * NSE Team also provides [such document](https://bbpteam.epfl.ch/project/spaces/pages/viewpage.action?spaceKey=BBPNSE&title=Code+style) # Conventions ## Ordering of Includes You may follow the conventions described in the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Names_and_Order_of_Includes) {% for conv in conventions %} ## {{ conv.title}} {%- if conv.description %} {{ conv.description }} {%- endif %} ### Example ```cpp {{ conv.snippet }} ``` {%- if conv.clang_format %} ### Clang-Format configuration {%- endif %} {%- for key, value in conv.clang_format.items() %} * `{{ key }}: {{ value }}` {%- endfor %} {% endfor %} # IDE Configuration ## JetBrains CLion CLion allows you to disable their formatting engine to get setting from CLangFormat: in `Settings > Editor > Code Style > C/C++`, enable `Enable ClangFormat support (only for C/C++/ObjectiveC)` checkbox. bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/formatting.py000077500000000000000000000062511434206647400271210ustar00rootroot00000000000000#!/usr/bin/env python3 from collections import namedtuple import glob import os import os.path as osp import jinja2 import yaml CONVENTION_ATTRS = ["title", "description", "clang_format", "snippet"] class Convention(namedtuple("Convention", CONVENTION_ATTRS)): @staticmethod def from_file(clang_format, file): attrs = dict(description="", snippet="", clang_format={}) def jinja_expand(content): template = jinja2.Template(content) return template.render(cf=clang_format) def cf_value(key): data = clang_format for key in key.split("."): if key in data: data = data[key] return data def is_comment(line): return line.startswith("//") def is_cf_data(line): return line.startswith("// clang-format:") def get_cf_data(line): line = line[16:].lstrip() fields = [t.strip() for t in line.split(":", 1)] if len(fields) == 1: fields = [fields[0], cf_value(fields[0])] else: fields[1] = yaml.safe_load(fields[1]) return dict([fields]) with open(file) as istr: content = [line.rstrip() for line in istr.readlines()] # retrieve title assert is_comment(content[0]) attrs["title"] = content[0].lstrip("//").lstrip() i = 1 # eat empty lines while i < len(content) and not content[i]: i += 1 # retrieve description while i < len(content) and is_comment(content[i]): if is_cf_data(content[i]): attrs["clang_format"].update(get_cf_data(content[i])) else: attrs["description"] += content[i].lstrip("//").lstrip() + "\n" i += 1 attrs["description"] = jinja_expand(attrs["description"]) # eat empty lines while i < len(content) and not content[i]: i += 1 # retrieve code snippet while i < len(content): if not content[i].lstrip().startswith("// clang-format"): attrs["snippet"] += content[i] + "\n" i += 1 basename = osp.splitext(osp.basename(file))[0] data = clang_format for key in basename.split("."): if key in data: data = data[key] else: break else: attrs["clang_format"][basename] = data return Convention(**attrs) def load_conventions(path): with open("../../.clang-format") as istr: clang_format = yaml.safe_load(istr) assert osp.isdir(path) for file in sorted(glob.glob(path + os.sep + "*.cpp")): yield Convention.from_file(clang_format, file) def build_documentation(template_str, ostr, **kwargs): template = jinja2.Template(template_str) template.stream(**kwargs).dump(ostr) def main(): with open("README.md.jinja") as istr: template_str = istr.read() with open("README.md", "w") as ostr: build_documentation( template_str, ostr, conventions=list(load_conventions("snippets")) ) if __name__ == "__main__": main() bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/github-pandoc.css000066400000000000000000000372071434206647400276350ustar00rootroot00000000000000/*! normalize.css v2.1.3 | MIT License | git.io/normalize */ /* ========================================================================== HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined in IE 8/9. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } /** * Correct `inline-block` display not defined in IE 8/9. */ audio, canvas, video { display: inline-block; } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9. * Hide the `template` element in IE, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* ========================================================================== Base ========================================================================== */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* ========================================================================== Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background: transparent; } /** * Address `outline` inconsistency between Chrome and other browsers. */ a:focus { outline: thin dotted; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* ========================================================================== Typography ========================================================================== */ /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari 5, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9, Safari 5, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari 5 and Chrome. */ dfn { font-style: italic; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Correct font family set oddly in Safari 5 and Chrome. */ code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; } /** * Improve readability of pre-formatted text in all browsers. */ pre { white-space: pre-wrap; } /** * Set consistent quote types. */ q { quotes: "\201C" "\201D" "\2018" "\2019"; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* ========================================================================== Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9. */ img { border: 0; } /** * Correct overflow displayed oddly in IE 9. */ svg:not(:root) { overflow: hidden; } /* ========================================================================== Figures ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari 5. */ figure { margin: 0; } /* ========================================================================== Forms ========================================================================== */ /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * 1. Correct font family not being inherited in all browsers. * 2. Correct font size not being inherited in all browsers. * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */ button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ button, input { line-height: normal; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. * Correct `select` style inheritance in Firefox 4+ and Opera. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari 5 and Chrome * on OS X. */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * 1. Remove default vertical scrollbar in IE 8/9. * 2. Improve readability and alignment in all browsers. */ textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ } /* ========================================================================== Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } .go-top { position: fixed; bottom: 2em; right: 2em; text-decoration: none; background-color: #E0E0E0; font-size: 12px; padding: 1em; display: inline; } /* Github css */ html,body{ margin: auto; padding-right: 1em; padding-left: 1em; max-width: 53em; color:black;}*:not('#mkdbuttons'){margin:0;padding:0}body{font:13.34px helvetica,arial,freesans,clean,sans-serif;-webkit-font-smoothing:subpixel-antialiased;line-height:1.4;padding:3px;background:#fff;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px}p{margin:1em 0}a{color:#4183c4;text-decoration:none}body{background-color:#fff;padding:30px;margin:15px;font-size:14px;line-height:1.6}body>*:first-child{margin-top:0!important}body>*:last-child{margin-bottom:0!important}@media screen{body{box-shadow:0 0 0 1px #cacaca,0 0 0 4px #eee}}h1,h2,h3,h4,h5,h6{margin:20px 0 10px;padding:0;font-weight:bold;-webkit-font-smoothing:subpixel-antialiased;cursor:text}h1{font-size:28px;color:#000}h2{font-size:24px;border-bottom:1px solid #ccc;color:#000}h3{font-size:18px;color:#333}h4{font-size:16px;color:#333}h5{font-size:14px;color:#333}h6{color:#777;font-size:14px}p,blockquote,table,pre{margin:15px 0}ul{padding-left:30px}ol{padding-left:30px}ol li ul:first-of-type{margin-top:0}hr{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;border:0 none;color:#ccc;height:4px;padding:0}body>h2:first-child{margin-top:0;padding-top:0}body>h1:first-child{margin-top:0;padding-top:0}body>h1:first-child+h2{margin-top:0;padding-top:0}body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child{margin-top:0;padding-top:0}a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6{margin-top:0;padding-top:0}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p,ul li>:first-child,ol li>:first-child{margin-top:0}dl{padding:0}dl dt{font-size:14px;font-weight:bold;font-style:italic;padding:0;margin:15px 0 5px}dl dt:first-child{padding:0}dl dt>:first-child{margin-top:0}dl dt>:last-child{margin-bottom:0}dl dd{margin:0 0 15px;padding:0 15px}dl dd>:first-child{margin-top:0}dl dd>:last-child{margin-bottom:0}blockquote{border-left:4px solid #DDD;padding:0 15px;color:#777}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}table{border-collapse:collapse;border-spacing:0;font-size:100%;font:inherit}table th{font-weight:bold;border:1px solid #ccc;padding:6px 13px}table td{border:1px solid #ccc;padding:6px 13px}table tr{border-top:1px solid #ccc;background-color:#fff}table tr:nth-child(2n){background-color:#f8f8f8}img{max-width:100%}code,tt{margin:0 2px;padding:0 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px;font-family:Consolas,'Liberation Mono',Courier,monospace;font-size:12px;color:#333}pre>code{margin:0;padding:0;white-space:pre;border:0;background:transparent}.highlight pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre code,pre tt{background-color:transparent;border:0}.poetry pre{font-family:Georgia,Garamond,serif!important;font-style:italic;font-size:110%!important;line-height:1.6em;display:block;margin-left:1em}.poetry pre code{font-family:Georgia,Garamond,serif!important;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;white-space:pre-wrap}sup,sub,a.footnote{font-size:1.4ex;height:0;line-height:1;vertical-align:super;position:relative}sub{vertical-align:sub;top:-1px}@media print{body{background:#fff}img,pre,blockquote,table,figure{page-break-inside:avoid}body{background:#fff;border:0}code{background-color:#fff;color:#333!important;padding:0 .2em;border:1px solid #dedede}pre{background:#fff}pre code{background-color:white!important;overflow:visible}}@media screen{body.inverted{color:#eee!important;border-color:#555;box-shadow:none}.inverted body,.inverted hr .inverted p,.inverted td,.inverted li,.inverted h1,.inverted h2,.inverted h3,.inverted h4,.inverted h5,.inverted h6,.inverted th,.inverted .math,.inverted caption,.inverted dd,.inverted dt,.inverted blockquote{color:#eee!important;border-color:#555;box-shadow:none}.inverted td,.inverted th{background:#333}.inverted h2{border-color:#555}.inverted hr{border-color:#777;border-width:1px!important}::selection{background:rgba(157,193,200,0.5)}h1::selection{background-color:rgba(45,156,208,0.3)}h2::selection{background-color:rgba(90,182,224,0.3)}h3::selection,h4::selection,h5::selection,h6::selection,li::selection,ol::selection{background-color:rgba(133,201,232,0.3)}code::selection{background-color:rgba(0,0,0,0.7);color:#eee}code span::selection{background-color:rgba(0,0,0,0.7)!important;color:#eee!important}a::selection{background-color:rgba(255,230,102,0.2)}.inverted a::selection{background-color:rgba(255,230,102,0.6)}td::selection,th::selection,caption::selection{background-color:rgba(180,237,95,0.5)}.inverted{background:#0b2531;background:#252a2a}.inverted body{background:#252a2a}.inverted a{color:#acd1d5}}.highlight .c{color:#998;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .k,.highlight .o{font-weight:bold}.highlight .cm{color:#998;font-style:italic}.highlight .cp{color:#999;font-weight:bold}.highlight .c1{color:#998;font-style:italic}.highlight .cs{color:#999;font-weight:bold;font-style:italic}.highlight .gd{color:#000;background-color:#fdd}.highlight .gd .x{color:#000;background-color:#faa}.highlight .ge{font-style:italic}.highlight .gr{color:#a00}.highlight .gh{color:#999}.highlight .gi{color:#000;background-color:#dfd}.highlight .gi .x{color:#000;background-color:#afa}.highlight .go{color:#888}.highlight .gp{color:#555}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#a00}.highlight .kc,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr{font-weight:bold}.highlight .kt{color:#458;font-weight:bold}.highlight .m{color:#099}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .nb{color:#0086b3}.highlight .nc{color:#458;font-weight:bold}.highlight .no{color:#008080}.highlight .ni{color:#800080}.highlight .ne,.highlight .nf{color:#900;font-weight:bold}.highlight .nn{color:#555}.highlight .nt{color:#000080}.highlight .nv{color:#008080}.highlight .ow{font-weight:bold}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#099}.highlight .sb,.highlight .sc,.highlight .sd,.highlight .s2,.highlight .se,.highlight .sh,.highlight .si,.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .bp{color:#999}.highlight .vc,.highlight .vg,.highlight .vi{color:#008080}.highlight .il{color:#099}.highlight .gc{color:#999;background-color:#eaf2f5}.type-csharp .highlight .k,.type-csharp .highlight .kt{color:#00F}.type-csharp .highlight .nf{color:#000;font-weight:normal}.type-csharp .highlight .nc{color:#2b91af}.type-csharp .highlight .nn{color:#000}.type-csharp .highlight .s,.type-csharp .highlight .sc{color:#a31515} bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/000077500000000000000000000000001434206647400262335ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/AccessModifierOffset.cpp000066400000000000000000000002501434206647400327630ustar00rootroot00000000000000// Access Modifiers Indentation inside `struct` and `class` // access modifiers may be indented this way: class Foo { public: Foo(); private: int data; }; bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/AlignAfterOpenBracket.cpp000066400000000000000000000004451434206647400330740ustar00rootroot00000000000000// Horizontally align arguments after an open bracket thisIsAVeryLongMethodName(thisIsTheFirstArgument, andHereIsTheSecondArgument, theThirdArgument, theFourthArgument, theFifthArgument); AlignArrayOfStructures.cpp000066400000000000000000000001761434206647400333060ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Do not align fields in array initialization struct test demo[] = {{56, 23, "hello"}, {-1, 93463, "world"}, {7, 5, "!!"}}; AlignConsecutiveAssignments.cpp000066400000000000000000000001221434206647400343310ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Do not align consecutive assignments int aaaa = 12; int b = 23; int ccc = 23; AlignConsecutiveDeclarations.cpp000066400000000000000000000001351434206647400344520ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Do not align consecutive declarations int aaaa = 12; float b = 23; std::string ccc = 23; AlignEscapedNewlinesLeft.cpp000066400000000000000000000004001434206647400335110ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Align escaped newlines as far left as possible #define MY_MACRO \ blablablablablablablabla blablablablablablablablablablablablablabla \ blablablablablablablablablablablablablablablabla bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/AlignOperands.cpp000066400000000000000000000002441434206647400314650ustar00rootroot00000000000000// Horizontally align operands of binary and ternary expressions. // // clang-format off int aaa = bbbbbbbbbbbbbbb + ccccccccccccccc; // clang-format on bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/AlignTrailingComments.cpp000066400000000000000000000001551434206647400331720ustar00rootroot00000000000000// Align trailing comments, prefixed with 2 spaces int a; // My comment for a int b = 2; // comment b AllowShortBlocksOnASingleLine.cpp000066400000000000000000000001101434206647400344540ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Never contract short blocks on a single line if (a) { return; } AllowShortCaseLabelsOnASingleLine.cpp000066400000000000000000000002711434206647400352450ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Never contract case labels on a single line switch (a) { case 1: x = 1; break; case 2: return; // clang-format off case 3: return; // NOT THIS // clang-format on } AllowShortFunctionsOnASingleLine.cpp000066400000000000000000000001671434206647400352230ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Only empty methods can be put in a single line class Foo { void f() { foo(); } void f() {} }; AllowShortIfStatementsOnASingleLine.cpp000066400000000000000000000005251434206647400356570ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Conditions // Break even for simple `if` statement and always put body inside braces. // // See clang-tidy check // [readability-braces-around-statements](http://releases.llvm.org/7.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability-braces-around-statements.html#readability-braces-around-statements) if (a) { return; } AllowShortLoopsOnASingleLine.cpp000066400000000000000000000005651434206647400343510ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Loop statements // Break even for simple `for` and `while` statements and always put bodies // inside braces. // // See clang-tidy check // [readability-braces-around-statements](http://releases.llvm.org/7.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability-braces-around-statements.html#readability-braces-around-statements) while (true) { continue; } AlwaysBreakBeforeMultilineStrings.cpp000066400000000000000000000001011434206647400354350ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Break before multiline strings AAAA = "bbbb" "cccc"; AlwaysBreakTemplateDeclarations.cpp000066400000000000000000000002341434206647400351110ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Break after template declaration template T foo() {} template T foo(int aaaaaaaaaaaaaaaaaaaaa, int bbbbbbbbbbbbbbbbbbbbb) {} BraceWrapping.AfterClass.cpp000066400000000000000000000001511434206647400334270ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Opening brace is on same line than struct/class definition class Foo {}; struct Bar { int i; }; BraceWrapping.AfterControlStatement.cpp000066400000000000000000000001531434206647400356710ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace is on same line than control statements if (foo()) { } else { } for (int i = 0; i < 10; ++i) { } BraceWrapping.AfterEnum.cpp000066400000000000000000000001101434206647400332610ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace on same line than *enum* definition enum X : int { A, B, C }; BraceWrapping.AfterExternBlock.cpp000066400000000000000000000001151434206647400346020ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace on same line than `extern "C"` directive extern "C" { int foo(); } BraceWrapping.AfterFunction.cpp000066400000000000000000000001311434206647400341450ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace on same line than *function* definitions void foo() { bar(); bar2(); } BraceWrapping.AfterNamespace.cpp000066400000000000000000000001451434206647400342610ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace on same line than *namespace* definition namespace { int foo(); int bar(); } // namespace BraceWrapping.AfterStruct.cpp000066400000000000000000000001141434206647400336450ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace on same line than *struct* definitions struct foo { int x; }; BraceWrapping.AfterUnion.cpp000066400000000000000000000001111434206647400334460ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Brace on same line than *union* definitions union foo { int x; } BraceWrapping.BeforeCatch.cpp000066400000000000000000000001131434206647400335430ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Do not break before *catch* directive try { foo(); } catch () { } BraceWrapping.BeforeElse.cpp000066400000000000000000000000751434206647400334200ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Do not break before *else* block if (foo()) { } else { } BreakBeforeBinaryOperators.cpp000066400000000000000000000007261434206647400341000ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Do not break before binary operators // When a break is required, the binary operator is the last // token of the line. LooooooooooongType loooooooooooooooooooooongVariable = someLooooooooooooooooongFunction(); bool value = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > ccccccccccccccccccccccccccccccccccccccccc; BreakBeforeConceptDeclarations.cpp000066400000000000000000000002511434206647400346720ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// New line before C++20 `concept` directive template concept Hashable = requires(T a) { { std::hash{}(a) } -> std::convertible_to; }; BreakBeforeTernaryOperators.cpp000066400000000000000000000003201434206647400342660ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// break before long ternary operators veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongDescription ? firstValue : SecondValueVeryVeryVeryVeryLong; bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/Cpp11BracedListStyle.cpp000066400000000000000000000002171434206647400326010ustar00rootroot00000000000000// No space inside C++11 braced lists vector x{1, 2, 3, 4}; vector x{{}, {}, {}, {}}; f(MyMap[{composite, key}]) new int[3]{1, 2, 3}; EmptyLineBeforeAccessModifier.cpp000066400000000000000000000002651434206647400345150ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// No line break restriction after access modifiers struct foo { private: int i; protected: int j; /* comment */ public: foo() {} private: protected: };bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/FixNamespaceComment.cpp000066400000000000000000000000761434206647400326300ustar00rootroot00000000000000// Recall namespace when closing it namespace bbp { foo(); } bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/IndentPPDirectives.cpp000066400000000000000000000001451434206647400324420ustar00rootroot00000000000000// Do no indent preprocesor directives #if FOO #if BAR #include #endif // BAR #endif // FOO bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/IndentRequires.cpp000066400000000000000000000002651434206647400317030ustar00rootroot00000000000000// line break before C++20 `requires` directive template requires Iterator // clang-format off void sort(It begin, It end) { //.... } // clang-format on bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/IndentWidth.cpp000066400000000000000000000001551434206647400311610ustar00rootroot00000000000000// Use 4 columns for indentation void f() { someFunction(); if (true, false) { f(); } } bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/LambdaBodyIndentation.cpp000066400000000000000000000003561434206647400331360ustar00rootroot00000000000000// Indent lambda body based on the signature of the lambda callingSomeLongLongLongLongLongLongLongLongLongLongMethod( [](SomeReallyLongLambdaSignatureArgument foo, SomeReallyLongLambdaSignatureArgument bar) { return; }); bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/NamespaceIndentation.cpp000066400000000000000000000001621434206647400330270ustar00rootroot00000000000000// Do not indent when entering a *namespace* namespace out { int i; namespace in { int i; } } // namespace out PackConstructorInitializers.cpp000066400000000000000000000002221434206647400343670ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// The pack constructor initializers style to use. Constructor() : aaaaaaaaaaaaaaaaaaaa() , bbbbbbbbbbbbbbbbbbbb() , ddddddddddddd() bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/PointerAlignment.cpp000066400000000000000000000001101434206647400322060ustar00rootroot00000000000000// pointer and reference are part of the type int* a; int& b = getB(); bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/QualifierAlignment.cpp000066400000000000000000000001521434206647400325150ustar00rootroot00000000000000// Different ways to arrange specifiers and qualifiers (e.g. const/volatile). const int a; const int* a; SeparateDefinitionBlocks.cpp000066400000000000000000000010341434206647400335710ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// Specifies the use of empty lines to separate definition blocks, including classes, structs, // enums, and functions. #include struct Foo { int a, b, c; }; namespace Ns { class Bar { public: struct Foobar { int a; int b; }; private: int t; int method1() { // ... } enum List { ITEM1, ITEM2 }; template int method2(T x) { // ... } int i, j, k; int method3(int par) { // ... } }; class C {}; } // namespace Ns bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/SortIncludes.cpp000066400000000000000000000003361434206647400313570ustar00rootroot00000000000000// Sort includes in different sections // * System headers // * Dependencies // * Current project #include #include #include #include #include "mylib/myclass.h" bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets/SpaceAfterBulk.cpp000066400000000000000000000037421434206647400316000ustar00rootroot00000000000000// Spacing related specifications // clang-format: SpaceAfterCStyleCast // clang-format: SpaceAfterTemplateKeyword // clang-format: SpaceBeforeAssignmentOperators // clang-format: SpaceBeforeCpp11BracedList // clang-format: SpaceBeforeCtorInitializerColon // clang-format: SpaceBeforeInheritanceColon // clang-format: SpaceBeforeParens // clang-format: SpaceBeforeRangeBasedForLoopColon // clang-format: SpaceInEmptyParentheses // clang-format: SpacesBeforeTrailingComments // clang-format: SpacesInAngles // clang-format: SpacesInCStyleCastParentheses // clang-format: SpacesInContainerLiterals // clang-format: SpacesInParentheses // clang-format: SpacesInSquareBrackets // clang-format: TabWidth // clang-format: UseTab // * Use {{ cf["TabWidth"]}} tab size // * Never use tab // a space is inserted after C style casts (int) i; // a space is inserted after the template keyword template void foo(); // a space a space before assignment operators int a = 5; a += 42; // do not put spaces before C++11 braced list Foo foo{bar}; Foo{}; vector{1, 2, 3}; new int[3]{1, 2, 3}; // do not add space before constructor initializer colon Foo::Foo() : a(a) {} // do not add space before inheritance colon class Foo: Bar { } // put a space before opening parentheses after control statement // (for/if/while...) void f() { if (true) { f(); } } // do not add space before ranged-based for loop colon for (auto v: values) { } // do not have space between `()` void f() { int x[] = {foo(), bar()}; if (true) { f(); } } // put {{ cf['SpacesBeforeTrailingComments']}} spaces before trailing comment void f() { if (true) { // foo1 f(); // bar } // foo } // do not add space after `<` and before `>` static_cast(arg); std::function fct; // do not add space inside C-style cast x = (int32) y; // do not add space insert `(` and before `)` t f(Deleted&) & = delete; // do not add space after `[` and before `]` int a[5]; one-parameter-per-line-if-they-do-no-fit.cpp000066400000000000000000000006711434206647400362710ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/formatting/snippets// One parameter per line if they don't all fit // clang-format: AllowAllParametersOfDeclarationOnNextLine: false // clang-format: BinPackParameters: False HighFive::DataSet dump(HighFive::File& file, const std::string& path, const std::vector& data, HighFive::Mode mode = HighFive::Mode::Create); void andALongFunctionName(the_long_parameter1, the_long_parameter2); bluebrain-hpc-coding-conventions-1.0.0+git20221201/cpp/lib.py000066400000000000000000001405201434206647400233360ustar00rootroot00000000000000""" Main module of this project """ import abc import argparse import collections import copy from fnmatch import fnmatch import functools import logging import operator import os import os.path from pathlib import Path import re import shlex import shutil 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]?)" def forget_python_pkg(package): """ exclude from PYTHONPATH environment variable and sys.path trace of the specified package Args: package: name of the Python package to exclude """ import pkg_resources try: dist = pkg_resources.get_distribution(package) except pkg_resources.DistributionNotFound: return PYTHONPATH = os.environ.get("PYTHONPATH") if PYTHONPATH is not None and dist.location in PYTHONPATH: logging.debug( "Remove incompatible version of %s in PYTHONPATH: %s", package, dist.location, ) os.environ["PYTHONPATH"] = PYTHONPATH.replace(dist.location, "") try: sys.path.remove(dist.location) except ValueError: pass @functools.lru_cache() def source_dir(): """ Returns: absolute path to the root of a git repository. The parent repository if hpc-coding-conventions is used as a git module, this repository otherwise. Implementation note: alternative is to use "git rev-parse --show-superproject-working-tree" but this solution requires git 2.13 or higher """ def git_rev_parse(*args, **kwargs): cmd = list((which("git"), "rev-parse") + args) log_command(cmd, level=logging.DEBUG) output = subprocess.check_output(cmd, **kwargs).decode("utf-8").strip() return Path(output).resolve() git_dir = Path(git_rev_parse("--git-dir", cwd=THIS_SCRIPT_DIR)) if git_dir.parent not in THIS_SCRIPT_DIR.parents: # This project is used as a git module module_dir = git_rev_parse("--show-toplevel", cwd=THIS_SCRIPT_DIR) git_dir = git_rev_parse("--git-dir", cwd=os.path.dirname(module_dir)) try: Path.cwd().relative_to(module_dir) # cwd is inside hpc-coding-conventions module. # assume this is for its development. return module_dir except ValueError: pass return git_dir.parent def merge_yaml_files(*files, **kwargs): """Merge YAML files. The last argument is the destination file""" try: import yaml # pylint: disable=C0415 except ImportError: logging.error( "Cannot find Python yaml module, which is needed to merge YAML files." ) sys.exit(1) succeeded = True transformers = kwargs.get("transformers") or {} out = files[-1] ins = files[:-1] outdated = not os.path.exists(out) or os.path.getmtime(out) < max( (os.path.getmtime(f) for f in ins) ) if outdated: data = {} for file in ins: with open(file, encoding="utf-8") as istr: content = yaml.safe_load(istr) if not isinstance(content, dict): logging.error( "while reading YAML file %s: expected dictionary but got %s", file, type(content).__name__, ) succeeded = False continue for key, value in content.items(): transform_func = transformers.get(key) if transform_func: data[key] = transform_func(data.get(key), value) else: data[key] = value logging.info("writing file %s", out) if succeeded: with open(out, "w", encoding="utf-8") as ostr: yaml.dump(data, ostr, default_flow_style=False) else: logging.info("file %s is up to date, nothing to do.", out) return succeeded def is_file_tracked(file: str, cwd=None) -> bool: """ Args: file: relative path to file within a git repository cwd: optional path to change before executing the command Returns: true if the given file is tracked by a git repository, false otherwise """ ret = subprocess.call( [which("git"), "ls-files", "--error-unmatch", file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd, ) return ret == 0 def chunkify(seq, chunk_size=1): """ Split a sequence of elements into chunks of a given size Args: seq: sequence of elements chunk_size: maximum chunk size Returns: a generator of sequence """ for chunk_start_idx in range(0, len(seq), chunk_size): yield seq[chunk_start_idx : chunk_start_idx + chunk_size] def log_command(*commands, logger=None, level=logging.DEBUG): """ Utility function to report to the logger a shell command about to be executed. """ if len(commands) == 1: message = " ".join([shlex.quote(e) for e in commands[0]]) else: message = " " + " |\n ".join([" ".join(cmd) for cmd in commands]) logger = logging if logger is None else logger logger.log(level, message) class cached_property: # pylint: disable=C0103 """ A property that is only computed once per instance and then replaces itself with an ordinary attribute. Deleting the attribute resets the property. Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 """ # noqa def __init__(self, func): self.__doc__ = getattr(func, "__doc__") self.func = func def __get__(self, obj, cls): if obj is None: return self value = obj.__dict__[self.func.__name__] = self.func(obj) return value @functools.lru_cache() def which(program: str, paths=None): """ Find the first location of a program in PATH environment variable. Args: program: program to look for i.e "clang-format" paths: optional list of paths where to look for the program. default is the PATH environment variable. Return: Path `str` of the executable if found, `None otherwise """ env_variable = re.sub("[^0-9a-zA-Z_]", "_", program).upper() program = Path(os.environ.get(env_variable, program)) if program.is_absolute(): return str(program) paths = paths or os.getenv("PATH").split(os.path.pathsep) for path in paths: abs_path = Path(path).joinpath(program) if abs_path.exists() and os.access(abs_path, os.X_OK): return str(abs_path) def where(program: str, regex=None, exclude_regex=None, paths=None): """ Find all the locations of a program in PATH environment variable. Args: program: program to look for i.e "clang-format" regex: optional regular expression for alternative program names i.e re.compile("clang-format-.*"]) exclude_regex: optional regular expression to exclude alternate program names matching `regex`. paths: optional list of paths where to look for the program. default is the PATH environment variable. """ env_variable = re.sub("[^0-9a-zA-Z_]", "_", program).upper() program = os.environ.get(env_variable, program) if os.path.isabs(program): yield program return paths = paths or os.getenv("PATH").split(os.path.pathsep) for path in paths: abs_path = os.path.join(path, program) if os.path.exists(abs_path) and os.access(abs_path, os.X_OK): yield abs_path if regex: for file_name in os.listdir(path): file_path = os.path.join(path, file_name) if regex.match(file_name) and os.access(file_path, os.X_OK): if not exclude_regex or not exclude_regex.match(file_name): yield file_path class BBPVEnv: """ Wrapper for the Python virtual environment used by this module. """ def __init__(self, path: str): """ Args: path: path to virtual environment """ 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: """ Return: Path to the virtual environment """ return self._path @property def bin_dir(self) -> str: """ Return: Path to the /bin directory of the virtual environment """ return self.path.joinpath("bin") @property def interpreter(self) -> str: """ Return: Path to the Python interpreter of the virtual environment """ return self.bin_dir.joinpath("python") def pip_cmd(self, *args) -> List[str]: """ Execute a pip command Arguments: args: the command Return: array containing the shell command to execute """ return [str(self.interpreter), "-m", "pip"] + list(args) def pip_install(self, requirement, upgrade=False): """ Args: requirement: list of (str or Requirement) to install. Possible inputs: - pkg_resources.Requirement.parse("foo==1.0") - "foo==1.0" - [pkg_resources.Requirement.parse("foo==1.0"), "bar==1.0"] """ cmd = self.pip_cmd("install") if logging.getLogger().level != logging.DEBUG: cmd += ["-q"] if upgrade: cmd += ["--upgrade"] if not isinstance(requirement, list): requirement = [requirement] requirement = [str(req) for req in requirement] cmd += requirement log_command(cmd) subprocess.check_call(cmd) def ensure_pip(self): def py_call(*cmd, check=False): cmd = [str(self.interpreter)] + list(cmd) log_command(cmd) kwargs = {} if logging.getLogger().level != logging.DEBUG: kwargs.update(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) call = subprocess.call if check: call = subprocess.check_call return call(cmd, **kwargs) == 0 if not py_call("-m", "pip", "--version"): py_call("-m", "ensurepip", "--default-pip") if not py_call("-m", "pip", "--version"): with tempfile.NamedTemporaryFile(suffix=".py") as get_pip_script: url = "https://bootstrap.pypa.io/get-pip.py" urllib.request.urlretrieve(url, get_pip_script.name) py_call(get_pip_script.name) py_call("-m", "pip", "--version", check=True) @property def in_venv(self) -> bool: """ Return: True if the current process is run by the Python interpreter of this virtual environment, False otherwise. """ venv_python_interpreter_pattern = self.bin_dir.resolve().joinpath("python*") return fnmatch(sys.executable, venv_python_interpreter_pattern) def restart_in_venv(self, reason=""): """ Replace the current process by the execution of the same command but with the Python interpreter of this virtual environment. This function doesn't return since the process is entirely replaced. Args: reason: optional log message """ if not self.in_venv: if not self.path.is_dir(): builder = venv.EnvBuilder(symlinks=True, with_pip=True) logging.debug("Creating virtual environment %s", self.path) builder.create(str(self.path)) self.ensure_pip() self.pip_install("pip", upgrade=True) logging.debug("Restarting process within own Python virtualenv %s", reason) os.execv(self.interpreter, [self.interpreter] + sys.argv) def is_requirement_met(self, requirement) -> bool: """ Args: requirement: str of pkg_resources.Requirement Return: True if the current Python environment fulfills the given requirement, False otherwise """ try: import pkg_resources 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): return False def ensure_requirement(self, requirement, restart=True): """ Ensure the Python script is running in an environment fulfilling the given requirement. If requirement already met Then do nothing. Else If not running in the virtual environment Then Create the virtual environment If it doesn't exist Rerun the Python script within the virtual environment Else If requirement is not met Then install the package Return the Python script only if "restart" is True Args: requirement: str of pkg_resources.Requirement restart: whether this process may restart after the requirement is installed. """ if not self.is_requirement_met(requirement): self._install_requirement(requirement, restart) def _install_requirement(self, requirement, restart=False): """ Force installation of the requirement in the virtual environment Args: requirement: str of pkg_resources.Requirement restart: whether this process may restart after the requirement is installed. """ if self.in_venv: self.pip_install(requirement) if restart: self.restart_in_venv( f"to take into account installed requirement {requirement}" ) else: self.restart_in_venv(f"because requirement {requirement} is not met") class Tool(metaclass=abc.ABCMeta): """ Wrapper class over a third-party tool like clang-format or pre-commit """ LOG_JOBS = True def __init__(self, config: dict, user_config: dict): """ Args: config: describes how to tool works internally user_config: describes how to use it """ self._config = config self._user_config = user_config @staticmethod @functools.lru_cache() def job_logger() -> logging.Logger: """ Return: `logging.getLogger` instance use to report the commands executed by a tool. """ logger = logging.getLogger("job") logger.propagate = False logger.setLevel(logging.INFO if Tool.LOG_JOBS else logging.WARN) handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter("%(message)s") handler.setFormatter(formatter) logger.addHandler(handler) return logger @property def name(self) -> str: """ Return: the tool name """ return self._config["name"] def __str__(self) -> str: """ Return: the tool name """ return self.name @property def config(self) -> dict: """ Return: tool internal configuration """ return self._config @property def user_config(self) -> dict: """ Return: tool user configuration """ return self._user_config @property def path(self) -> str: """ Return: absolute path to the tool """ path = self._user_config.get("path") if path is None: path = getattr(self, "_path", None) if path is None: raise RuntimeError( f"{self.__class__}.configure should be called before" ) return path @cached_property def requirement(self): """ Return: `pkg_resources.Requirement` of the tool if it is a Python package, `None` otherwise """ pip_pkg = self.config["capabilities"].pip_pkg assert isinstance(pip_pkg, (str, bool)) if isinstance(pip_pkg, str): name = pip_pkg else: name = self.name import pkg_resources return pkg_resources.Requirement.parse(f"{name} {self.user_config['version']}") @abc.abstractmethod def configure(self): """ Find the tool on the system and setup its configuration """ @classmethod def cli_options(cls, task: str, parser: argparse.ArgumentParser): """ Hook function to add options to the CLI parser of a task Args: task: task name parser: argument parser to complete """ def cmd_opts(self, task: str, dry_run=False, **kwargs): """ Args: task: task name dry_run: whether the tool should actually not perform the task kwargs: additional options given in CLI Return: The command line options to pass to the tool """ task_config = self.config["provides"][task] if dry_run: try: return task_config["dry_run_cmd_opts"] except KeyError as exc: raise Exception(f"{self}: error: dry-run: unsupported option") from exc else: return task_config["cmd_opts"] @cached_property def bbp_config_file(self): """ Returns: absolute path `pathlib.Path` to the proper config file in the hpc-coding-conventions project. It depends on the version of the tool """ version = self._version major_ver, _ = self._version.split(".", 1) config_lib_dir = THIS_SCRIPT_DIR test_ver = int(major_ver) while test_ver >= 0: candidate = config_lib_dir.joinpath(f"{self}-{test_ver}") if candidate.exists(): return candidate test_ver -= 1 candidate = config_lib_dir.joinpath( self.config["config_file"].format(self=self)[1:] ) if candidate.exists(): return candidate raise RuntimeError( f"Could not find appropriate config file for {self} {version}" ) def prepare_config(self): """ Setup the configuration file of the tool. For instance this function will create the proper ".clang-format" file at the root of a C++ project. """ config_fname = self.config["config_file"].format(self=self) config_f = source_dir().joinpath(config_fname) if not is_file_tracked(config_fname, cwd=source_dir()) or not config_f.exists(): custom_conf_f_name = self.config["custom_config_file"].format(self=self) custom_config_f = source_dir().joinpath(custom_conf_f_name) if custom_config_f.exists(): build_file = False if not config_f.exists(): build_file = True else: config_f_mtime = os.path.getmtime(config_f) deps = [config_f, custom_config_f] if config_f_mtime < max((os.path.getmtime(f) for f in deps)): build_file = True if build_file: logging.info("Merging custom %s YAML changes ", self) merge_yaml_files( self.bbp_config_file, custom_config_f, config_f, transformers=self.config.get("config_yaml_transformers"), ) else: logging.info( "%s config is up to date with BBP %s" " and custom %s config files", self, self.bbp_config_file.name, custom_conf_f_name, ) else: bbp_config_f = self.bbp_config_file bbp_config_base = bbp_config_f.name if not config_f.exists() or os.path.getmtime( config_f ) < os.path.getmtime(bbp_config_f): logging.info( "Copying BBP config %s to %s", bbp_config_base, source_dir() ) shutil.copy(bbp_config_f, config_f) else: logging.info( "%s config is up to date with BBP %s config", self, bbp_config_base, ) else: logging.info("%s config is tracked by git, nothing to do.", self) def run(self, task: str, *files, dry_run=False, **kwargs): """ Execute a task on a set of files Args: task: task name files: list of files on which to execute the task dry_run: if True, the tool should not actually perform the task, just report issues. kwargs: additional task options Return: number of failed tasks """ max_num_files = self.config["capabilities"].cli_max_num_files num_errors = 0 for files_chunk in chunkify(files, max_num_files): num_errors += self._run_chunk(task, *files_chunk, dry_run=dry_run, **kwargs) return num_errors def _run_chunk(self, task: str, *files, cwd=None, **kwargs): cmd = [self.path] user_option = self.user_config.get("option") or [] if isinstance(user_option, str): user_option = [user_option] cmd += user_option dry_run = kwargs.get("dry_run", False) task_config = self.config["provides"][task] call_kwargs = dict(cwd=cwd) if logging.getLogger().level > logging.INFO: call_kwargs.update(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) cmd += self.cmd_opts(task, **kwargs) + list(files) log_command(cmd, logger=self.job_logger(), level=logging.INFO) status = subprocess.call(cmd, **call_kwargs) if dry_run and status != 0: lang_str = "/".join(task_config["languages"]) logging.error( "%s | failed checks (one or more): %s", lang_str, " ".join(files), ) return 1 if status != 0 else 0 def accepts_file(self, file: str) -> bool: """ Args: file: path to file Return: True if the tool accepts the file, False otherwise """ for regexp in self.user_config.get("exclude", {}).get("match", []): if regexp.match(file): return False for regexp in self.user_config.get("include", {}).get("match", []): if regexp.match(file): return True return False class ExecutableTool(Tool): """ Specialization of `Tool` for utilities that can be called from the command line """ def configure(self): if self.user_config.get("path") is None: # path is not provided by the user conf # let's find it! try: if self.user_config.get("requirements", []): raise FileNotFoundError( f"Force usage of custom virtualenv for {self} " "since it requires extra Python packages" ) self._path, self._version = self.find_tool_in_path() except FileNotFoundError as e: if self.requirement: forget_python_pkg(self.requirement.name) BBPProject.virtualenv().ensure_requirement(self.requirement) self._path, self._version = self.find_tool_in_path( [BBPProject.virtualenv().bin_dir] ) else: raise e logging.info( f"{self}: found %s (%s) matching requirement %s", self._path, self._version, self.requirement, ) else: self._version = self.find_version(self.path) # Install additional requirements for req in self.user_config.get("requirements", []): BBPProject.virtualenv().ensure_requirement(req, restart=False) def find_tool_in_path(self, search_paths=None): paths = list( where(self.name, self.names_regex, self.names_exclude_regex, search_paths) ) 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(sorted(paths, key=lambda tup: tup[1])) # sort by version if not paths: raise FileNotFoundError( f"Could not find a version of {self} " + f"matching the requirement {self.requirement}\nCandidates are:\n" + "\n".join(f"{tpl[1]}: {tpl[0]}" for tpl in all_paths) ) return paths[-1] @cached_property def names_regex(self): """ Return: Optional regular expression to look for the tool in PATH environment variable. """ pattern = self.config.get("names_regex") return re.compile(pattern) if pattern else None @cached_property def names_exclude_regex(self): """ Return: Optional regular expression to exclude executables for the tool in PATH. """ pattern = self.config.get("names_exclude_regex") return re.compile(pattern) if pattern else None def find_version(self, path: str) -> str: """ Returns: extract version of given utility, i.e "13.0.0" """ if self.config["capabilities"].pip_pkg: # This tool is a Python package venv = BBPProject.virtualenv() if venv.in_venv and venv.bin_dir in Path(path).parents: # `path` belongs to the ClangFormat Python package # available in the current Python environment. # Let's query the environment instead of parsing # the output of `clang-format --version` 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 cmd = [path] + self._config["version_opt"] log_command(cmd) proc = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False, encoding="utf-8", ) output = proc.stdout.strip() match = re.search(self._config["version_re"], output) if match: ver = match.group(1) return ver raise RuntimeError( f"Could not extract version of program {path} from output: '{output}'" ) class ClangTidy(ExecutableTool): """ Specialization for ClangTidy utility in order to: - add the -p CLI option and takes it into account when executing the command - properly merge the ClangTidy checks in YAML files """ @classmethod def cli_options(cls, task: str, parser: argparse.ArgumentParser): parser.add_argument( "-p", metavar="build-path", dest="compile_commands_file", type=str, help="a Clang compile command database", ) def cmd_opts(self, task: str, compile_commands_file=None, **kwargs): compile_commands_file = compile_commands_file or self.user_config.get( "compile_commands_file" ) if compile_commands_file: return ["-p", compile_commands_file] return [] @classmethod def merge_clang_tidy_checks(cls, orig_checks, new_checks): """Merge 2 'Checks' ClangTidy configuration key values""" if orig_checks is None: return new_checks if new_checks is None: return orig_checks orig_checks = [check.strip() for check in orig_checks.split(",")] new_checks = [check.strip() for check in new_checks.split(",")] for new_check in new_checks: name_without_prefix = ( new_check[1:] if new_check.startswith("-") else new_check ) name_with_prefix = "-" + name_without_prefix orig_checks = list( check for check in orig_checks if not fnmatch(check, name_with_prefix) ) orig_checks = list( check for check in orig_checks if not fnmatch(check, name_without_prefix) ) orig_checks.append(new_check) return ",".join(c for c in orig_checks if c) class TaskDescription( collections.namedtuple( "TaskDescription", ["on_codebase", "modify_files", "description"] ) ): """ Attributes: on_codebase: True if the tool accepts files in parameter (like clang-format), False otherwise (like pre-commit) modify_files: True if the tool can modify files, False otherwise. description: Tool short description """ class ToolCapabilities( collections.namedtuple( "ToolCapabilities", ["cli_accept_dir", "cli_max_num_files", "pip_pkg"], ) ): """ Attributes: cli_accept_dir: should directories be passed directly to the tool (like black) or should the files be listed and then passed to the tool (like clang-format) cli_max_num_files: number of files that can be passed at once to the tool in CLI pip_pkg: - `True` if the tool is an installable Python package - the package name if different than the tool - `False` if the tool is not a Python package """ class BBPProject: # This is the config file of the HPC Coding Conventions project. CONFIG_FILE = "bbp-project.yaml" # This is the config file for the project being formatted. The path is # relative to root of the project using the HPC CC. USER_CONFIG_FILE = ".bbp-project.yaml" TASKS_DESCRIPTION = { "format": TaskDescription( on_codebase=True, modify_files=True, description="Code formatter utility", ), "static-analysis": TaskDescription( on_codebase=True, modify_files=False, description="Code static analyzer", ), "clang-tidy": TaskDescription( on_codebase=True, modify_files=False, description="C++ code static analyzer", ), } TOOLS_DESCRIPTION = dict( ClangFormat=dict( cls=ExecutableTool, name="clang-format", names_regex="clang-format-[-a-z0-9]+$", names_exclude_regex="clang-format-.*diff.*$", version_opt=["--version"], version_re=DEFAULT_RE_EXTRACT_VERSION, capabilities=ToolCapabilities( cli_accept_dir=False, cli_max_num_files=30, pip_pkg=True, ), provides=dict( format=dict( languages=["C++"], cmd_opts=["-i"], dry_run_cmd_opts=["--dry-run", "--ferror-limit", "1", "--Werror"], ) ), config_file=".{self}", custom_config_file=".{self}.changes", ), CMakeFormat=dict( cls=ExecutableTool, name="cmake-format", version_opt=["--version"], version_re=DEFAULT_RE_EXTRACT_VERSION, provides=dict( format=dict( languages=["CMake"], cmd_opts=["-i"], dry_run_cmd_opts=["--check"], ) ), capabilities=ToolCapabilities( cli_accept_dir=False, cli_max_num_files=30, pip_pkg="cmake-format[YAML]", ), config_file=".{self}.yaml", custom_config_file=".{self}.changes.yaml", ), ClangTidy=dict( cls=ClangTidy, name="clang-tidy", names_regex="clang-tidy-.*", version_opt=["--version"], version_re=DEFAULT_RE_EXTRACT_VERSION, provides={ "static-analysis": dict(languages=["C++"]), "clang-tidy": dict( languages=["C++"], ), }, capabilities=ToolCapabilities( cli_accept_dir=False, cli_max_num_files=30, pip_pkg=False ), config_file=".{self}", custom_config_file=".{self}.changes.yaml", config_yaml_transformers=ClangTidy.merge_clang_tidy_checks, ), Flake8=dict( cls=ExecutableTool, name="flake8", version_opt=["--version"], version_re=DEFAULT_RE_EXTRACT_VERSION, provides={ "static-analysis": dict( languages=["Python"], cmd_opts=[], ), }, capabilities=ToolCapabilities( cli_accept_dir=True, cli_max_num_files=30, pip_pkg=True, ), ), Black=dict( cls=ExecutableTool, name="black", version_opt=["--version"], version_re=DEFAULT_RE_EXTRACT_VERSION, provides=dict( format=dict( languages=["Python"], cmd_opts=[], dry_run_cmd_opts=["--check", "--diff", "--color"], ) ), capabilities=ToolCapabilities( cli_accept_dir=True, cli_max_num_files=30, pip_pkg=True, ), ) # Will come later # PreCommit=dict( # cls=ExecutableTool, # name="pre-commit", # version_opt=["--version"], # version_re="([0-9]+\\.[0-9]+\\.[0-9]+)", # provides={"setup": dict()}, # capabilities=ToolCapabilities( # ), # ), ) @classmethod @functools.lru_cache() def virtualenv(cls): return BBPVEnv(source_dir().joinpath(".bbp-project-venv")) @classmethod def task_cli(cls, task: str): """ Construct the `argparse.ArgumentParser` for the given `task` Args: task: task name to execute Return: instance of argparse.ArgumentParser """ task_config = cls.TASKS_DESCRIPTION[task] parser = argparse.ArgumentParser(description=task_config.description) ns = argparse.Namespace() if task_config.modify_files: parser.add_argument( "-n", "--dry-run", action="store_true", help="do not update the files, simply report formatting issues", ) if task_config.on_codebase: supported_languages = cls.supported_languages(task) if not supported_languages: raise RuntimeError(f"No tool supports task named '{task}'") if len(supported_languages) > 1: parser.add_argument( "--lang", action="store", dest="languages", help="only format the specified languages, " "default is: '%(default)s'", default=",".join(map(str.lower, supported_languages)), ) else: ns.languages = next(iter(supported_languages)) parser.add_argument( "sources", metavar="SRC", nargs="*", help="Files or directories. Default is the entire codebase.", ) for tool in cls.TOOLS_DESCRIPTION.values(): task_info = tool["provides"].get(task) if task_info is not None: tool["cls"].cli_options(task, parser) parser.add_argument( "-v", "--verbose", action="count", default=0, help="Give more output. Option is additive, " "and can be used up to 2 times.", ) parser.add_argument( "-q", "--quiet", action="store_true", help="Do not write the executed commands " "to standard output", ) parser.parse_args = functools.partial(parser.parse_args, namespace=ns) return parser @classmethod def run_task(cls, task: str, args=None): """ Execute a task Args: task: task name to execute args: list of CLI arguments, default is `sys.argv` Return: Number of failed jobs """ task_config = cls.TASKS_DESCRIPTION[task] parser = cls.task_cli(task) options = parser.parse_args(args=args) if options.verbose == 0: level = logging.WARN elif options.verbose == 1: level = logging.INFO else: level = logging.DEBUG logging.basicConfig(level=level, format="%(levelname)s: %(message)s") Tool.LOG_JOBS = not options.quiet project = BBPProject.from_config_files() num_errors = 0 if task_config.on_codebase: options.languages = map(str.strip, options.languages.split(",")) options.languages = map(str.lower, options.languages) options.languages = list(options.languages) num_errors = project.run_task_on_codebase(task, **vars(options)) else: num_errors = project.run_global_task(**vars(options)) if num_errors != 0: logging.error("%i jobs failed", num_errors) return num_errors def run_global_task(self, task, **kwargs): """ Execute a task that do not take files from codebase in argument Args: task: task name to execute kwargs: CLI arguments Return: Number of failed jobs """ tools = list(self.tools_for_task(task, kwargs["languages"])) for tool in tools: tool.configure() [tool.configure() for tool in tools] [tool.prepare_config() for tool in tools if "config_file" in tool.config] num_errors = 0 for tool in tools: num_errors += tool.run(task, **kwargs) return num_errors def run_task_on_codebase(self, task: str, languages=None, sources=None, **kwargs): """ Execute a task working on files of the codebase Args: task: task name to execute languages: list of desired languages to apply the task sources: subset of files or dirs to apply the task kwargs: CLI arguments Return: Number of failed jobs """ tools = list(self.tools_for_task(task, languages)) [tool.configure() for tool in tools] [tool.prepare_config() for tool in tools if "config_file" in tool.config] if not tools: logging.warning( "No tool enabled for task %s. " "Consider editing file %s" " at the root of your project", task, self.USER_CONFIG_FILE, ) return 0 src_dirs = [] src_others = [] for src in sources or []: if os.path.isdir(src): src_dirs.append(src) else: src_others.append(src) if not sources: git_ls_tree_required = True elif src_dirs: git_ls_tree_required = not functools.reduce( operator.and_, (tool.config["capabilities"].cli_accept_dir for tool in tools), ) else: git_ls_tree_required = False src_others = [os.path.join(os.getcwd(), f) for f in src_others] if git_ls_tree_required: cmd = [ which("git"), "ls-tree", "-r", "-z", "--name-only", "--full-name", "HEAD", ] cmd += src_dirs log_command(cmd) if not sources or src_dirs: git_ls_tree = subprocess.check_output(cmd).decode("utf-8").split("\0") else: git_ls_tree = [] num_errors = 0 for tool in tools: accept_dir = tool.config["capabilities"].cli_accept_dir files = copy.copy(src_others) if not sources or (src_dirs and not accept_dir): files += git_ls_tree tasks = collections.defaultdict(set) for file in files: # build the task list per tool for tool in tools: if tool.accepts_file(file): tasks[tool].add(file) for tool, tool_tasks in tasks.items(): # perform the tasks num_errors += tool.run(task, *tool_tasks, cwd=source_dir(), **kwargs) return num_errors def tools_for_task(self, task: str, languages): """ Get the tools able to process a task on given languages >>> list(BBPProject.tools_for_task("format", ["C++"])) [ClangFormat] >>> list(BBPProject.tools_for_task("format", ["C++", "CMake"])) [ClangFormat, CMake] Args: task: task name to execute languages: list of languages Return: Generator of `Tool` instances """ if languages is None: languages = BBPProject.supported_languages(task) for tool in self.tools.values(): task_config = tool.config.get("provides", {}).get(task, {}) if task_config: for lang in task_config["languages"]: if lang.lower() in languages: yield tool @classmethod def supported_languages(cls, task: str): """ Get the list of languages supported by a given task Args: task: task name to execute Return: list of programming languages supported by the provider """ languages = set() for tool_conf in cls.TOOLS_DESCRIPTION.values(): provider_conf = tool_conf.get("provides", {}).get(task) if provider_conf: provider_langs = provider_conf.get("languages") assert isinstance(provider_langs, list) for provider_lang in provider_langs: languages.add(provider_lang) return languages def __init__(self, config): self._config = config @cached_property def tools(self): tools = {} for name, tool_desc in BBPProject.TOOLS_DESCRIPTION.items(): if name in self._config["tools"]: tools[name] = tool_desc["cls"](tool_desc, self._config["tools"][name]) return tools @classmethod def default_config_file(cls): """ Return: Path to the default YAML configuration file """ return THIS_SCRIPT_DIR.parent.joinpath(cls.CONFIG_FILE) @classmethod def user_config_file(cls): """ Locate the user configuration file in project root Return: Path to the file if found, `None` otherwise """ expected_location = source_dir().joinpath(cls.USER_CONFIG_FILE) if expected_location.exists(): return expected_location @classmethod def merge_user_config(cls, conf: dict, user_conf: dict): cls._merge_user_config_global(conf, user_conf) cls._merge_user_config_tools(conf, user_conf) @classmethod def _apply_global_conf(cls, conf: dict): global_conf = conf["tools"].pop("global", None) if not global_conf: return for tool in conf["tools"]: if tool == "global": continue tool_conf = copy.copy(global_conf) tool_conf.update(conf["tools"][tool]) conf["tools"][tool] = tool_conf @classmethod def _parse_conf_regex(cls, conf: dict): for tool, tool_conf in conf["tools"].items(): if tool == "global": continue def apply_on_section(section): include = tool_conf.get(section) if include is not None and include.get("match"): tool_conf[section]["match"] = list( map(re.compile, include["match"]) ) apply_on_section("include") apply_on_section("exclude") @classmethod def _merge_user_config_tools(cls, conf: dict, user_conf: dict): tools = user_conf.get("tools", {}) assert isinstance(tools, dict) for name, config in tools.items(): enable = config.get("enable", True) conf["tools"][name]["enable"] = enable @classmethod def _merge_user_config_global(cls, conf: dict, user_conf: dict, path=None): path = [] if path is None else path for key in user_conf: if key in conf: # pylint: disable=C0123 if isinstance(conf[key], dict) and isinstance(user_conf[key], dict): cls._merge_user_config_global( conf[key], user_conf[key], path + [str(key)] ) elif conf[key] == user_conf[key]: pass elif type(conf[key]) == type(user_conf[key]): # noqa: E721 conf[key] = user_conf[key] else: raise Exception(f"Conflict at {'.'.join(path)}") else: conf[key] = user_conf[key] @classmethod def _exclude_disabled_tools(cls, conf: dict): tools = conf["tools"] tools = {name: tools[name] for name in tools if tools[name]["enable"]} conf["tools"] = tools @classmethod def _sanitize_config(cls, conf): tools = conf.setdefault("tools", {}) for name in tools: if tools[name] is None: tools[name] = {} config = tools[name] def fix_re_pattern_sections(section): include = config.get(section) if include is not None and include.get("match"): if isinstance(include["match"], str): include["match"] = [include["match"]] fix_re_pattern_sections("include") fix_re_pattern_sections("exclude") @classmethod def from_config_files(cls): """ Construct a BBPProject from: - the YAML file bbp-project.yaml provided by this project - the YAML file provided by the parent project if available. Return: Instance of `BBPProject` """ cls.virtualenv().ensure_requirement("PyYAML>=5") import yaml # pylint: disable=C0415 with open(cls.default_config_file(), encoding="utf-8") as file: conf = yaml.safe_load(file) cls._sanitize_config(conf) for config in conf["tools"].values(): config.setdefault("enable", True) user_conf_file = cls.user_config_file() if user_conf_file: with open(user_conf_file, encoding="utf-8") as file: user_conf = yaml.safe_load(file) cls._sanitize_config(user_conf) cls.merge_user_config(conf, user_conf) cls._apply_global_conf(conf) cls._parse_conf_regex(conf) cls._exclude_disabled_tools(conf) return BBPProject(conf) bluebrain-hpc-coding-conventions-1.0.0+git20221201/dev/000077500000000000000000000000001434206647400222105ustar00rootroot00000000000000bluebrain-hpc-coding-conventions-1.0.0+git20221201/dev/bump000077500000000000000000000406171434206647400231110ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse from contextlib import contextmanager from collections import namedtuple import copy import functools import glob import logging import os import os.path from pathlib import Path import re import shutil import subprocess from subprocess import check_output import tempfile import yaml DEFAULT_REPOS_YAML = "repositories.yaml" LOGGER = logging.getLogger("bump") CMD_LOGGER = LOGGER.getChild("cmd") HPC_CC_HTTP_REMOTE = "https://github.com/BlueBrain/hpc-coding-conventions.git" IGNORED_CONFIG_FILES = { ".clang-format", ".clang-tidy", ".cmake-format.yaml", ".pre-commit-config.yaml", } @contextmanager def spack_build_env(spec): """Execute a Python context within a `spack build-env` environment""" cur_environ = os.environ.copy() cmd = ["spack", "build-env", spec, "bash", "--norc", "-c", "env"] output = check_output(cmd).decode("utf-8") VAR_RE = re.compile("^[a-zA-Z][a-zA-Z0-9_]*=.*") environ = { tuple(var.split("=", 1)) for var in output.splitlines() if VAR_RE.match(var) } try: os.environ.clear() os.environ.update(environ) yield environ finally: os.environ.clear() os.environ.update(cur_environ) def git_status(): adds = set() changes = set() dels = set() unknowns = set() for change in ( check_output(["git", "status", "--short"]).decode("utf-8").splitlines() ): if change.startswith("D "): dels.add(change[3:]) elif change.startswith(" M "): changes.add(change[3:]) elif change.startswith("?? "): unknowns.add(change[3:]) elif change.startswith("A "): adds.add(change[3:]) return adds, changes, dels, unknowns def _check_call(*args): CMD_LOGGER.info(" ".join(args)) return subprocess.check_call(args) def _call(*args): CMD_LOGGER.info(" ".join(args)) return subprocess.call(args) @contextmanager def pushd(path): old_cwd = os.getcwd() cwd = os.chdir(path) try: yield cwd finally: os.chdir(old_cwd) class SpackDevBuild: def __init__(self, spec, **kwargs): self._spec = spec self._command = SpackDevBuild.get_command(spec, **kwargs) self._call() self._env = self._extract_environ() def _call(self): timestamp_file = Path(".spack-dev-build.ts") timestamp_file.touch() _check_call(*self._command) self._path = None for d in glob.glob("spack-build-*"): if os.path.isdir(d) and os.path.getmtime(d) > os.path.getmtime( timestamp_file ): self._path = d break timestamp_file.unlink() if not self._path: raise RuntimeError("Could not deduce spack dev-build directory") def _extract_environ(self): cmd = "source spack-build-env.txt; unset PYTHONHOME; $SPACK_PYTHON -c " cmd += "'import os; print(repr(dict(os.environ)))'" return eval(check_output(cmd, shell=True).decode("utf-8")) @classmethod def get_command(cls, spec, test=None, overwrite=True, until=None): cmd = ["spack", "dev-build"] if overwrite: cmd.append("--overwrite") if until: cmd += ["--until", until] if test: cmd += [f"--test={test}"] cmd.append(spec) return cmd @property def spec(self): return self._spec @property def path(self): return self._path @property def command(self): return self._command def __enter__(self): self.__prev_cwd = os.getcwd() self.__prev_environ = copy.deepcopy(os.environ) os.chdir(self.path) os.environ.clear() os.environ.update(self._env) return self.path def __exit__(self, type, value, traceback): os.chdir(self.__prev_cwd) os.environ = self.__prev_environ del self.__prev_cwd del self.__prev_environ class Repository( namedtuple( "Repository", [ "url", "features", "location", "cmake_project_name", "default_branch", "spack_spec", "spack_until", "patch", ], ) ): @staticmethod def create(**kwargs): url = kwargs["url"] kwargs.setdefault("location", "hpc-coding-conventions") kwargs.setdefault("default_branch", "master") kwargs.setdefault("spack_spec", None) kwargs.setdefault("spack_until", "cmake") if "patch" in kwargs: kwargs["patch"] = os.path.abspath(kwargs["patch"]) else: kwargs["patch"] = None if "github.com" in url: repo = GitHubRepository(**kwargs) elif "bbpcode.epfl.ch" in url: repo = GerritRepository(**kwargs) elif "bbpgitlab.epfl.ch" in url: repo = GitLabRepository(**kwargs) else: raise Exception("Unsupported git server") repo.log = LOGGER.getChild(repo.name) return repo def submit(self): raise NotImplementedError @property def name(self): name = self.url.split(":")[-1] name = name.rsplit("/", 2)[-2:] if name[-1].endswith(".git"): name[-1] = name[-1][:-4] return "-".join(name) def bump_branch_exists(self, revision): return self._bump_branch(revision) in self.branches @property def remote(self): return self.name @property def branch(self): return self.name @property def remotes(self): return check_output(["git", "remote"]).decode("utf-8").split("\n") @property def branches(self): eax = [] for br in check_output(["git", "branch"]).decode("utf-8").split("\n"): if br.startswith("*"): br = br[1:] eax.append(br.strip()) return eax def fetch(self): self.log.info("fetching repository") if self.remote not in self.remotes: _check_call("git", "remote", "add", self.name, self.url) _check_call("git", "fetch", self.name) return True def _bump_branch(self, revision): return self.name + "-" + revision[:8] def _upstream_branch(self, revision): return self.remote + "/" + self._bump_branch(revision) def checkout_branch(self, revision): branch = self._bump_branch(revision) if branch in self.branches: _check_call("git", "checkout", "-f", "master") _check_call("git", "branch", "-D", branch) self.log.info("checkout branch %s", branch) _check_call( "git", "checkout", "-b", branch, "{}/{}".format(self.remote, self.default_branch), ) if not os.path.exists(self.location): with pushd(os.path.dirname(self.location)): _check_call("git", "submodule", "add", "--force", HPC_CC_HTTP_REMOTE) _check_call("git", "submodule", "update", "--recursive", "--init") return True def bump(self, revision): self.log.info("Bump submodule") with pushd(self.location): _check_call("git", "fetch") _check_call("git", "checkout", revision) if self.is_dirty: _check_call("git", "add", self.location) _check_call("git", "status") return True else: return False def update_gitignore(self): _, changes, _, unknowns = git_status() ignore_additions = [] for unknown in unknowns: if unknown in IGNORED_CONFIG_FILES: ignore_additions.append(unknown) for change in changes: if change in IGNORED_CONFIG_FILES: _check_call("git", "rm", "--force", change) ignore_additions.append(unknown) else: _check_call("git", "add", change) ignore_additions.sort() if ignore_additions: with open(".gitignore", "a") as ostr: for ignored in ignore_additions: print(ignored, file=ostr) _check_call("git", "add", ".gitignore") return True def commit(self, revision): _check_call("git", "commit", "-m", self.commit_message(revision)) return True def commit_message(self, revision): return f"Bump {self.location} submodule to {revision[:8]}" def merge_request_title(self, revision): return self.commit_message(revision) @property def is_dirty(self): return ( _call("git", "diff", "--quiet") != 0 or _call("git", "diff", "--staged", "--quiet") != 0 ) def test(self): with self._test_cmake(): self._test_formatting_targets() self._test_static_analysis() self._test_precommit() return True @property def cmake_base_cmd(self): cmd = ["cmake"] var_prefix = "-D" + self.cmake_project_name + "_" for feature, value in self.features.items(): if value: cmd.append(var_prefix + feature.upper() + ":BOOL=ON") return cmd def _test_cmake(self): if self.spack_spec: dev_build = SpackDevBuild(self.spack_spec, until=self.spack_until) with dev_build: # recall CMake to set proper variables _check_call(*self.cmake_base_cmd, ".") return dev_build if os.path.isdir("_build"): shutil.rmtree("_build") os.makedirs("_build") with pushd("_build"): cmd = copy.copy(self.cmake_base_cmd) python = check_output(["which", "python"]).decode("utf-8").rstrip() cmd.append("-DPYTHON_EXECUTABLE=" + python) cmd.append("..") _check_call(*cmd) return functools.partial(pushd, "_build") def _test_formatting_targets(self): if self.features.get("formatting") or self.features.get("clang_format"): _check_call("make", "clang-format") if self.features.get("formatting") or self.features.get("cmake_format"): _check_call("make", "cmake-format") def _test_static_analysis(self): if self.features.get("static_analysis"): _check_call("make", "clang-tidy") def _test_precommit(self): if self.features.get("precommit"): _check_call("pre-commit", "run", "-a") def clean_local_branches(self): _check_call("git", "checkout", "-f", "master") _check_call("git", "clean", "-ffdx") for branch in self.branches: if branch.startswith(self.remote): _check_call("git", "branch", "-D", branch) precommit_hook = os.path.join(".git", "hooks", "pre-commit") if os.path.exists(precommit_hook): os.remove(precommit_hook) class GitHubRepository(Repository): def submit(self, revision): _check_call("git", "push", self.remote, "HEAD") _check_call( "hub", "pull-request", "-m", self.commit_message(revision), ) return True class GerritRepository(Repository): def submit(self, revision): _check_call("git-review") return True class GitLabRepository(Repository): def submit(self, revision): _check_call( "git", "push", self.remote, "HEAD", "merge_request.create", "merge_request.title=" + self.merge_request_title(revision), ) return True def repositories(file): with open(file) as istr: return [ Repository.create(**repo) for repo in yaml.safe_load(istr)["repositories"] ] class IntegrationRepository: def __init__(self, repos, top_repository): self._repos = repos self._ignored = set() self.top_repository = top_repository if not os.path.isdir(self.top_repository): _check_call("git", "init", self.top_repository) with pushd(self.top_repository): with open("test", "w") as ostr: ostr.write("test") _check_call("git", "add", "test") _check_call("git", "commit", "-n", "-m", "add test") if not os.path.isdir(os.path.join(self.top_repository, ".git")): raise Exception("Could not find .git directory in " + self.top_repository) @property def repos(self): return [repo for repo in self._repos if repo.name not in self._ignored] def clean_local_branches(self): with pushd(self.top_repository): for repo in self.repos: repo.clean_local_branches() def update(self, revision, dry_run): with pushd(self.top_repository): self._fetch() self._checkout_branch(revision) self._patch() self._bump(revision) self._test() self._update_gitignore() self._commit(revision) if not dry_run: self._submit(revision) def _fetch(self): succeeded = True for repo in self.repos: succeeded &= repo.fetch() if not succeeded: raise Exception("Fetch operation failed") def _checkout_branch(self, revision): succeeded = True for repo in self.repos: if repo.bump_branch_exists(revision): repo.log.info( 'ignored because branch "%s" already exists.', repo._bump_branch(revision), ) self._ignored.add(repo.name) else: succeeded &= repo.checkout_branch(revision) if not succeeded: raise Exception("Checkout operation failed") def _patch(self): for repo in self.repos: if repo.patch: subprocess.check_call("patch -p1 <" + repo.patch, shell=True) def _bump(self, revision): for repo in self.repos: if not repo.bump(revision): repo.log.info("ignore because submodule is up to date") self._ignored.add(repo.name) def _test(self): succeeded = True for repo in self.repos: succeeded &= repo.test() if not succeeded: raise Exception("Test operation failed") def _update_gitignore(self): succeeded = True for repo in self.repos: succeeded &= repo.update_gitignore() if not succeeded: raise Exception("Gitignore update operation failed") def _commit(self, revision): succeeded = True for repo in self.repos: succeeded &= repo.commit(revision) if not succeeded: raise Exception("Commit operation failed") def _submit(self, revision): succeeded = True for repo in self.repos: succeeded = repo.submit(revision) if not succeeded: raise Exception("Submit operation failed") def main(**kwargs): parser = argparse.ArgumentParser() parser.add_argument( "-c", "--config", metavar="FILE", help="Configuration file [default: %(default)s]", default=DEFAULT_REPOS_YAML, ) parser.add_argument( "-r", "--revision", metavar="HASH", default="HEAD", help="Git revision to bump [default: %(default)s]", ) parser.add_argument( "--clean", help="remove local git branches", action="store_true" ) parser.add_argument( "-d", "--dry-run", action="store_true", help="do not create pull-request or gerrit review", ) parser.add_argument( "--repo", help="Collective git repository [default: %(default)s]", default=os.path.join(tempfile.gettempdir(), "hpc-cc-projects"), ) parser.add_argument( "-p", "--project", nargs="+", help="Filter repositories by CMake project name" ) args = parser.parse_args(**kwargs) revision = check_output(["git", "rev-parse", args.revision]).decode("utf-8").strip() repos = repositories(args.config) if args.project: repos = [repo for repo in repos if repo.cmake_project_name in set(args.project)] ir = IntegrationRepository(repos, args.repo) if args.clean: ir.clean_local_branches() else: ir.update(revision, args.dry_run) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) main() bluebrain-hpc-coding-conventions-1.0.0+git20221201/dev/repositories.yaml000066400000000000000000000020651434206647400256260ustar00rootroot00000000000000repositories: - url: git@github.com:BlueBrain/basalt.git features: formatting: true precommit: true static_analysis: true cmake_project_name: Basalt location: cmake/hpc-coding-conventions spack_spec: py-basalt@develop+codechecks - url: ssh://bbpcode.epfl.ch/incubator/nocmodl features: formatting: true precommit: true location: cmake/hpc-coding-conventions cmake_project_name: NMODL - url: ssh://bbpcode.epfl.ch/building/FLATIndex features: formatting: true location: CMake/hpc-coding-conventions cmake_project_name: FLATIndex - url: git@bbpgitlab.epfl.ch:hpc/circuit-building/touchdetector.git features: clang_format: true location: deps/hpc-coding-conventions cmake_project_name: TouchDetector default_branch: main spack_spec: touchdetector@5.6.1%gcc # patch: add-extensions.patch - url: git@github.com:CNS-OIST/HBP_STEPS.git features: formatting: true precommit: false static_analysis: false cmake_project_name: STEPS location: CMake/hpc-coding-conventions spack_spec: steps@develop+codechecks bluebrain-hpc-coding-conventions-1.0.0+git20221201/dev/test_lib.py000066400000000000000000000053211434206647400243700ustar00rootroot00000000000000from pathlib import Path import re import stat import sys import tempfile sys.path.append(str(Path(__file__).resolve().parent.parent)) import cpp.lib # noqa: E402 def test_clang_tidy_conf_merger(): orig_checks = "foo-*,bar-pika,-bar-foo" test_func = cpp.lib.ClangTidy.merge_clang_tidy_checks assert test_func(orig_checks, None) == orig_checks assert test_func(orig_checks, "") == orig_checks assert test_func(orig_checks, "-bar-pika") == "foo-*,-bar-foo,-bar-pika" assert test_func(orig_checks, "bar-pika") == "foo-*,-bar-foo,bar-pika" assert test_func(orig_checks, "-bar-*") == "foo-*,-bar-*" assert test_func(orig_checks, "bar-*") == "foo-*,bar-*" assert test_func(orig_checks, "-bar-*") == "foo-*,-bar-*" assert test_func(orig_checks, "-bar-foo") == "foo-*,bar-pika,-bar-foo" assert test_func(orig_checks, "bar-foo") == "foo-*,bar-pika,bar-foo" assert test_func("bar-foo", "-bar-*") == "-bar-*" assert test_func("", "-bar-pika") == "-bar-pika" assert test_func("", "bar-foo") == "bar-foo" assert test_func(None, None) is None def test_where(): """Test cpp.lib.where function""" with tempfile.TemporaryDirectory() as bin_dir: expected_paths = set() for name in [ "clang-format", "clang-format-13", "clang-format-14", "clang-format-mp-13", "clang-format-mp-14", "clang-format-diff.py", "clang-format-mp-diff.py", "clang-format-14-diff.py", "clang-format-diff", "clang-format-mp-diff", "clang-format-14-diff", ]: Path(bin_dir, name).touch() for name in ["clang-format", "clang-format-13", "clang-format-mp-13"]: executable = Path(bin_dir, name) executable.chmod(executable.stat().st_mode | stat.S_IEXEC) expected_paths.add(str(executable)) for name in [ "clang-format-diff.py", "clang-format-mp-diff.py", "clang-format-14-diff.py", "clang-format-diff", "clang-format-mp-diff", "clang-format-14-diff", ]: executable = Path(bin_dir, name) executable.chmod(executable.stat().st_mode | stat.S_IEXEC) TOOLS = cpp.lib.BBPProject.TOOLS_DESCRIPTION names_regex = TOOLS["ClangFormat"]["names_regex"] names_exclude_regex = TOOLS["ClangFormat"]["names_exclude_regex"] paths = set( cpp.lib.where( "clang-format", regex=re.compile(names_regex), exclude_regex=re.compile(names_exclude_regex), paths=[bin_dir], ) ) assert paths == expected_paths bluebrain-hpc-coding-conventions-1.0.0+git20221201/setup.cfg000066400000000000000000000000611434206647400232500ustar00rootroot00000000000000[flake8] max-line-length = 88 ignore = E203 W503