From d8f71c2570d9abfa06469c21a56fe53238530c14 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 3 Dec 2023 16:22:25 +0100 Subject: [PATCH] Imported upstream version '2.4.0' of 'upstream' --- .git-blame-ignore-revs | 5 + .github/workflows/check-changelog.yml | 14 + .github/workflows/macos-linux-conda.yml | 18 +- .github/workflows/macos-linux-pip.yml | 31 + .github/workflows/ros_ci.yml | 31 +- .github/workflows/windows-conda-clang.yml | 29 +- .github/workflows/windows-conda-v142.yml | 30 +- .gitlab-ci.yml | 2 +- .pre-commit-config.yaml | 36 +- CHANGELOG.md | 488 +++++++++++ CITATION.bib | 6 + CITATION.cff | 23 + CMakeLists.txt | 50 +- README.md | 35 +- cmake/.cmake-format.py | 240 ++++++ cmake/.github/workflows/cmake.yml | 10 +- cmake/.pre-commit-config.yaml | 16 +- cmake/Config.cmake.in | 6 +- cmake/GNUInstallDirs.cmake | 5 - cmake/README.md | 10 +- cmake/_unittests/test_pkg-config.cmake | 1 - cmake/base.cmake | 84 +- cmake/boost/FindBoost.cmake | 5 - cmake/cmake_reinstall.cmake.in | 1 - cmake/cmake_uninstall.cmake.in | 1 - cmake/compile.py | 42 - cmake/cxx-standard.cmake | 9 + cmake/cxx11.cmake | 48 +- cmake/cython/cython.cmake | 309 +++++++ cmake/cython/python/FindPython/Support.cmake | 2 +- cmake/doxygen.cmake | 5 +- cmake/find-external/OpenMP/FindOpenMP.cmake | 785 +++++++++++++++++- cmake/logging.cmake | 6 +- cmake/memorycheck_unit_test.cmake.in | 32 + cmake/msvc-specific.cmake | 4 - cmake/pkg-config.cmake | 37 +- cmake/post-project.cmake | 7 +- cmake/python-helpers.cmake | 115 +++ cmake/python.cmake | 245 ++---- cmake/python/FindPythonInterp.cmake | 171 ---- cmake/python/FindPythonLibs.cmake | 399 --------- cmake/release.cmake | 45 +- cmake/relpath.cmake | 49 ++ cmake/stubs.cmake | 52 +- cmake/test.cmake | 92 +- cmake/uninstall.cmake | 5 +- cmake/version-script.cmake | 3 - cmake/version.cmake | 17 +- doc/generate_distance_plot.py | 1 - doc/gjk.py | 58 +- .../hpp-fcl-vs-the-rest-of-the-world.png | Bin 0 -> 98179 bytes doc/mesh-mesh-collision-call.pdf | Bin 24297 -> 0 bytes doc/python/doxygen_xml_parser.py | 37 +- doc/python/xml_docstring.py | 10 +- doc/shape-mesh-collision-call.pdf | Bin 26183 -> 0 bytes doc/shape-shape-collision-call.pdf | Bin 22270 -> 0 bytes include/hpp/fcl/BVH/BVH_model.h | 2 +- include/hpp/fcl/collision_object.h | 10 +- include/hpp/fcl/internal/tools.h | 8 +- include/hpp/fcl/narrowphase/narrowphase.h | 3 +- include/hpp/fcl/timings.h | 24 +- package.xml | 2 +- python/CMakeLists.txt | 15 +- python/broadphase/broadphase_callbacks.hh | 4 +- .../broadphase_collision_manager.hh | 4 + python/hppfcl/__init__.py | 3 +- python/hppfcl/viewer.py | 11 +- python/math.cc | 4 +- python/pickle.hh | 2 +- src/BV/AABB.cpp | 4 +- src/BV/OBB.cpp | 12 +- src/CMakeLists.txt | 5 +- src/broadphase/broadphase_SaP.cpp | 2 +- .../broadphase_dynamic_AABB_tree.cpp | 4 +- .../broadphase_dynamic_AABB_tree_array.cpp | 4 +- src/collision.cpp | 4 +- src/distance.cpp | 4 +- src/shape/convex.cpp | 4 +- test/CMakeLists.txt | 5 +- test/collision.cpp | 28 +- test/obb.cpp | 119 +-- test/python_unit/api.py | 1 - test/python_unit/collision.py | 1 - test/python_unit/geometric_shapes.py | 10 +- test/scripts/collision-bench.py | 9 +- test/scripts/collision.py | 1 - test/scripts/distance_lower_bound.py | 1 - test/scripts/geometric_shapes.py | 4 +- test/scripts/gjk.py | 1 - test/scripts/obb.py | 41 +- test/scripts/octree.py | 1 - test/serialization.cpp | 10 +- 92 files changed, 2831 insertions(+), 1298 deletions(-) create mode 100644 .github/workflows/check-changelog.yml create mode 100644 .github/workflows/macos-linux-pip.yml create mode 100644 CHANGELOG.md create mode 100644 CITATION.bib create mode 100644 CITATION.cff create mode 100644 cmake/.cmake-format.py delete mode 100755 cmake/compile.py create mode 100644 cmake/memorycheck_unit_test.cmake.in create mode 100644 cmake/python-helpers.cmake delete mode 100644 cmake/python/FindPythonInterp.cmake delete mode 100644 cmake/python/FindPythonLibs.cmake create mode 100644 cmake/relpath.cmake create mode 100644 doc/images/hpp-fcl-vs-the-rest-of-the-world.png delete mode 100644 doc/mesh-mesh-collision-call.pdf delete mode 100644 doc/shape-mesh-collision-call.pdf delete mode 100644 doc/shape-shape-collision-call.pdf diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 5ded138..5463ef8 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1 +1,6 @@ 0067c8aa66aac548601e2a3fd029aa264cc59f2a +76b68f785df31b00e153290b45ec290a9c5f7963 +# ruff --fix . (Guilhem Saurel, 2023-10-24) +58dee5ae90eded5125825a2da0fe76a5031f3334 +# black . (Guilhem Saurel, 2023-10-24) +889ff8d1ca00b9e317e1da4136e233bb49a049df diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml new file mode 100644 index 0000000..da9c25b --- /dev/null +++ b/.github/workflows/check-changelog.yml @@ -0,0 +1,14 @@ +name: Check-changelog +on: + pull_request: + types: [assigned, opened, synchronize, reopened, labeled, unlabeled] + branches: + - devel +jobs: + check-changelog: + name: Check changelog action + runs-on: ubuntu-latest + steps: + - uses: tarides/changelog-check-action@v2 + with: + changelog: CHANGELOG.md diff --git a/.github/workflows/macos-linux-conda.yml b/.github/workflows/macos-linux-conda.yml index fa05fda..9a83ac9 100644 --- a/.github/workflows/macos-linux-conda.yml +++ b/.github/workflows/macos-linux-conda.yml @@ -11,6 +11,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "macos-latest"] + python-version: ["3.8", "3.12"] steps: - uses: actions/checkout@v3 @@ -22,7 +23,7 @@ jobs: activate-environment: hpp-fcl auto-update-conda: true environment-file: .github/workflows/conda/conda-env.yml - python-version: 3.7 + python-version: ${{ matrix.python-version }} - name: Install compilers on OSX if: contains(matrix.os, 'macos') @@ -60,3 +61,18 @@ jobs: run: | cd build make uninstall + + check: + if: always() + name: check-macos-linux-conda + + needs: + - hpp-fcl-conda + + runs-on: Ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/macos-linux-pip.yml b/.github/workflows/macos-linux-pip.yml new file mode 100644 index 0000000..12caef0 --- /dev/null +++ b/.github/workflows/macos-linux-pip.yml @@ -0,0 +1,31 @@ +name: Build hpp-fcl for Mac OS X/Linux via pip + +on: [push, pull_request] + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + CTEST_PARALLEL_LEVEL: 4 + +jobs: + hpp-fcl-pip: + name: "CI on ${{ matrix.os }} / py ${{ matrix.python-version }} with pip" + runs-on: "${{ matrix.os }}-latest" + + strategy: + fail-fast: false + matrix: + os: ["ubuntu", "macos"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - run: python -m pip install -U pip + - run: python -m pip install cmeel-assimp cmeel-octomap cmeel-qhull eigenpy[build] + - run: echo "CMAKE_PREFIX_PATH=$(cmeel cmake)" >> $GITHUB_ENV + - run: cmake -B build -S . -DHPP_FCL_HAS_QHULL=ON + - run: cmake --build build -j 4 + - run: cmake --build build -t test diff --git a/.github/workflows/ros_ci.yml b/.github/workflows/ros_ci.yml index 2441baa..5e0d756 100644 --- a/.github/workflows/ros_ci.yml +++ b/.github/workflows/ros_ci.yml @@ -11,12 +11,12 @@ jobs: strategy: matrix: env: - - {ROS_DISTRO: melodic, PRERELEASE: false} - {ROS_DISTRO: noetic} - - {ROS_DISTRO: foxy} + - {ROS_DISTRO: iron} + - {ROS_DISTRO: humble} - {ROS_DISTRO: rolling} env: - CCACHE_DIR: /github/home/.ccache # Enable ccache + #CCACHE_DIR: /github/home/.ccache # Enable ccache BUILDER: colcon PRERELEASE: true runs-on: ubuntu-latest @@ -25,10 +25,25 @@ jobs: with: submodules: recursive # This step will fetch/store the directory used by ccache before/after the ci run - - uses: actions/cache@v3 - with: - path: ${{ env.CCACHE_DIR }} - key: ccache-${{ matrix.env.ROS_DISTRO }}-${{ matrix.env.ROS_REPO }} + #- uses: actions/cache@v3 + # with: + # path: ${{ env.CCACHE_DIR }} + # key: ccache-${{ matrix.env.ROS_DISTRO }}-${{ matrix.env.ROS_REPO }} # Run industrial_ci - - uses: 'ros-industrial/industrial_ci@6a8f546cbd31fbd5c9f77e3409265c8b39abc3d6' + - uses: 'ros-industrial/industrial_ci@9f963f67ebb889792175776c5ee00134d7bb569b' env: ${{ matrix.env }} + + check: + if: always() + name: check-ros-ci + + needs: + - CI + + runs-on: Ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/windows-conda-clang.yml b/.github/workflows/windows-conda-clang.yml index 1a7eebd..22c27a6 100644 --- a/.github/workflows/windows-conda-clang.yml +++ b/.github/workflows/windows-conda-clang.yml @@ -18,13 +18,13 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive - - uses: goanpeca/setup-miniconda@v1 + - uses: conda-incubator/setup-miniconda@v2 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' with: activate-environment: fcl environment-file: .github/workflows/conda/conda-env.yml - python-version: 3.7 + python-version: "3.10" - name: Install cmake and update conda run: | conda install cmake -c main @@ -52,23 +52,38 @@ jobs: -G "Visual Studio 16 2019" -T "ClangCl" -DCMAKE_GENERATOR_PLATFORM=x64 ^ -DCMAKE_INSTALL_PREFIX=%CONDA_PREFIX%\Library ^ -DCMAKE_BUILD_TYPE=Release ^ - -DGENERATE_PYTHON_STUBS=OFF ^ + -DGENERATE_PYTHON_STUBS=ON ^ -DPYTHON_SITELIB=%CONDA_PREFIX%\Lib\site-packages ^ -DPYTHON_EXECUTABLE=%CONDA_PREFIX%\python.exe ^ -DHPP_FCL_HAS_QHULL=ON ^ -DBUILD_PYTHON_INTERFACE=ON ^ .. + if errorlevel 1 exit 1 :: Build and Install cmake --build . --config Release --target install - - :: Build stubs - git clone https://github.com/jcarpent/pybind11-stubgen.git - python "%CD%\pybind11-stubgen\pybind11_stubgen\__init__.py" -o %CONDA_PREFIX%\Lib\site-packages hppfcl --boost-python --ignore-invalid signature --no-setup-py --root-module-suffix "" + if errorlevel 1 exit 1 :: Testing ctest --output-on-failure -C Release -V + if errorlevel 1 exit 1 :: Test Python import cd .. python -c "import hppfcl" + if errorlevel 1 exit 1 + + check: + if: always() + name: check-windows-conda-clang + + needs: + - build + + runs-on: Ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/windows-conda-v142.yml b/.github/workflows/windows-conda-v142.yml index 7c05a83..6b18303 100644 --- a/.github/workflows/windows-conda-v142.yml +++ b/.github/workflows/windows-conda-v142.yml @@ -17,13 +17,13 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive - - uses: goanpeca/setup-miniconda@v1 + - uses: conda-incubator/setup-miniconda@v2 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' with: activate-environment: fcl environment-file: .github/workflows/conda/conda-env.yml - python-version: 3.7 + python-version: "3.10" - name: Install cmake and update conda run: | conda install cmake -c main @@ -51,24 +51,38 @@ jobs: -G "Visual Studio 16 2019" -T "v142" -DCMAKE_GENERATOR_PLATFORM=x64 ^ -DCMAKE_INSTALL_PREFIX=%CONDA_PREFIX%\Library ^ -DCMAKE_BUILD_TYPE=Release ^ - -DGENERATE_PYTHON_STUBS=OFF ^ + -DGENERATE_PYTHON_STUBS=ON ^ -DPYTHON_SITELIB=%CONDA_PREFIX%\Lib\site-packages ^ -DPYTHON_EXECUTABLE=%CONDA_PREFIX%\python.exe ^ -DHPP_FCL_HAS_QHULL=ON ^ -DBUILD_PYTHON_INTERFACE=ON ^ .. + if errorlevel 1 exit 1 :: Build and Install cmake --build . --config Release --target install - - - :: Build stubs - git clone https://github.com/jcarpent/pybind11-stubgen.git - python "%CD%\pybind11-stubgen\pybind11_stubgen\__init__.py" -o %CONDA_PREFIX%\Lib\site-packages hppfcl --boost-python --ignore-invalid signature --no-setup-py --root-module-suffix "" + if errorlevel 1 exit 1 :: Testing ctest --output-on-failure -C Release -V + if errorlevel 1 exit 1 :: Test Python import cd .. python -c "import hppfcl" + if errorlevel 1 exit 1 + + check: + if: always() + name: check-windows-conda-v142 + + needs: + - build + + runs-on: Ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0281d6e..fcd4d41 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1 +1 @@ -include: http://rainboard.laas.fr/project/hpp-fcl/.gitlab-ci.yml +include: https://rainboard.laas.fr/project/hpp-fcl/.gitlab-ci.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf17bb7..881a2ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,24 @@ ci: - autoupdate_branch: 'devel' + autoupdate_branch: devel repos: -- repo: https://github.com/pre-commit/mirrors-clang-format - rev: v15.0.7 - hooks: - - id: clang-format - args: ['--style={BasedOnStyle: Google, SortIncludes: false}'] -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: trailing-whitespace -- repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.6 + hooks: + - id: ruff + args: + - --fix + - --exit-non-zero-on-fix +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v17.0.5 + hooks: + - id: clang-format + args: + - '--style={BasedOnStyle: Google, SortIncludes: false}' +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 23.11.0 + hooks: + - id: black diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b563fd5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,488 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [Unreleased] + +## [2.4.0] - 2023-11-27 + +### Added +- Add method to `CollisionObject` to get `CollisionGeometry` raw pointer + +### Fixed +- Fix RPATH computation on OSX +- Fix Python stubs generation on Windows + +## [2.3.7] - 2023-11-15 + +### What's Changed +- Add Python 3.12 support by @jorisv in https://github.com/humanoid-path-planner/hpp-fcl/pull/471 +- Enable ruff linting by @nim65s https://github.com/humanoid-path-planner/hpp-fcl/pull/464 + +## [2.3.6] - 2023-09-30 + +### What's Changed +- Update ROS_DISTRO by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/442 +- Add citations by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/449 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/444 +- [WIP] Debug by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/455 +- CMake: require >= 3.10 by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/453 +- core: fix SaPCollisionManager::empty() by @rujialiu in https://github.com/humanoid-path-planner/hpp-fcl/pull/454 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/452 + +### New Contributors +- @rujialiu made their first contribution in https://github.com/humanoid-path-planner/hpp-fcl/pull/454 + +## [2.3.5] - 2023-07-11 + +### What's Changed +- Fix compilation warning by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/434 +- Fix parsing of doxygen doc by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/439 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/438 + +## [2.3.4] - 2023-06-01 + +### What's Changed +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/414 +- Fix conversion warning by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/417 +- Add missing boost include by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/418 +- ci: update macos-linux-pip by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/419 +- Modernize Cmake use by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/420 +- tests: use boost::filesystem by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/424 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/425 +- Update minimal Python version by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/427 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/430 +- Sync submodule CMake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/431 + +## [2.3.3] - 2023-05-09 + +### What's Changed +- update default C++ to 14 by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/410 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/413 + +## [2.3.2] - 2023-04-27 + +### What's Changed +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/391 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/393 +- Topic/rpath by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/394 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/396 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/399 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/402 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/406 + +## [2.3.1] - 2023-03-25 + +### What's Changed +- Remove useless call to /proc/cpuinfo by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/385 +- Add pip CI by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/386 +- [GJKSolver] Fix missing switch case in result status of GJK by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/387 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/388 + +## [2.3.0] - 2023-03-17 + +### What's Changed +- [CI] Remove EOL Galactic by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/366 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/367 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/368 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/369 +- Adding EarlyStopped flag in GJK by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/371 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/373 +- Update CI by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/374 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/375 +- Skip test if BUILD_TESTING is OFF by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/378 + +## [2.2.0] - 2022-12-12 + +### What's Changed +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/358 +- Extract checks if AABB overlap by @jmirabel in https://github.com/humanoid-path-planner/hpp-fcl/pull/360 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/361 +- Sync submodule CMake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/362 +- Add support of Pickling by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/363 + +## [2.1.4] - 2022-10-24 + +### What's Changed +- Sync submodule CMake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/352 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/353 + +## [2.1.3] - 2022-09-13 + +### What's Changed +- Minor boost cleanup by @pantor in https://github.com/humanoid-path-planner/hpp-fcl/pull/331 +- [CI] Activate ROS2 configurations by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/332 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/337 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/341 +- Fix shapeIntersect when for EPA FallBack by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/342 +- Fix findAssimp on Windows by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/345 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/347 + +### New Contributors +- @pantor made their first contribution in https://github.com/humanoid-path-planner/hpp-fcl/pull/331 + +## [2.1.2] - 2022-08-01 + +### What's Changed +- core: add EPA::FallBack condition to shapeDistance computation by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/325 +- CMake: update to eigenpy 2.7.10 by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/327 + +## [2.1.1] - 2022-07-25 + +### What's Changed +- cmake: relocatable package for recent CMake versions by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/319 +- ROS2/Colcon integration by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/321 + +## [2.1.0] - 2022-07-13 + +### What's Changed +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/288 +- Add enum helpers by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/290 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/294 +- Ellipsoids in collision & distance matrices by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/295 +- doc: simplex projection in GJK class. by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/296 +- Feature: Nesterov acceleration for GJK by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/289 +- Add more testing to broadphase by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/298 +- Feature: adding convergence criterions for GJK algorithm by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/299 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/300 +- Reorder triangles when computing convex hulls by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/301 +- Exposing gjk utils by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/302 +- Fix assert precision in GJK by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/304 +- Simplify GJKSolver settings by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/305 +- Add CollisionResult::nearest_points by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/303 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/306 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/309 +- Fix minimal value for GJK::distance_upper_bound by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/310 +- Fix incoherent overlap by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/311 +- Expose shared_ptr by @Jiayuan-Gu in https://github.com/humanoid-path-planner/hpp-fcl/pull/314 +- test/gjk_convergence_criterion: Add check on GJK::Status by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/315 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/316 +- Handle negative security margin by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/312 + +### New Contributors +- @Jiayuan-Gu made their first contribution in https://github.com/humanoid-path-planner/hpp-fcl/pull/314 + +## [2.0.1] - 2022-04-15 + +This PR mainly fixes packaging issues and removes compilation warnings. + +### What's Changed +- Zero warnings by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/282 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/283 +- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci in https://github.com/humanoid-path-planner/hpp-fcl/pull/284 +- Activate python3-pylatexenc dependency by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/286 +- Comment pylatexenc again since it's not available on the buildfarm by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/287 + +### New Contributors +- @pre-commit-ci made their first contribution in https://github.com/humanoid-path-planner/hpp-fcl/pull/284 + +## [2.0.0] - 2022-04-06 + +This new release reintroduces the full support of Broad phase within hpp-fcl while also enforcing C++11 as minimal standard. + +### What's Changed +- Add Ellipsoid by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/259 +- Removing comment about inflation. by @lmontaut in https://github.com/humanoid-path-planner/hpp-fcl/pull/261 +- Reintroduce broadphase by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/260 +- Simplify CollisionObject by removing cgeom_const by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/263 +- Address some warnings by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/262 +- Fix missing copy of aabb_local in CollisionGeometry by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/264 +- use std::shared_ptr, fix #218 by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/266 +- Fix broadphase warnings for clang (some conversion remain for g++) by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/268 +- [ComputeCollision] Return no collision if security_margin is set to -inf by @florent-lamiraux in https://github.com/humanoid-path-planner/hpp-fcl/pull/271 +- tests: remove link to boost unit test framework by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/270 +- Fix computation of aabb_center by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/273 +- Add operator== and operator!= to CollisionGeometry by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/274 +- Merge pull request #276 from humanoid-path-planner/patch-release-1.8.1 by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/277 +- Fix some missing features in base classes by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/275 +- Add operator{==,!=} to CollisionObject by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/278 +- Configure and apply pre-commit by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/280 +- Fix DistanceCallBackBaseWrapper by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/281 + +### New Contributors +- @lmontaut made their first contribution in https://github.com/humanoid-path-planner/hpp-fcl/pull/261 + +## [1.8.1] - 2022-03-20 + +### What's Changed +- Preparing for ROS1 and ROS2 release by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/255 +- Patch release 1.8.1 by @wxmerkt in https://github.com/humanoid-path-planner/hpp-fcl/pull/276 + +## [1.8.0] - 2022-02-08 + +### What's Changed +- [CMake] Qhull is a private dependency by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/247 +- Remove useless warnings by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/248 +- fix submodule url by @nim65s in https://github.com/humanoid-path-planner/hpp-fcl/pull/246 +- Remove warnings and add missing noalias by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/249 +- Function makeOctree returns a shared pointer by @florent-lamiraux in https://github.com/humanoid-path-planner/hpp-fcl/pull/254 +- Add support of HeightField by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/251 +- [OcTree] Add method to save octree in obj file. by @florent-lamiraux in https://github.com/humanoid-path-planner/hpp-fcl/pull/256 +- Fix C++98 compatibility by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/258 + +## [1.7.8] - 2021-10-30 + +### What's Changed +- Fix conversion by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/242 +- Fix exposition of vertices by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/243 +- Enhance Convex exposition by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/244 +- Sync submodule cmake by @jcarpent in https://github.com/humanoid-path-planner/hpp-fcl/pull/245 + +## [1.7.7] - 2021-09-13 + +This new release fixes several bugs within the framework. + +## [1.7.6] - 2021-09-08 + +This new release improves the packaging of the project and integrates the Stub generation of Python bindings. + +## [1.7.5] - 2021-07-30 + +This new release provides extended API exposition in Python, removes some code related to CDD while also trying to rely on the QHULL version present on the system. + +## [1.7.4] - 2021-06-11 + +This release fixes several bugs: +- correct update of the distance lower bound +- fix memory footprint computation + +while also removing the support of Travis CI. + +## [1.7.3] - 2021-05-26 + +This new release provides: +- fixes of LINE and POINTS when loading meshes with assimp +- removing of various warnings +- computation of memory footprint for geometries + +## [1.7.2] - 2021-04-19 + +This new release improves the loading of meshes using Assimp by automatically removing degenerated LINES and POINTS. + +## [1.7.1] - 2021-04-02 + +This new release reduces the impact of timers on the computations. +This should be used with care and can be enabled by setting the correct flag to true in the QueryRequest. + +## [1.7.0] - 2021-03-31 + +This new release provides: +- extended support for serialization +- timing of the collision/distance computations +- helpers to build octree +- various bug fixes and interface improvements + +## [1.6.0] - 2020-10-06 + +This new release provides: +- functors for evaluating Collision and Distances (faster call) +- extended support of v142 compiler +- support of collision check between HalfSpace and Convex shapes +- improvement of GJK solver +- fixes on Python bindings + +## [1.5.4] - 2020-09-22 + +In this new release, the support of collision checking between Convex objects and HalfSpace have been enhanced and some minor fixes have been provided. + +## [1.5.3] - 2020-08-31 + +This new release provides better CMake packaging and improved GJK algorithms. + +## [1.5.2] - 2020-08-15 + +This release improves the packaging of the project and provides fixes for the GJK solver. + +## [1.5.1] - 2020-08-06 + +This new release fixes packaging issues with precedent release 1.5.0. It also provides additional fixes in main collision/distance algorithms. + +## [1.4.6] - 2020-06-10 + +This new release enhances the packaging of the project and allows the compilation of FCL on Windows systems. + +## [1.4.5] - 2020-06-03 + +Changes in v1.4.5: +- Fix Python 3 doc generation +- Fix packaging of the project +- Compilation on Windows. +- [CMake] Install missing header. +- Add collide and distance prototype that update the GJK guess. +- Add support function cached guess in queries and merge query attribute. +- Add function to generate the convex hull. +- Add hint to the support function + Fix usage of GJK guess. +- [Python] Add constructor for class Convex. +- [Python] Bind functions to create BVHModel. + +## [1.4.4] - 2020-04-29 + +Changes in 1.4.4: +- add MeshLoader::loadOctree +- fix generation of XML documentation +- fix generation of Doxygen documentation + +## [1.4.3] - 2020-04-08 + +This new release fixes some packagings issues for OS X systems. + +## [1.4.2] - 2020-04-04 + +Changes in v1.4.2: +- don't require linking to eigenpy in .pc file. + +## [1.4.1] - 2020-04-03 + +Changes in v1.4.1: +- Bug fix + prepare optimization of collision using GJK / EPA +- Add missing constructor for Transform3f + +## [1.4.0] - 2020-04-03 + +Changes since v1.3.0: +- Improve code efficiency + use shared memory between Numpy and Eigen +- [Python] Doc and minor update + [C++] bugfix +- [Python] Fix bindings of CollisionResult. +- FIX: throw when no contact is available +- Minor fix and computational improvments +- [GJK/EPA] Fix bugs + Treat sphere as point and capsule as line segment. +- Fix boxSphereDistance +- Provide documentation for the Python bindings. +- Generate Python documentation from doxygen documentation. +- Fix issue when Python_EXECUTABLE is not defined +- update CMake packaging + +## [1.3.0] - 2020-01-28 + +This new release comes with: +- the removing of the GJK solver +- the Python bindings build by default +- an improved documentation +- additional Python bindings + +## [1.2.2] - 2019-12-17 + +This new Release improves the Python bindings and fixes an important bug when checking the collision between two Capsules. + +Thanks to @rstrudel for this fix. + +## [1.2.1] - 2019-12-09 + +This new release improves both the packaging of the project, which seems to be totally compatible with the new CMake linkage style. In addition, the bindings are now fully compatible with Pinocchio. + +## [1.2.0] - 2019-11-22 + +Changes since v1.1.3: +- Add python bindings +- Update CMake +- Add version support +- New folder Internal for internal header +- Travis: update CI & change policy to only perform build in DEBUG mode on Bionic +- assimp: fix issue with recent version of assimp +- [bindings] [CMakeLists] Use .so for Mac and .pyd for Windows, fix #86 +- Organize documentation +- [CMake] fix octomap detection +- [Minor] update CMake module + fix visibility of some methods. +- Enable Convex / Convex queries + Add Python bindings. +- Fix unit-tests and compilation +- [GJK] Fix GJK::encloseOrigin (fixes unit-tests) +- Improve GJK implementation + OBB overlap test + bug fixes +- Clean include BV/BVH/math/mesh_loader + +## [1.1.3] - 2019-08-07 + +This new release enhances the compatibility of hpp-fcl with C++14 and more. +This feature is requested for integration in Anaconda. + +## [1.1.2] - 2019-08-05 + +This new release provides a fix in the parallelization of the computations and improves the packaging of the whole project. + +## [1.0.2] - 2019-04-24 + +Changes since v1.0.1: +- obb: fix compatibility with Eigen 3.0.5 +- [CI] octomap for osx + +## [1.0.1] - 2019-02-20 + +- Fix CI on OSX +- Declare CachedMeshLoader::Key::operator< +- minor details + +## [0.7.0] - 2019-01-31 + +This release is mainly here to allow the packaging of HPP-RBPRM. +Another release will follow with more news. + +## [0.6.0] - 2018-10-22 + +- Fix bug when OCTOMAP is not found +- move buildTrianglePlane and clipTriangle method from private to public +- Fix bug with "\" symbols +- [CMake] Add flags related to Octomap in pkg-config file and remove FCL_HAVE_EIGEN + +## [0.5.1] - 2017-10-02 + +Now Eigen is at the heart of linear algebra computations. + +## [0.5] - 2017-03-17 + +First release + + +[Unreleased]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.4.0...HEAD +[2.4.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.7...v2.4.0 +[2.3.7]: https://github.com/humanoid-path-planner/hpp-fcl/compare/2.3.6...v2.3.7 +[2.3.6]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.5...v2.3.6 +[2.3.5]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.4...v2.3.5 +[2.3.4]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.3...v2.3.4 +[2.3.3]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.2...v2.3.3 +[2.3.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.1...v2.3.2 +[2.3.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.3.0...v2.3.1 +[2.3.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.2.0...v2.3.0 +[2.2.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.1.4...v2.2.0 +[2.1.4]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.1.3...v2.1.4 +[2.1.3]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.1.2...v2.1.3 +[2.1.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.1.1...v2.1.2 +[2.1.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.1.0...v2.1.1 +[2.1.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.0.1...v2.1.0 +[2.0.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.8.1...v2.0.0 +[1.8.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.8.0...v1.8.1 +[1.8.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.8...v1.8.0 +[1.7.8]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.7...v1.7.8 +[1.7.7]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.6...v1.7.7 +[1.7.6]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.5...v1.7.6 +[1.7.5]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.4...v1.7.5 +[1.7.4]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.3...v1.7.4 +[1.7.3]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.2...v1.7.3 +[1.7.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.1...v1.7.2 +[1.7.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.5.4...v1.6.0 +[1.5.4]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.5.3...v1.5.4 +[1.5.3]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.5.2...v1.5.3 +[1.5.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.5.1...v1.5.2 +[1.5.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.6...v1.5.1 +[1.4.6]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.5...v1.4.6 +[1.4.5]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.4...v1.4.5 +[1.4.4]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.3...v1.4.4 +[1.4.3]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.2...v1.4.3 +[1.4.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.1...v1.4.2 +[1.4.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.3.0...v1.4.0 +[1.3.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.2.2...v1.3.0 +[1.2.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.2.1...v1.2.2 +[1.2.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.1.3...v1.2.0 +[1.1.3]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.1.2...v1.1.3 +[1.1.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.0.2...v1.1.2 +[1.0.2]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v0.7.0...v1.0.1 +[0.7.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v0.6.0...v0.7.0 +[0.6.0]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v0.5.1...v0.6.0 +[0.5.1]: https://github.com/humanoid-path-planner/hpp-fcl/compare/v0.5...v0.5.1 +[0.5]: https://github.com/humanoid-path-planner/hpp-fcl/releases/tag/v0.5 diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 0000000..cc6fee3 --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,6 @@ +@misc{hppfclweb, + author = {Jia Pan and Sachin Chitta and Dinesh Manocha and Florent Lamiraux and Joseph Mirabel and Justin Carpentier and Louis Montaut and others}, + title = {HPP-FCL: an extension of the Flexible Collision Library}, + howpublished = {https://github.com/humanoid-path-planner/hpp-fcl}, + year = {2015--2023} +} \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..d70e6d1 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,23 @@ +cff-version: 1.2.0 +message: Thanks for using HPP-FCL. Please use the following metadata to cite us in your documents. +title: HPP-FCL - An extension of the Flexible Collision Library +abstract: An extension of the Flexible Collision Library + - family-names: Pan + given-names: Jia + - family-names: Chitta + given-names: Sachin + - family-names: Pan + given-names: Jia + - family-names: Manocha + given-names: Dinesh + - family-names: Mirabel + given-names: Joseph + - family-names: Carpentier + given-names: Justin + orcid: "https://orcid.org/0000-0001-6585-2894" + - family-names: Montaut + given-names: Louis +version: 2.3.5 +date-released: "2023-07-11" +license: BSD-2-Clause +repository-code: "https://github.com/humanoid-path-planner/hpp-fcl" \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 58dd41d..9a1c88c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.10) set(CXX_DISABLE_WERROR TRUE) set(PROJECT_NAME hpp-fcl) @@ -42,8 +42,6 @@ set(PROJECT_DESCRIPTION SET(PROJECT_USE_CMAKE_EXPORT TRUE) SET(PROJECT_COMPATIBILITY_VERSION AnyNewerVersion) -# Do not support CMake older than 2.8.12 -CMAKE_POLICY(SET CMP0022 NEW) SET(PROJECT_USE_KEYWORD_LINK_LIBRARIES TRUE) SET(DOXYGEN_USE_TEMPLATE_CSS TRUE) @@ -54,29 +52,42 @@ SET(DOXYGEN_USE_TEMPLATE_CSS TRUE) OPTION(INSTALL_DOCUMENTATION "Generate and install the documentation" OFF) # Check if the submodule cmake have been initialized -IF(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/base.cmake") - MESSAGE(FATAL_ERROR "\nPlease run the following command first:\ngit submodule update --init\n") -ENDIF() +set(JRL_CMAKE_MODULES "${CMAKE_CURRENT_LIST_DIR}/cmake") +if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/base.cmake") + if(${CMAKE_VERSION} VERSION_LESS "3.14.0") + message( + FATAL_ERROR + "\nPlease run the following command first:\ngit submodule update --init\n" + ) + else() + message(STATUS "JRL cmakemodules not found. Let's fetch it.") + include(FetchContent) + FetchContent_Declare( + "jrl-cmakemodules" + GIT_REPOSITORY "https://github.com/jrl-umi3218/jrl-cmakemodules.git") + FetchContent_MakeAvailable("jrl-cmakemodules") + FetchContent_GetProperties("jrl-cmakemodules" SOURCE_DIR JRL_CMAKE_MODULES) + endif() +endif() -include(cmake/boost.cmake) -include(cmake/hpp.cmake) -include(cmake/apple.cmake) -include(cmake/ide.cmake) +include("${JRL_CMAKE_MODULES}/boost.cmake") +include("${JRL_CMAKE_MODULES}/hpp.cmake") +include("${JRL_CMAKE_MODULES}/apple.cmake") +include("${JRL_CMAKE_MODULES}/ide.cmake") include(CMakeDependentOption) SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake-modules/ ${CMAKE_MODULE_PATH}) +set_default_cmake_build_type("RelWithDebInfo") + # If needed, fix CMake policy for APPLE systems APPLY_DEFAULT_APPLE_CONFIGURATION() COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX) PROJECT(${PROJECT_NAME} ${PROJECT_ARGS}) -# Default C++ version should be C++11 -CHECK_MINIMAL_CXX_STANDARD(11 ENFORCE) - OPTION(BUILD_PYTHON_INTERFACE "Build the python bindings" ON) CMAKE_DEPENDENT_OPTION(GENERATE_PYTHON_STUBS "Generate the Python stubs associated to the Python library" OFF BUILD_PYTHON_INTERFACE OFF) @@ -111,6 +122,14 @@ if(BUILD_PYTHON_INTERFACE) find_package(Boost REQUIRED COMPONENTS system) endif(BUILD_PYTHON_INTERFACE) +if(Boost_VERSION_STRING VERSION_LESS 1.81) + # Default C++ version should be C++11 + CHECK_MINIMAL_CXX_STANDARD(11 ENFORCE) +else() + # Boost.Math will be C++14 starting in July 2023 (Boost 1.82 release) + CHECK_MINIMAL_CXX_STANDARD(14 ENFORCE) +endif() + # Optional dependencies ADD_PROJECT_DEPENDENCY(octomap PKG_CONFIG_REQUIRES "octomap >= 1.6") if(octomap_FOUND) @@ -119,10 +138,10 @@ if(octomap_FOUND) list(GET VERSION_LIST 0 OCTOMAP_MAJOR_VERSION) list(GET VERSION_LIST 1 OCTOMAP_MINOR_VERSION) list(GET VERSION_LIST 2 OCTOMAP_PATCH_VERSION) - message(STATUS "FCL uses Octomap") + message(STATUS "HPP-FCL uses Octomap") else() SET(HPP_FCL_HAS_OCTOMAP FALSE) - message(STATUS "FCL does not use Octomap") + message(STATUS "HPP-FCL does not use Octomap") endif() option(HPP_FCL_HAS_QHULL "use qhull library to compute convex hulls." FALSE) @@ -130,6 +149,7 @@ if(HPP_FCL_HAS_QHULL) find_package(Qhull COMPONENTS qhull_r qhullcpp) if(Qhull_FOUND) set(HPP_FCL_USE_SYSTEM_QHULL TRUE) + message(STATUS "HPP-FCL uses system Qhull") else() message(STATUS "Qhullcpp not found: it will be build from sources, if Qhull_r is found") file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/third-parties) diff --git a/README.md b/README.md index ba47568..f1bde0d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -HPP-FCL — An extension of the Flexible Collision Library -======= +# HPP-FCL — An extension of the Flexible Collision Library

Pipeline status @@ -8,33 +7,49 @@ HPP-FCL — An extension of the Flexible Collision Library Conda Downloads Conda Version PyPI version + black + ruff

[FCL](https://github.com/flexible-collision-library/fcl) was forked in 2015. Since then, a large part of the code has been rewritten or removed (for the unused and untested part). -The broadphase was reintroduced by J. Carpentier in 2022 based on the FCL version 0.7.0. +The broad phase was reintroduced by J. Carpentier in 2022 based on the FCL version 0.7.0. ## New features Compared to the original [FCL](https://github.com/flexible-collision-library/fcl) library, the main new features are: - a dedicated and efficient implementation of the GJK algorithm (we do not rely anymore on [libccd](https://github.com/danfis/libccd)) - the support of safety margins for collision detection -- an accelerated version of Collision Detection *à la Nesterov* which leads to increased performances (up to a factor 2). More details are available in this [paper](https://hal.archives-ouvertes.fr/hal-03662157/) -- the computation of a lower bound of the distance between two objects when collision checking is performed and no collision is found +- an accelerated version of collision detection *à la Nesterov*, which leads to increased performances (up to a factor of 2). More details are available in this [paper](https://hal.archives-ouvertes.fr/hal-03662157/) +- the computation of a lower bound of the distance between two objects when collision checking is performed, and no collision is found - the implementation of Python bindings for easy code prototyping - the support of height fields, capsule shapes, etc. - the fix of various bugs -This project is now used in many robotics frameworks such as [Pinocchio](https://github.com/stack-of-tasks/pinocchio), an open-source software which implements efficient and versatile rigid body dynamics algorithms and the [Humanoid Path Planner](https://humanoid-path-planner.github.io/hpp-doc), an open-source software for Motion and Manipulation Planning. +This project is now used in many robotics frameworks such as [Pinocchio](https://github.com/stack-of-tasks/pinocchio), an open-source software that implements efficient and versatile rigid body dynamics algorithms and the [Humanoid Path Planner](https://humanoid-path-planner.github.io/hpp-doc), an open-source software for Motion and Manipulation Planning. -## Performances +## A high-performance library -Unlike the original FCL library, HPP-FCL implements the well-established GJK algorithm and [its variants](https://hal.archives-ouvertes.fr/hal-03662157/) for collision detection and distance computation. These implementations lead to state-of-the-art performances, as depicted by the figure below. In particular, you can observe that GJK-based approaches largely outperform solutions based on classic optimization solvers (e.g., QP solver like [ProxQP](https://github.com/Simple-Robotics/proxsuite)), notably for large geometries composed of tens or hundred of vertices. +Unlike the original FCL library, HPP-FCL implements the well-established [GJK algorithm](https://en.wikipedia.org/wiki/Gilbert%E2%80%93Johnson%E2%80%93Keerthi_distance_algorithm) and [its variants](https://hal.archives-ouvertes.fr/hal-03662157/) for collision detection and distance computation. These implementations lead to state-of-the-art performances, as depicted by the figures below. + +On the one hand, we have benchmarked HPP-FCL against major software alternatives of the state of the art: +1. the [Bullet simulator](https://github.com/bulletphysics/bullet3), +2. the original [FCL library](https://github.com/flexible-collision-library/fcl) (used in the [Drake framework]()), +3. the [libccd library](https://github.com/danfis/libccd) (used in [MuJoCo](http://mujoco.org/)). + +The results are depicted in the following figure, which notably shows that the accelerated variants of GJK largely outperform by a large margin (from 5x up to 15x times faster). + +

+ HPP-FCL vs the rest of the world +

+ +On the other hand, why do we care about dedicated collision detection solvers like GJK for the narrow phase? Why can't we simply formulate the collision detection problem as a quadratic problem and call an off-the-shelf optimization solver like [ProxQP](https://github.com/Simple-Robotics/proxsuite))? Here is why.

- HPP-FCL performances + HPP-FCL vs generic QP solvers

+One can observe that GJK-based approaches largely outperform solutions based on classic optimization solvers (e.g., QP solver like [ProxQP](https://github.com/Simple-Robotics/proxsuite)), notably for large geometries composed of tens or hundreds of vertices. + ## Acknowledgments The development of **HPP-FCL** is actively supported by the [Gepetto team](http://projects.laas.fr/gepetto/) [@LAAS-CNRS](http://www.laas.fr), the [Willow team](https://www.di.ens.fr/willow/) [@INRIA](http://www.inria.fr) and, to some extend, [Eureka Robotics](https://eurekarobotics.com/). - diff --git a/cmake/.cmake-format.py b/cmake/.cmake-format.py new file mode 100644 index 0000000..39d77f8 --- /dev/null +++ b/cmake/.cmake-format.py @@ -0,0 +1,240 @@ +# ---------------------------------- +# Options affecting listfile parsing +# ---------------------------------- +with section("parse"): + + # Specify structure for custom cmake functions + additional_commands = { 'foo': { 'flags': ['BAR', 'BAZ'], + 'kwargs': {'DEPENDS': '*', 'HEADERS': '*', 'SOURCES': '*'}}} + + # Override configurations per-command where available + override_spec = {} + + # Specify variable tags. + vartags = [] + + # Specify property tags. + proptags = [] + +# ----------------------------- +# Options affecting formatting. +# ----------------------------- +with section("format"): + + # Disable formatting entirely, making cmake-format a no-op + disable = False + + # How wide to allow formatted cmake files + line_width = 80 + + # How many spaces to tab for indent + tab_size = 2 + + # If true, lines are indented using tab characters (utf-8 0x09) instead of + # space characters (utf-8 0x20). In cases where the layout would + # require a fractional tab character, the behavior of the fractional + # indentation is governed by + use_tabchars = False + + # If is True, then the value of this variable indicates how + # fractional indentions are handled during whitespace replacement. If set to + # 'use-space', fractional indentation is left as spaces (utf-8 0x20). If set + # to `round-up` fractional indentation is replaced with a single tab character + # (utf-8 0x09) effectively shifting the column to the next tabstop + fractional_tab_policy = 'use-space' + + # If an argument group contains more than this many sub-groups (parg or kwarg + # groups) then force it to a vertical layout. + max_subgroups_hwrap = 2 + + # If a positional argument group contains more than this many arguments, then + # force it to a vertical layout. + max_pargs_hwrap = 6 + + # If a cmdline positional group consumes more than this many lines without + # nesting, then invalidate the layout (and nest) + max_rows_cmdline = 2 + + # If true, separate flow control names from their parentheses with a space + separate_ctrl_name_with_space = False + + # If true, separate function names from parentheses with a space + separate_fn_name_with_space = False + + # If a statement is wrapped to more than one line, than dangle the closing + # parenthesis on its own line. + dangle_parens = False + + # If the trailing parenthesis must be 'dangled' on its on line, then align it + # to this reference: `prefix`: the start of the statement, `prefix-indent`: + # the start of the statement, plus one indentation level, `child`: align to + # the column of the arguments + dangle_align = 'prefix' + + # If the statement spelling length (including space and parenthesis) is + # smaller than this amount, then force reject nested layouts. + min_prefix_chars = 4 + + # If the statement spelling length (including space and parenthesis) is larger + # than the tab width by more than this amount, then force reject un-nested + # layouts. + max_prefix_chars = 10 + + # If a candidate layout is wrapped horizontally but it exceeds this many + # lines, then reject the layout. + max_lines_hwrap = 2 + + # What style line endings to use in the output. + line_ending = 'unix' + + # Format command names consistently as 'lower' or 'upper' case + command_case = 'canonical' + + # Format keywords consistently as 'lower' or 'upper' case + keyword_case = 'unchanged' + + # A list of command names which should always be wrapped + always_wrap = [] + + # If true, the argument lists which are known to be sortable will be sorted + # lexicographicall + enable_sort = True + + # If true, the parsers may infer whether or not an argument list is sortable + # (without annotation). + autosort = False + + # By default, if cmake-format cannot successfully fit everything into the + # desired linewidth it will apply the last, most agressive attempt that it + # made. If this flag is True, however, cmake-format will print error, exit + # with non-zero status code, and write-out nothing + require_valid_layout = False + + # A dictionary mapping layout nodes to a list of wrap decisions. See the + # documentation for more information. + layout_passes = {} + +# ------------------------------------------------ +# Options affecting comment reflow and formatting. +# ------------------------------------------------ +with section("markup"): + + # What character to use for bulleted lists + bullet_char = '*' + + # What character to use as punctuation after numerals in an enumerated list + enum_char = '.' + + # If comment markup is enabled, don't reflow the first comment block in each + # listfile. Use this to preserve formatting of your copyright/license + # statements. + first_comment_is_literal = False + + # If comment markup is enabled, don't reflow any comment block which matches + # this (regex) pattern. Default is `None` (disabled). + literal_comment_pattern = None + + # Regular expression to match preformat fences in comments default= + # ``r'^\s*([`~]{3}[`~]*)(.*)$'`` + fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$' + + # Regular expression to match rulers in comments default= + # ``r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'`` + ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' + + # If a comment line matches starts with this pattern then it is explicitly a + # trailing comment for the preceeding argument. Default is '#<' + explicit_trailing_pattern = '#<' + + # If a comment line starts with at least this many consecutive hash + # characters, then don't lstrip() them off. This allows for lazy hash rulers + # where the first hash char is not separated by space + hashruler_min_length = 10 + + # If true, then insert a space between the first hash char and remaining hash + # chars in a hash ruler, and normalize its length to fill the column + canonicalize_hashrulers = True + + # enable comment markup parsing and reflow + enable_markup = True + +# ---------------------------- +# Options affecting the linter +# ---------------------------- +with section("lint"): + + # a list of lint codes to disable + disabled_codes = [] + + # regular expression pattern describing valid function names + function_pattern = '[0-9a-z_]+' + + # regular expression pattern describing valid macro names + macro_pattern = '[0-9A-Z_]+' + + # regular expression pattern describing valid names for variables with global + # (cache) scope + global_var_pattern = '[A-Z][0-9A-Z_]+' + + # regular expression pattern describing valid names for variables with global + # scope (but internal semantic) + internal_var_pattern = '_[A-Z][0-9A-Z_]+' + + # regular expression pattern describing valid names for variables with local + # scope + local_var_pattern = '[a-z][a-z0-9_]+' + + # regular expression pattern describing valid names for privatedirectory + # variables + private_var_pattern = '_[0-9a-z_]+' + + # regular expression pattern describing valid names for public directory + # variables + public_var_pattern = '[A-Z][0-9A-Z_]+' + + # regular expression pattern describing valid names for function/macro + # arguments and loop variables. + argument_var_pattern = '[a-z][a-z0-9_]+' + + # regular expression pattern describing valid names for keywords used in + # functions or macros + keyword_pattern = '[A-Z][0-9A-Z_]+' + + # In the heuristic for C0201, how many conditionals to match within a loop in + # before considering the loop a parser. + max_conditionals_custom_parser = 2 + + # Require at least this many newlines between statements + min_statement_spacing = 1 + + # Require no more than this many newlines between statements + max_statement_spacing = 2 + max_returns = 6 + max_branches = 12 + max_arguments = 5 + max_localvars = 15 + max_statements = 50 + +# ------------------------------- +# Options affecting file encoding +# ------------------------------- +with section("encode"): + + # If true, emit the unicode byte-order mark (BOM) at the start of the file + emit_byteorder_mark = False + + # Specify the encoding of the input file. Defaults to utf-8 + input_encoding = 'utf-8' + + # Specify the encoding of the output file. Defaults to utf-8. Note that cmake + # only claims to support utf-8 so be careful when using anything else + output_encoding = 'utf-8' + +# ------------------------------------- +# Miscellaneous configurations options. +# ------------------------------------- +with section("misc"): + + # A dictionary containing any per-command configuration overrides. Currently + # only `command_case` is supported. + per_command = {} diff --git a/cmake/.github/workflows/cmake.yml b/cmake/.github/workflows/cmake.yml index cf1773c..96eb728 100644 --- a/cmake/.github/workflows/cmake.yml +++ b/cmake/.github/workflows/cmake.yml @@ -5,7 +5,7 @@ on: [push,pull_request] jobs: build-ubuntu: - runs-on: [ubuntu-18.04] + runs-on: [ubuntu-22.04] steps: - uses: actions/checkout@v1 @@ -28,11 +28,3 @@ jobs: CMAKE_BIN="cmake" $CMAKE_BIN --version $CMAKE_BIN -P test_pkg-config.cmake - - name="cmake-3.2.2-Linux-x86_64" - wget --quiet "https://cmake.org/files/v3.2/${name}.tar.gz" - tar xf "${name}.tar.gz" - - CMAKE_BIN="${name}/bin/cmake" - $CMAKE_BIN --version - $CMAKE_BIN -P test_pkg-config.cmake diff --git a/cmake/.pre-commit-config.yaml b/cmake/.pre-commit-config.yaml index d916008..3db8053 100644 --- a/cmake/.pre-commit-config.yaml +++ b/cmake/.pre-commit-config.yaml @@ -5,12 +5,12 @@ repos: - id: check-useless-excludes - id: check-hooks-apply - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v15.0.7 + rev: v17.0.5 hooks: - id: clang-format args: [--style=Google] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-ast @@ -25,13 +25,21 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.11.0 hooks: - id: black + exclude: | + (?x)^( + .cmake-format.py + )$ - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 + exclude: | + (?x)^( + .cmake-format.py + )$ - repo: https://github.com/cheshirekow/cmake-format-precommit rev: v0.6.13 hooks: diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in index 6f840d2..274c9fc 100644 --- a/cmake/Config.cmake.in +++ b/cmake/Config.cmake.in @@ -142,7 +142,11 @@ foreach(component ${@PROJECT_NAME@_FIND_COMPONENTS}) if(@PROJECT_NAME@_${component}_FOUND) message(STATUS "@PROJECT_NAME@: ${component} found.") else() - message(STATUS "@PROJECT_NAME@: ${component} not found.") + if(@PROJECT_NAME@_FIND_REQUIRED_${component}) + message(FATAL_ERROR "@PROJECT_NAME@: ${component} not found.") + else() + message(STATUS "@PROJECT_NAME@: ${component} not found.") + endif() endif() endforeach() check_required_components("@PROJECT_NAME@") diff --git a/cmake/GNUInstallDirs.cmake b/cmake/GNUInstallDirs.cmake index f95e6e2..fbf56b8 100644 --- a/cmake/GNUInstallDirs.cmake +++ b/cmake/GNUInstallDirs.cmake @@ -124,9 +124,6 @@ Macros absolute paths where necessary, using the same logic. #]=======================================================================] -cmake_policy(PUSH) -cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced - # Convert a cache variable to PATH type macro(_GNUInstallDirs_cache_convert_to_path var description) @@ -380,5 +377,3 @@ foreach(dir ) GNUInstallDirs_get_absolute_install_dir(CMAKE_INSTALL_FULL_${dir} CMAKE_INSTALL_${dir}) endforeach() - -cmake_policy(POP) diff --git a/cmake/README.md b/cmake/README.md index 0380943..824ca55 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -1,10 +1,10 @@ -Shared CMake submodule -====================== +# Shared CMake submodule [![Documentation Status](https://readthedocs.org/projects/jrl-cmakemodules/badge/?version=master)](https://jrl-cmakemodules.readthedocs.io/en/master/?badge=master) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/jrl-umi3218/jrl-cmakemodules/master.svg)](https://results.pre-commit.ci/latest/github/jrl-umi3218/jrl-cmakemodules/master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + This repository is meant to be used as a submodule for any project from CNRS LAAS/HPP or JRL. @@ -20,7 +20,11 @@ You can also checkout the more complete [documentation] of the modules. [documentation]: http://jrl-cmakemodules.readthedocs.io/en/master/ -# pre-commit +## Supported CMake versions + +We currently support CMake >= 3.10 + +## pre-commit This project use [pre-commit](https://pre-commit.com) and [pre-commit.ci](https://pre-commit.ci). diff --git a/cmake/_unittests/test_pkg-config.cmake b/cmake/_unittests/test_pkg-config.cmake index 97bae31..ca33930 100644 --- a/cmake/_unittests/test_pkg-config.cmake +++ b/cmake/_unittests/test_pkg-config.cmake @@ -1,6 +1,5 @@ include(cmake/pkg-config.cmake) -cmake_policy(SET CMP0054 NEW) # cmake -P macro(EXPECT_STREQUAL _lhs _rhs) if(NOT "${_lhs}" STREQUAL ${_rhs}) diff --git a/cmake/base.cmake b/cmake/base.cmake index fed3c94..f53544f 100644 --- a/cmake/base.cmake +++ b/cmake/base.cmake @@ -103,6 +103,10 @@ # # This variable provides the full path pointing to the JRL cmake module. # +# .. variable:: PROJECT_JRL_CMAKE_BINARY_DIR +# +# This variable provides the full path pointing to the JRL cmake binary dir. +# # .. variable:: PROJECT_COMPATIBILITY_VERSION # # If set, this variable defines COMPATIBILITY version of the project @@ -115,10 +119,21 @@ # ------ # +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.10) + message( + FATAL_ERROR + "JRL-CMakemodules require CMake >= 3.10. Please update your main 'cmake_minimum_required'" + ) +endif() + set(PROJECT_JRL_CMAKE_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "") +set(PROJECT_JRL_CMAKE_BINARY_DIR + ${CMAKE_CURRENT_BINARY_DIR} + CACHE INTERNAL "") + # Please note that functions starting with an underscore are internal functions # and should not be used directly. @@ -142,6 +157,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/oscheck.cmake) include(${CMAKE_CURRENT_LIST_DIR}/cxx-standard.cmake) include(${CMAKE_CURRENT_LIST_DIR}/coverage.cmake) include(${CMAKE_CURRENT_LIST_DIR}/modernize-links.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/relpath.cmake) # --------- # Constants # --------- # @@ -206,11 +222,14 @@ variable_watch(CMAKE_CURRENT_LIST_DIR SETUP_PROJECT_FINALIZE_HOOK) function(SETUP_PROJECT_FINALIZE_HOOK VARIABLE ACCESS) if("${${VARIABLE}}" STREQUAL "") set(CMAKE_CURRENT_LIST_DIR ${PROJECT_JRL_CMAKE_MODULE_DIR}) + set(JRL_CMAKEMODULE_LOGGING_FILENAME + "${PROJECT_JRL_CMAKE_BINARY_DIR}/config.log") setup_project_finalize() if(PROJECT_USE_CMAKE_EXPORT) setup_project_package_finalize() endif() set(CMAKE_CURRENT_LIST_DIR "") # restore value + set(JRL_CMAKEMODULE_LOGGING_FILENAME "") # restore value endif() endfunction() @@ -245,19 +264,9 @@ endmacro( # LIST : the list. VALUE : the value to be appended. # macro(_ADD_TO_LIST_IF_NOT_PRESENT LIST VALUE) - if(CMAKE_VERSION VERSION_GREATER "3.3.0") - cmake_policy(PUSH) - cmake_policy(SET CMP0057 NEW) - # To be more robust, value should be stripped - if(NOT "${VALUE}" IN_LIST ${LIST}) - list(APPEND ${LIST} "${VALUE}") - endif() - cmake_policy(POP) - else() - list(FIND LIST "${VALUE}" _index) - if(${_index} EQUAL -1) - list(APPEND LIST "${VALUE}") - endif() + # To be more robust, value should be stripped + if(NOT "${VALUE}" IN_LIST ${LIST}) + list(APPEND ${LIST} "${VALUE}") endif() endmacro( _ADD_TO_LIST_IF_NOT_PRESENT @@ -343,20 +352,39 @@ macro(COMPUTE_PROJECT_ARGS _project_VARIABLE) set(_project_LANGUAGES "CXX") endif() - if(CMAKE_VERSION VERSION_GREATER "3.0.0") - # CMake >= 3.0 - cmake_policy(SET CMP0048 NEW) - set(${_project_VARIABLE} VERSION ${PROJECT_VERSION_FULL} LANGUAGES - ${_project_LANGUAGES}) + set(${_project_VARIABLE} + VERSION ${PROJECT_VERSION_FULL} LANGUAGES ${_project_LANGUAGES} + DESCRIPTION ${PROJECT_DESCRIPTION}) +endmacro(COMPUTE_PROJECT_ARGS) + +# .rst: .. ifmode:: user +# +# .. command:: SET_DEFAULT_CMAKE_BUILD_TYPE +# (Release|Debug|RelWithDebInfo|MinSizeRel) +# +# Set the default value of CMAKE_BUILD_TYPE if it is not already defined by the +# user. +# +macro(SET_DEFAULT_CMAKE_BUILD_TYPE build_type) + string(TOLOWER "${build_type}" build_type_lower) - # Append description for CMake >= 3.9 - if(CMAKE_VERSION VERSION_GREATER "3.9.0") - set(${_project_VARIABLE} ${${_project_VARIABLE}} DESCRIPTION - ${PROJECT_DESCRIPTION}) - endif(CMAKE_VERSION VERSION_GREATER "3.9.0") - else(CMAKE_VERSION VERSION_GREATER "3.0.0") + if(NOT "${build_type_lower}" MATCHES + "(debug)|(release)|(relwithdebinfo)|(minsizerel)") + message( + FATAL_ERROR + "${build_type} value does not match with Debug, Release, RelWithDebInfo or MinSizeRel" + ) + endif() - # CMake < 3.0 - set(${_project_VARIABLE} ${_project_LANGUAGES}) - endif(CMAKE_VERSION VERSION_GREATER "3.0.0") -endmacro(COMPUTE_PROJECT_ARGS) + if(NOT CMAKE_BUILD_TYPE + AND NOT CMAKE_CONFIGURATION_TYPES + AND NOT DEFINED ENV{CMAKE_BUILD_TYPE}) + set(CMAKE_BUILD_TYPE + ${build_type} + CACHE STRING "Choose the build type value." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" + "RelWithDebInfo" "MinSizeRel") + message( + STATUS "CMAKE_BUILD_TYPE has automatically been set to ${build_type}") + endif() +endmacro(SET_DEFAULT_CMAKE_BUILD_TYPE) diff --git a/cmake/boost/FindBoost.cmake b/cmake/boost/FindBoost.cmake index 38b0d00..d655d2a 100644 --- a/cmake/boost/FindBoost.cmake +++ b/cmake/boost/FindBoost.cmake @@ -384,8 +384,6 @@ Set ``Boost_NO_BOOST_CMAKE`` to ``ON``, to disable the search for boost-cmake. include(FindPackageHandleStandardArgs) # Save project's policies -cmake_policy(PUSH) -cmake_policy(SET CMP0057 NEW) # if IN_LIST if(POLICY CMP0102) cmake_policy(SET CMP0102 NEW) # if mark_as_advanced(non_cache_var) endif() @@ -2592,6 +2590,3 @@ list(REMOVE_DUPLICATES _Boost_COMPONENTS_SEARCHED) list(SORT _Boost_COMPONENTS_SEARCHED) set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}" CACHE INTERNAL "Components requested for this build tree.") - -# Restore project's policies -cmake_policy(POP) diff --git a/cmake/cmake_reinstall.cmake.in b/cmake/cmake_reinstall.cmake.in index fae9569..3b98bef 100644 --- a/cmake/cmake_reinstall.cmake.in +++ b/cmake/cmake_reinstall.cmake.in @@ -12,7 +12,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -cmake_policy(SET CMP0007 NEW) if(EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") execute_process(COMMAND "@CMAKE_COMMAND@" --build "@PROJECT_BINARY_DIR@" --target uninstall --config $) endif() diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in index 2d6c62c..1dd0999 100644 --- a/cmake/cmake_uninstall.cmake.in +++ b/cmake/cmake_uninstall.cmake.in @@ -14,7 +14,6 @@ # along with this program. If not, see . # # This files comes from the CMake FAQ: http://www.cmake.org/Wiki/CMake_FAQ -cmake_policy(SET CMP0007 NEW) IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") RETURN() ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") diff --git a/cmake/compile.py b/cmake/compile.py deleted file mode 100755 index 8f63506..0000000 --- a/cmake/compile.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008-2014 LAAS-CNRS, JRL AIST-CNRS. -# -# This file is part of jrl-cmakemodules. -# jrl-cmakemodules is free software: you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public License -# as published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# jrl-cmakemodules is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Lesser Public License for more details. You should have -# received a copy of the GNU Lesser General Public License along with -# jrl-cmakemodules. If not, see . - -import sys -import py_compile - -srcdir = sys.argv[1] -builddir = sys.argv[2] -name = sys.argv[3] - -if srcdir[-1] != "/": - srcdir = srcdir + "/" -if builddir[-1] != "/": - builddir = builddir + "/" - -src = srcdir + name -comp = builddir + name + (__debug__ and "c" or "o") - -# print("compiling " + src + " into " + comp) - -# os.mkdir(os.path.splittext(comp)[0]) - -try: - py_compile.compile(src, comp, doraise=True) -except Exception as e: - print("Failed to compile python script: {0}".format(repr(src))) - print("Exception raised: {0}".format(str(e))) - sys.exit(1) diff --git a/cmake/cxx-standard.cmake b/cmake/cxx-standard.cmake index 6808507..ff3ebfd 100644 --- a/cmake/cxx-standard.cmake +++ b/cmake/cxx-standard.cmake @@ -147,6 +147,15 @@ macro(CHECK_MINIMAL_CXX_STANDARD STANDARD) STATUS "C++ standard sufficient: Minimal required ${_MINIMAL_CXX_STANDARD}, currently defined: ${_CURRENT_STANDARD}" ) + # current C++ standard was not set in CMake but we enforce it + if(NOT DEFINED CMAKE_CXX_STANDARD AND (ENFORCE_MINIMAL_CXX_STANDARD + OR MINIMAL_CXX_STANDARD_ENFORCE)) + message( + STATUS + "CMAKE_CXX_STANDARD was not set: automatically set to currently defined standard ${_CURRENT_STANDARD}" + ) + set(CMAKE_CXX_STANDARD ${_CURRENT_STANDARD}) + endif() endif() # requested minimum is higher than the currently selected endif() endmacro() diff --git a/cmake/cxx11.cmake b/cmake/cxx11.cmake index 8c6779a..a3b623d 100644 --- a/cmake/cxx11.cmake +++ b/cmake/cxx11.cmake @@ -13,52 +13,16 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see . -include(CheckCXXCompilerFlag) - -# .rst: .. ifmode:: user -# -# .. command:: CHECK_CXX11_SUPPORT -# -# Set ouput variable CXX11_SUPPORTED to TRUE if C++11 is supported by the -# current compiler. Set to FALSE otherwise. -# -function(CHECK_CXX11_SUPPORT CXX11_SUPPORTED) - check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X) - check_cxx_compiler_flag("-std=c++11" COMPILER_SUPPORTS_CXX11) - - if(COMPILER_SUPPORTS_CXX0X OR COMPILER_SUPPORTS_CXX11) - set(${CXX11_SUPPORTED} - TRUE - PARENT_SCOPE) - else() - set(${CXX11_SUPPORTED} - FALSE - PARENT_SCOPE) - endif() -endfunction(CHECK_CXX11_SUPPORT) - # .rst: .. ifmode:: user # # .. command:: PROJECT_USE_CXX11 # -# This macro set up the project to compile the whole project with C++11 -# standards. +# DEPRECATED. This macro set up the project to compile the whole project with +# C++11 standards. # macro(PROJECT_USE_CXX11) - check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X) - check_cxx_compiler_flag("-std=c++11" COMPILER_SUPPORTS_CXX11) - if(COMPILER_SUPPORTS_CXX0X OR COMPILER_SUPPORTS_CXX11) - if(CMAKE_VERSION VERSION_LESS "3.1") - if(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") - elseif(COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - endif() - else() - set(CMAKE_CXX_STANDARD 11) - set(CXX_STANDARD_REQUIRED ON) - endif() - else() - message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support.") - endif() + message( + DEPRECATION + "This macro is deprecated. Use CHECK_MINIMAL_CXX_STANDARD instead.") + check_minimal_cxx_standard(11 REQUIRED) endmacro(PROJECT_USE_CXX11) diff --git a/cmake/cython/cython.cmake b/cmake/cython/cython.cmake index 224233d..e6015d6 100644 --- a/cmake/cython/cython.cmake +++ b/cmake/cython/cython.cmake @@ -37,6 +37,54 @@ set(CYTHON_SETUP_IN_PY_LOCATION "${CMAKE_CURRENT_LIST_DIR}/setup.in.py") set(CYTHON_DUMMY_CPP_LOCATION "${CMAKE_CURRENT_LIST_DIR}/dummy.cpp") set(PYTHON_EXTRA_CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/python") +# Find the Python packages required depending on binding options +macro(_setup_python_for_cython) + # FindPython(2|3).cmake only exists from CMake 3.12 + if(${CMAKE_VERSION} VERSION_LESS "3.12.0") + list(APPEND CMAKE_MODULE_PATH ${PYTHON_EXTRA_CMAKE_MODULE_PATH}) + endif() + set(PYTHON_BINDING_VERSIONS) + if(PYTHON_BINDING) + if(PYTHON_BINDING_FORCE_PYTHON2 OR PYTHON_BINDING_BUILD_PYTHON2_AND_PYTHON3) + list(APPEND PYTHON_BINDING_VERSIONS Python2) + endif() + if(PYTHON_BINDING_FORCE_PYTHON3 OR PYTHON_BINDING_BUILD_PYTHON2_AND_PYTHON3) + list(APPEND PYTHON_BINDING_VERSIONS Python3) + endif() + list(LENGTH PYTHON_BINDING_VERSIONS N_PYTHON_BINDING_VERSIONS) + if(N_PYTHON_BINDING_VERSIONS EQUAL 0) + list(APPEND PYTHON_BINDING_VERSIONS Python) + # Recent CMake always favor Python 3 but we really want the system's + # default Python in that case + if(NOT DEFINED Python_EXECUTABLE) + find_program(DEFAULT_PYTHON_EXECUTABLE python) + if(DEFAULT_PYTHON_EXECUTABLE) + set(Python_EXECUTABLE ${DEFAULT_PYTHON_EXECUTABLE}) + endif() + endif() + endif() + foreach(PYTHON_VERSION ${PYTHON_BINDING_VERSIONS}) + # CMake favors the most recent version it can find on the system but we + # really mean to pick the default "python3" if availble + if(PYTHON_VERSION STREQUAL "Python3" AND NOT DEFINED Python3_EXECUTABLE) + find_program(DEFAULT_PYTHON3_EXECUTABLE python3) + if(DEFAULT_PYTHON3_EXECUTABLE) + set(Python3_EXECUTABLE ${DEFAULT_PYTHON3_EXECUTABLE}) + endif() + endif() + # Same for python2 + if(PYTHON_VERSION STREQUAL "Python2" AND NOT DEFINED Python2_EXECUTABLE) + find_program(DEFAULT_PYTHON2_EXECUTABLE python2) + if(DEFAULT_PYTHON2_EXECUTABLE) + set(Python2_EXECUTABLE ${DEFAULT_PYTHON2_EXECUTABLE}) + endif() + endif() + find_package(${PYTHON_VERSION} REQUIRED COMPONENTS Interpreter + Development NumPy) + endforeach() + endif() +endmacro() + # This macro adds a dummy shared library target to extract compilation flags # from an interface library macro(_CYTHON_DUMMY_TARGET TARGET) @@ -476,3 +524,264 @@ macro(GET_PYTHON_NAMES VAR) list(APPEND ${VAR} Python) endif() endmacro() + +# .rst: .. command:: MAKE_CYTHON_BINDINGS(PACKAGE TARGETS targets... [VERSION +# version] [MODULES modules...] [EXPORT_SOURCES sources...] [PRIVATE_SOURCES +# ...] [GENERATE_SOURCES ...]) +# +# This function adds cython bindings using one or more libraries built by the +# project. It is similar to ADD_CYTHON_BINDINGS but the process is entirely +# handled by CMake which gives us better incremental builds. +# +# For each module ``a.b.c`` it creates a target name ``c_${PYTHON_VERSION}`` for +# each ``PYTHON_VERSION`` in ``PYTHON_BINDING_VERSIONS`` +# +# :PACKAGE: Name of the Python package +# +# :TARGETS: Name of the targets that the bindings should link to +# +# :VERSION: Version of the bindings, defaults to ``PROJECT_VERSION`` +# +# :MODULES: Python modules built by this macro call. Defaults to +# ``PACKAGE.PACKAGE`` +# +# :EXPORT_SOURCES: Sources that will be installed along with the package +# (typically, public pxd files and __init__.py) +# +# :PRIVATE_SOURCES: Sources that are needed to built the package but will not +# be installed +# +# :GENERATE_SOURCES: Sources that will be configured and then generated in the +# correct location, the generated files are installed unless they are test files +# +# The macro will generate a setup.py script in +# ``$CMAKE_CURRENT_BINARY_DIR/$PACKAGE/$PYTHON/$`` and copy the +# provided sources in this location. Relative paths are preferred to provide +# sources but one can use absolute paths if and only if the absolute path starts +# with ``$CMAKE_CURRENT_BINARY_DIR`` +# +function(MAKE_CYTHON_BINDINGS PACKAGE) + set(options) + set(oneValueArgs VERSION) + set(multiValueArgs MODULES TARGETS EXPORT_SOURCES PRIVATE_SOURCES + GENERATE_SOURCES) + cmake_parse_arguments(CYTHON_BINDINGS "${options}" "${oneValueArgs}" + "${multiValueArgs}" ${ARGN}) + if(NOT DEFINED CYTHON_BINDINGS_VERSION) + set(CYTHON_BINDINGS_VERSION ${PROJECT_VERSION}) + endif() + if(NOT DEFINED CYTHON_BINDINGS_EXPORT_SOURCES) + set(CYTHON_BINDINGS_EXPORT_SOURCES) + endif() + if(NOT DEFINED CYTHON_BINDINGS_PRIVATE_SOURCES) + set(CYTHON_BINDINGS_PRIVATE_SOURCES) + endif() + if(NOT DEFINED CYTHON_BINDINGS_GENERATE_SOURCES) + set(CYTHON_BINDINGS_GENERATE_SOURCES) + endif() + if(NOT DEFINED CYTHON_BINDINGS_MODULES) + set(CYTHON_BINDINGS_MODULES "${PACKAGE}.${PACKAGE}") + endif() + if(NOT DEFINED CYTHON_BINDINGS_TARGETS) + message( + FATAL_ERROR + "Error in ADD_CYTHON_BINDINGS, bindings should depend on at least one target" + ) + endif() + set(CYTHON_BINDINGS_SOURCES) + list(APPEND CYTHON_BINDINGS_SOURCES ${CYTHON_BINDINGS_EXPORT_SOURCES}) + list(APPEND CYTHON_BINDINGS_SOURCES ${CYTHON_BINDINGS_PRIVATE_SOURCES}) + list(APPEND CYTHON_BINDINGS_SOURCES ${CYTHON_BINDINGS_GENERATE_SOURCES}) + set(CYTHON_BINDINGS_COMPILE_SOURCES) + set(WITH_TESTS False) + foreach(SRC ${CYTHON_BINDINGS_SOURCES}) + if(${SRC} MATCHES "^tests/") + set(WITH_TESTS True) + endif() + if(${SRC} MATCHES ".pyx$") + list(APPEND CYTHON_BINDINGS_COMPILE_SOURCES ${SRC}) + endif() + endforeach() + add_library(_cython_dummy_${PACKAGE} SHARED EXCLUDE_FROM_ALL + "${CYTHON_DUMMY_CPP_LOCATION}") + target_link_libraries(_cython_dummy_${PACKAGE} + INTERFACE ${CYTHON_BINDINGS_TARGETS}) + set_target_properties(_cython_dummy_${PACKAGE} PROPERTIES FOLDER + "bindings/details") + foreach(PYTHON ${PYTHON_BINDING_VERSIONS}) + set(PACKAGE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}/$/${PACKAGE}) + if(DEFINED PYTHON_DEB_ROOT) + execute_process( + COMMAND + ${${PYTHON}_EXECUTABLE} -c + "from distutils import sysconfig; print(sysconfig.get_python_lib(plat_specific = True, standard_lib = False))" + RESULT_VARIABLE PYTHON_INSTALL_DESTINATION_FOUND + OUTPUT_VARIABLE PYTHON_INSTALL_DESTINATION + OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + execute_process( + COMMAND + ${${PYTHON}_EXECUTABLE} -c + "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix = '${CMAKE_INSTALL_PREFIX}', plat_specific = True))" + RESULT_VARIABLE PYTHON_INSTALL_DESTINATION_FOUND + OUTPUT_VARIABLE PYTHON_INSTALL_DESTINATION + OUTPUT_STRIP_TRAILING_WHITESPACE) + # Debian/Ubuntu has a specific problem here See + # https://github.com/mesonbuild/meson/issues/8739 for an overview of the + # problem + if(EXISTS /etc/debian_version) + execute_process( + COMMAND + ${${PYTHON}_EXECUTABLE} -c + "import sys; print(\"python{}.{}\".format(sys.version_info.major, sys.version_info.minor));" + OUTPUT_VARIABLE PYTHON_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REPLACE "python3/" "${PYTHON_VERSION}/" + PYTHON_INSTALL_DESTINATION + "${PYTHON_INSTALL_DESTINATION}") + endif() + endif() + foreach(F ${CYTHON_BINDINGS_GENERATE_SOURCES}) + configure_file(${F} ${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}/cmake/${F}) + file( + GENERATE + OUTPUT ${PACKAGE_OUTPUT_DIRECTORY}/${F} + INPUT ${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}/cmake/${F}) + endforeach() + foreach(F ${CYTHON_BINDINGS_EXPORT_SOURCES}) + file( + GENERATE + OUTPUT ${PACKAGE_OUTPUT_DIRECTORY}/${F} + INPUT ${CMAKE_CURRENT_SOURCE_DIR}/${F}) + endforeach() + foreach(F ${CYTHON_BINDINGS_PRIVATE_SOURCES}) + if(${F} MATCHES "^tests/") + file( + GENERATE + OUTPUT ${PACKAGE_OUTPUT_DIRECTORY}/${F} + INPUT ${CMAKE_CURRENT_SOURCE_DIR}/${F}) + endif() + endforeach() + install( + DIRECTORY ${PACKAGE_OUTPUT_DIRECTORY}/ + DESTINATION ${PYTHON_INSTALL_DESTINATION} + # We can't use PACKAGE_OUTPUT_DIRECTORY because it contains a + # generator-expression + REGEX "^${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}/[A-z]*/${PACKAGE}/tests.*" + EXCLUDE + PATTERN ".pytest_cache/*" EXCLUDE + PATTERN "__pycache__/*" EXCLUDE) + # Make an uninstall rule that: + # + # * Remove the installed module fully (including the empty directory) + # * Remove trace of bindings that could have been installed by + # add_cython_bindings in the past + set(UNINSTALL_TARGET_NAME uninstall-${PACKAGE}-${PYTHON}-bindings) + add_custom_target( + ${UNINSTALL_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -rf + ${PYTHON_INSTALL_DESTINATION}/${PACKAGE}*.dist-info) + add_dependencies(uninstall ${UNINSTALL_TARGET_NAME}) + if(WITH_TESTS AND BUILD_TESTING) + if(WIN32) + set(ENV_VAR "PATH") + set(PATH_SEP ";") + else() + set(ENV_VAR "LD_LIBRARY_PATH") + set(PATH_SEP ":") + endif() + set(EXTRA_LD_PATH "") + foreach(TGT ${CYTHON_BINDINGS_TARGETS}) + _is_interface_library(${TGT} IS_INTERFACE) + if(NOT ${IS_INTERFACE}) + set(EXTRA_LD_PATH + "$${PATH_SEP}${EXTRA_LD_PATH}") + endif() + endforeach() + add_test( + NAME test-${PACKAGE}-${PYTHON}-bindings + COMMAND + ${CMAKE_COMMAND} -E env "${ENV_VAR}=${EXTRA_LD_PATH}$ENV{${ENV_VAR}}" + ${CMAKE_COMMAND} -E env "PYTHONPATH=.${PATH_SEP}$ENV{PYTHONPATH}" + ${CMAKE_COMMAND} -E chdir "${PACKAGE_OUTPUT_DIRECTORY}" + ${${PYTHON}_EXECUTABLE} -m pytest) + endif() + foreach(MOD ${CYTHON_BINDINGS_MODULES}) + string(REPLACE "." "/" SRC ${MOD}) + set(SRC "${SRC}.pyx") + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}") + message( + FATAL_ERROR "Expected to find ${CMAKE_CURRENT_SOURCE_DIR}/${SRC}") + endif() + string(REGEX REPLACE ".pyx$" ".cpp" SRC_CPP ${SRC}) + string(REGEX REPLACE "/[^/]*$" "" SRC_DIR ${SRC}) + string(REGEX REPLACE "^(.*)\\..*$" "\\1" LIB_NAME ${MOD}) + string(REGEX REPLACE "\\." "_" LIB_NAME ${LIB_NAME}) + string(REGEX REPLACE "^.*\\.(.*)$" "\\1" LIB_OUTPUT_NAME ${MOD}) + string(REGEX REPLACE "^([^/]*)/.*$" "\\1" MOD_FOLDER ${SRC}) + set(MOD_OUTPUT_DIRECTORY ${PACKAGE_OUTPUT_DIRECTORY}/${SRC_DIR}) + set(CPP_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}/${SRC_DIR}) + set(CPP_OUT ${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}/${SRC_CPP}) + file(MAKE_DIRECTORY ${CPP_OUT_DIR}) + add_custom_command( + OUTPUT ${CPP_OUT} + COMMAND + ${${PYTHON}_EXECUTABLE} -m cython --cplus -o ${CPP_OUT} + "-I$>,;-I>" + -I${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC} + DEPENDS ${CYTHON_BINDINGS_SOURCES} ${CYTHON_BINDINGS_TARGETS} + COMMAND_EXPAND_LISTS) + set(TARGET_NAME ${LIB_NAME}_${PYTHON}) + if(${PYTHON} STREQUAL "Python") + python_add_library(${TARGET_NAME} MODULE ${CPP_OUT}) + elseif(${PYTHON} STREQUAL "Python2") + python2_add_library(${TARGET_NAME} MODULE ${CPP_OUT}) + elseif(${PYTHON} STREQUAL "Python3") + python3_add_library(${TARGET_NAME} MODULE ${CPP_OUT}) + else() + message(FATAL_ERROR "Unknown Python value: ${PYTHON}") + endif() + # Cython is likely to generate code that won't compile without warnings + if(UNIX AND NOT DEFINED CXX_DISABLE_WERROR) + target_compile_options(${TARGET_NAME} PRIVATE -Wno-error) + endif() + if(UNIX) + # Cython does a lot of casts that remove the const qualifier + target_compile_options(${TARGET_NAME} PRIVATE -Wno-cast-qual) + # Cython usually includes the deprecated NumPy API + target_compile_options(${TARGET_NAME} PRIVATE -Wno-cpp) + # Cython does some fishy conversions + target_compile_options(${TARGET_NAME} PRIVATE -Wno-conversion + -Wno-overflow) + # Generating API might look like unusued variables + target_compile_options(${TARGET_NAME} PRIVATE -Wno-unused-variable + -Wno-unused-function) + endif() + target_include_directories(${TARGET_NAME} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + target_include_directories( + ${TARGET_NAME} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/${PYTHON}) + target_link_libraries( + ${TARGET_NAME} PUBLIC ${CYTHON_BINDINGS_TARGETS} ${PYTHON}::Python + ${PYTHON}::NumPy) + set_target_properties( + ${TARGET_NAME} + PROPERTIES CXX_VISIBILITY_PRESET default + PREFIX "" + DEBUG_POSTFIX "" + OUTPUT_NAME ${LIB_OUTPUT_NAME} + LIBRARY_OUTPUT_DIRECTORY ${MOD_OUTPUT_DIRECTORY} + RUNTIME_OUTPUT_DIRECTORY ${MOD_OUTPUT_DIRECTORY}) + if(NOT TARGET ${UNINSTALL_TARGET_NAME}-${MOD_FOLDER}) + add_custom_target( + ${UNINSTALL_TARGET_NAME}-${MOD_FOLDER} + COMMAND ${CMAKE_COMMAND} -E rm -rf + ${PYTHON_INSTALL_DESTINATION}/${MOD_FOLDER}) + add_dependencies(${UNINSTALL_TARGET_NAME} + ${UNINSTALL_TARGET_NAME}-${MOD_FOLDER}) + endif() + endforeach() + endforeach() +endfunction() diff --git a/cmake/cython/python/FindPython/Support.cmake b/cmake/cython/python/FindPython/Support.cmake index 82f0493..e0590b3 100644 --- a/cmake/cython/python/FindPython/Support.cmake +++ b/cmake/cython/python/FindPython/Support.cmake @@ -5,7 +5,7 @@ # This file is a "template" file used by various FindPython modules. # -cmake_policy (VERSION 3.5) +cmake_policy (VERSION 3.10) # # Initial configuration diff --git a/cmake/doxygen.cmake b/cmake/doxygen.cmake index 6a9c08f..caaccd0 100644 --- a/cmake/doxygen.cmake +++ b/cmake/doxygen.cmake @@ -1,4 +1,4 @@ -# Copyright (C) 2008-2014 LAAS-CNRS, JRL AIST-CNRS. +# Copyright (C) 2008-2023 LAAS-CNRS, JRL AIST-CNRS, INRIA. # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software @@ -675,6 +675,7 @@ macro(_SETUP_PROJECT_DOCUMENTATION_FINALIZE) if(INSTALL_DOCUMENTATION) # Find doxytag files To ignore this list of tag files, set variable # DOXYGEN_TAGFILES + set(INSTALL_DOCDIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}) set(PKG_REQUIRES ${_PKG_CONFIG_REQUIRES}) list(APPEND PKG_REQUIRES ${_PKG_CONFIG_COMPILE_TIME_REQUIRES}) foreach(PKG_CONFIG_STRING ${PKG_REQUIRES}) @@ -683,7 +684,7 @@ macro(_SETUP_PROJECT_DOCUMENTATION_FINALIZE) # If DOXYGENDOCDIR is specified, add a doc path. if(DEFINED ${PREFIX}_DOXYGENDOCDIR AND EXISTS ${${PREFIX}_DOXYGENDOCDIR}/${LIBRARY_NAME}.doxytag) - file(RELATIVE_PATH DEP_DOCDIR ${_PKG_CONFIG_DOXYGENDOCDIR} + file(RELATIVE_PATH DEP_DOCDIR ${INSTALL_DOCDIR} ${${PREFIX}_DOXYGENDOCDIR}) set(DOXYGEN_TAGFILES_FROM_DEPENDENCIES diff --git a/cmake/find-external/OpenMP/FindOpenMP.cmake b/cmake/find-external/OpenMP/FindOpenMP.cmake index 506742a..4a66db9 100644 --- a/cmake/find-external/OpenMP/FindOpenMP.cmake +++ b/cmake/find-external/OpenMP/FindOpenMP.cmake @@ -1,23 +1,772 @@ -find_library(OpenMP_CXX_LIBRARY NAMES omp) +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. -find_path(OpenMP_CXX_INCLUDE_DIR omp.h) +#[=======================================================================[.rst: +FindOpenMP +---------- -mark_as_advanced(OpenMP_CXX_LIBRARY OpenMP_CXX_INCLUDE_DIR) +Finds Open Multi-Processing (OpenMP) support. + +This module can be used to detect OpenMP support in a compiler. If +the compiler supports OpenMP, the flags required to compile with +OpenMP support are returned in variables for the different languages. +The variables may be empty if the compiler does not need a special +flag to support OpenMP. + +.. versionadded:: 3.5 + Clang support. + +Variables +^^^^^^^^^ + +.. versionadded:: 3.10 + The module exposes the components ``C``, ``CXX``, and ``Fortran``. + Each of these controls the various languages to search OpenMP support for. + +Depending on the enabled components the following variables will be set: + +``OpenMP_FOUND`` + Variable indicating that OpenMP flags for all requested languages have been found. + If no components are specified, this is true if OpenMP settings for all enabled languages + were detected. +``OpenMP_VERSION`` + Minimal version of the OpenMP standard detected among the requested languages, + or all enabled languages if no components were specified. + +This module will set the following variables per language in your +project, where ```` is one of C, CXX, or Fortran: + +``OpenMP__FOUND`` + Variable indicating if OpenMP support for ```` was detected. +``OpenMP__FLAGS`` + OpenMP compiler flags for ````, separated by spaces. +``OpenMP__INCLUDE_DIRS`` + Directories that must be added to the header search path for ```` + when using OpenMP. + +For linking with OpenMP code written in ````, the following +variables are provided: + +``OpenMP__LIB_NAMES`` + :ref:`;-list ` of libraries for OpenMP programs for ````. +``OpenMP__LIBRARY`` + Location of the individual libraries needed for OpenMP support in ````. +``OpenMP__LIBRARIES`` + A list of libraries needed to link with OpenMP code written in ````. + +Additionally, the module provides :prop_tgt:`IMPORTED` targets: + +``OpenMP::OpenMP_`` + Target for using OpenMP from ````. + +Specifically for Fortran, the module sets the following variables: + +``OpenMP_Fortran_HAVE_OMPLIB_HEADER`` + Boolean indicating if OpenMP is accessible through ``omp_lib.h``. +``OpenMP_Fortran_HAVE_OMPLIB_MODULE`` + Boolean indicating if OpenMP is accessible through the ``omp_lib`` Fortran module. + +The module will also try to provide the OpenMP version variables: + +``OpenMP__SPEC_DATE`` + .. versionadded:: 3.7 + + Date of the OpenMP specification implemented by the ```` compiler. +``OpenMP__VERSION_MAJOR`` + Major version of OpenMP implemented by the ```` compiler. +``OpenMP__VERSION_MINOR`` + Minor version of OpenMP implemented by the ```` compiler. +``OpenMP__VERSION`` + OpenMP version implemented by the ```` compiler. + +The specification date is formatted as given in the OpenMP standard: +``yyyymm`` where ``yyyy`` and ``mm`` represents the year and month of +the OpenMP specification implemented by the ```` compiler. + +For some compilers, it may be necessary to add a header search path to find +the relevant OpenMP headers. This location may be language-specific. Where +this is needed, the module may attempt to find the location, but it can be +provided directly by setting the ``OpenMP__INCLUDE_DIR`` cache variable. +Note that this variable is an _input_ control to the module. Project code +should use the ``OpenMP__INCLUDE_DIRS`` _output_ variable if it needs +to know what include directories are needed. +#]=======================================================================] + +function(_OPENMP_FLAG_CANDIDATES LANG) + if(NOT OpenMP_${LANG}_FLAG) + unset(OpenMP_FLAG_CANDIDATES) + + set(OMP_FLAG_GNU "-fopenmp") + set(OMP_FLAG_LCC "-fopenmp") + set(OMP_FLAG_Clang "-fopenmp=libomp" "-fopenmp=libiomp5" "-fopenmp" + "-Xclang -fopenmp") + set(OMP_FLAG_AppleClang "-Xclang -fopenmp") + set(OMP_FLAG_HP "+Oopenmp") + if(WIN32) + set(OMP_FLAG_Intel "-Qopenmp") + elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "Intel" + AND "${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS + "15.0.0.20140528") + set(OMP_FLAG_Intel "-openmp") + else() + set(OMP_FLAG_Intel "-qopenmp") + endif() + if(CMAKE_${LANG}_COMPILER_ID STREQUAL "IntelLLVM" + AND "x${CMAKE_${LANG}_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC") + set(OMP_FLAG_IntelLLVM "-Qiopenmp") + else() + set(OMP_FLAG_IntelLLVM "-fiopenmp") + endif() + set(OMP_FLAG_MSVC "-openmp") + set(OMP_FLAG_PathScale "-openmp") + set(OMP_FLAG_NAG "-openmp") + set(OMP_FLAG_Absoft "-openmp") + set(OMP_FLAG_NVHPC "-mp") + set(OMP_FLAG_PGI "-mp") + set(OMP_FLAG_Flang "-fopenmp") + set(OMP_FLAG_SunPro "-xopenmp") + set(OMP_FLAG_XL "-qsmp=omp") + # Cray compiler activate OpenMP with -h omp, which is enabled by default. + set(OMP_FLAG_Cray " " "-h omp") + set(OMP_FLAG_Fujitsu "-Kopenmp" "-KOMP") + set(OMP_FLAG_FujitsuClang "-fopenmp" "-Kopenmp") + + # If we know the correct flags, use those + if(DEFINED OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}) + set(OpenMP_FLAG_CANDIDATES "${OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}}") + # Fall back to reasonable default tries otherwise + else() + set(OpenMP_FLAG_CANDIDATES "-openmp" "-fopenmp" "-mp" " ") + endif() + set(OpenMP_${LANG}_FLAG_CANDIDATES + "${OpenMP_FLAG_CANDIDATES}" + PARENT_SCOPE) + else() + set(OpenMP_${LANG}_FLAG_CANDIDATES + "${OpenMP_${LANG}_FLAG}" + PARENT_SCOPE) + endif() +endfunction() + +# sample openmp source code to test +set(OpenMP_C_CXX_TEST_SOURCE + " +#include +int main(void) { +#ifdef _OPENMP + omp_get_max_threads(); + return 0; +#elif defined(__HIP_DEVICE_COMPILE__) + return 0; +#else + breaks_on_purpose +#endif +} +") + +# in Fortran, an implementation may provide an omp_lib.h header or omp_lib +# module, or both (OpenMP standard, section 3.1) Furthmore !$ is the Fortran +# equivalent of #ifdef _OPENMP (OpenMP standard, 2.2.2) Without the conditional +# compilation, some compilers (e.g. PGI) might compile OpenMP code while not +# actually enabling OpenMP, building code sequentially +set(OpenMP_Fortran_TEST_SOURCE + " + program test + @OpenMP_Fortran_INCLUDE_LINE@ + !$ integer :: n + n = omp_get_num_threads() + end program test + ") + +macro(_OPENMP_PREPARE_SOURCE LANG CONTENT_ID NAME_PREFIX FULLNAME_VAR + CONTENT_VAR) + if("${LANG}" STREQUAL "C") + set(${FULLNAME_VAR} "${NAME_PREFIX}.c") + set(${CONTENT_VAR} "${OpenMP_C_CXX_${CONTENT_ID}}") + elseif("${LANG}" STREQUAL "CXX") + set(${FULLNAME_VAR} "${NAME_PREFIX}.cpp") + set(${CONTENT_VAR} "${OpenMP_C_CXX_${CONTENT_ID}}") + elseif("${LANG}" STREQUAL "Fortran") + set(${FULLNAME_VAR} "${NAME_PREFIX}.F90") + string(CONFIGURE "${OpenMP_Fortran_${CONTENT_ID}}" ${CONTENT_VAR} @ONLY) + endif() +endmacro() + +include(CMakeParseImplicitLinkInfo) + +function(_OPENMP_GET_FLAGS LANG FLAG_MODE OPENMP_FLAG_VAR OPENMP_LIB_NAMES_VAR) + _openmp_flag_candidates("${LANG}") + _openmp_prepare_source("${LANG}" TEST_SOURCE OpenMPTryFlag + _OPENMP_TEST_SRC_NAME _OPENMP_TEST_SRC_CONTENT) + + unset(OpenMP_VERBOSE_COMPILE_OPTIONS) + separate_arguments(OpenMP_VERBOSE_OPTIONS NATIVE_COMMAND + "${CMAKE_${LANG}_VERBOSE_FLAG}") + foreach(_VERBOSE_OPTION IN LISTS OpenMP_VERBOSE_OPTIONS) + if(NOT _VERBOSE_OPTION MATCHES "^-Wl,") + list(APPEND OpenMP_VERBOSE_COMPILE_OPTIONS ${_VERBOSE_OPTION}) + endif() + endforeach() + + foreach(OPENMP_FLAG IN LISTS OpenMP_${LANG}_FLAG_CANDIDATES) + set(OPENMP_FLAGS_TEST "${OPENMP_FLAG}") + if(OpenMP_VERBOSE_COMPILE_OPTIONS) + string(APPEND OPENMP_FLAGS_TEST " ${OpenMP_VERBOSE_COMPILE_OPTIONS}") + endif() + string(REGEX REPLACE "[-/=+]" "" OPENMP_PLAIN_FLAG "${OPENMP_FLAG}") + unset(_includeDirFlags) + if(OpenMP_${LANG}_INCLUDE_DIR) + set(_includeDirFlags + "-DINCLUDE_DIRECTORIES:STRING=${OpenMP_${LANG}_INCLUDE_DIR}") + endif() + try_compile( + OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} SOURCE_FROM_VAR + "${_OPENMP_TEST_SRC_NAME}" _OPENMP_TEST_SRC_CONTENT LOG_DESCRIPTION + "Detecting ${LANG} OpenMP compiler info" + CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" + ${_includeDirFlags} + LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} + OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) + + if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) + set("${OPENMP_FLAG_VAR}" + "${OPENMP_FLAG}" + PARENT_SCOPE) + + if(CMAKE_${LANG}_VERBOSE_FLAG) + unset(OpenMP_${LANG}_IMPLICIT_LIBRARIES) + unset(OpenMP_${LANG}_IMPLICIT_LINK_DIRS) + unset(OpenMP_${LANG}_IMPLICIT_FWK_DIRS) + unset(OpenMP_${LANG}_LOG_VAR) + + cmake_parse_implicit_link_info( + "${OpenMP_TRY_COMPILE_OUTPUT}" + OpenMP_${LANG}_IMPLICIT_LIBRARIES + OpenMP_${LANG}_IMPLICIT_LINK_DIRS + OpenMP_${LANG}_IMPLICIT_FWK_DIRS + OpenMP_${LANG}_LOG_VAR + "${CMAKE_${LANG}_IMPLICIT_OBJECT_REGEX}" + LANGUAGE + ${LANG}) + + # For LCC we should additionally alanyze -print-search-dirs output to + # check for additional implicit_dirs. Note: This won't work if CMP0129 + # policy is set to OLD! + if("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "LCC") + execute_process( + COMMAND ${CMAKE_${LANG}_COMPILER} -print-search-dirs + OUTPUT_VARIABLE output_lines COMMAND_ERROR_IS_FATAL ANY + ERROR_QUIET) + if("${output_lines}" MATCHES ".*\nlibraries:[ \t]+(.*:)\n.*") + string(REPLACE ":" ";" implicit_dirs_addon "${CMAKE_MATCH_1}") + list(PREPEND OpenMP_${LANG}_IMPLICIT_LINK_DIRS + ${implicit_dirs_addon}) + string( + APPEND OpenMP_${LANG}_LOG_VAR + " Extended OpenMP library search paths: [${implicit_dirs}]\n") + endif() + endif() + + message( + CONFIGURE_LOG + "Parsed ${LANG} OpenMP implicit link information from above output:\n${OpenMP_${LANG}_LOG_VAR}\n\n" + ) + + unset(_OPENMP_LIB_NAMES) + foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_IMPLICIT_LIBRARIES) + get_filename_component(_OPENMP_IMPLICIT_LIB_DIR + "${_OPENMP_IMPLICIT_LIB}" DIRECTORY) + get_filename_component(_OPENMP_IMPLICIT_LIB_NAME + "${_OPENMP_IMPLICIT_LIB}" NAME) + get_filename_component(_OPENMP_IMPLICIT_LIB_PLAIN + "${_OPENMP_IMPLICIT_LIB}" NAME_WE) + string( + REGEX + REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PLAIN_ESC + "${_OPENMP_IMPLICIT_LIB_PLAIN}") + string( + REGEX + REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PATH_ESC + "${_OPENMP_IMPLICIT_LIB}") + if(NOT + ( + "${_OPENMP_IMPLICIT_LIB}" IN_LIST + CMAKE_${LANG}_IMPLICIT_LINK_LIBRARIES + OR "${CMAKE_${LANG}_STANDARD_LIBRARIES}" + MATCHES + "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)" + OR "${CMAKE_${LANG}_LINK_EXECUTABLE}" + MATCHES + "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)" + )) + if(_OPENMP_IMPLICIT_LIB_DIR) + set(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY + "${_OPENMP_IMPLICIT_LIB}" + CACHE + FILEPATH + "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP" + ) + else() + find_library( + OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY + NAMES "${_OPENMP_IMPLICIT_LIB_NAME}" + DOC "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP" + HINTS ${OpenMP_${LANG}_IMPLICIT_LINK_DIRS} + CMAKE_FIND_ROOT_PATH_BOTH NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH) + endif() + mark_as_advanced(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY) + list(APPEND _OPENMP_LIB_NAMES ${_OPENMP_IMPLICIT_LIB_PLAIN}) + endif() + endforeach() + set("${OPENMP_LIB_NAMES_VAR}" + "${_OPENMP_LIB_NAMES}" + PARENT_SCOPE) + else() + # We do not know how to extract implicit OpenMP libraries for this + # compiler. Assume that it handles them automatically, e.g. the Intel + # Compiler on Windows should put the dependency in its object files. + set("${OPENMP_LIB_NAMES_VAR}" + "" + PARENT_SCOPE) + endif() + break() + elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "AppleClang" + AND CMAKE_${LANG}_COMPILER_VERSION VERSION_GREATER_EQUAL "7.0") + + # Check for separate OpenMP library on AppleClang 7+ + find_library( + OpenMP_libomp_LIBRARY + NAMES omp gomp iomp5 + HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}) + mark_as_advanced(OpenMP_libomp_LIBRARY) + + if(OpenMP_libomp_LIBRARY) + # Try without specifying include directory first. We only want to + # explicitly add a search path if the header can't be found on the + # default header search path already. + try_compile( + OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} + SOURCE_FROM_VAR + "${_OPENMP_TEST_SRC_NAME}" _OPENMP_TEST_SRC_CONTENT LOG_DESCRIPTION + "Trying ${LANG} OpenMP compiler with '${OpenMP_libomp_LIBRARY}'" + CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" + LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY}) + if(NOT OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) + find_path(OpenMP_${LANG}_INCLUDE_DIR omp.h) + mark_as_advanced(OpenMP_${LANG}_INCLUDE_DIR) + set(OpenMP_${LANG}_INCLUDE_DIR + "${OpenMP_${LANG}_INCLUDE_DIR}" + PARENT_SCOPE) + if(OpenMP_${LANG}_INCLUDE_DIR) + try_compile( + OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} + SOURCE_FROM_VAR + "${_OPENMP_TEST_SRC_NAME}" + _OPENMP_TEST_SRC_CONTENT + LOG_DESCRIPTION + "Trying ${LANG} OpenMP compiler with '${OpenMP_libomp_LIBRARY}' and '${OpenMP_${LANG}_INCLUDE_DIR}'" + CMAKE_FLAGS + "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" + "-DINCLUDE_DIRECTORIES:STRING=${OpenMP_${LANG}_INCLUDE_DIR}" + LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} + ${OpenMP_libomp_LIBRARY}) + endif() + endif() + if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) + set("${OPENMP_FLAG_VAR}" + "${OPENMP_FLAG}" + PARENT_SCOPE) + set("${OPENMP_LIB_NAMES_VAR}" + "libomp" + PARENT_SCOPE) + break() + endif() + endif() + elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "Clang" AND WIN32) + # Check for separate OpenMP library for Clang on Windows + find_library( + OpenMP_libomp_LIBRARY + NAMES libiomp5 libomp libgomp + HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}) + mark_as_advanced(OpenMP_libomp_LIBRARY) + if(OpenMP_libomp_LIBRARY) + try_compile( + OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} + SOURCE_FROM_VAR + "${_OPENMP_TEST_SRC_NAME}" _OPENMP_TEST_SRC_CONTENT LOG_DESCRIPTION + "Trying ${LANG} OpenMP compiler with '${OpenMP_libomp_LIBRARY}'" + CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" + LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY}) + if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) + set("${OPENMP_FLAG_VAR}" + "${OPENMP_FLAG}" + PARENT_SCOPE) + set("${OPENMP_LIB_NAMES_VAR}" + "libomp" + PARENT_SCOPE) + break() + endif() + endif() + endif() + set("${OPENMP_LIB_NAMES_VAR}" + "NOTFOUND" + PARENT_SCOPE) + set("${OPENMP_FLAG_VAR}" + "NOTFOUND" + PARENT_SCOPE) + endforeach() + + unset(OpenMP_VERBOSE_COMPILE_OPTIONS) +endfunction() + +set(OpenMP_C_CXX_CHECK_VERSION_SOURCE + " +#include +#include +const char ompver_str[] = { 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', + 'P', '-', 'd', 'a', 't', 'e', '[', + ('0' + ((_OPENMP/100000)%10)), + ('0' + ((_OPENMP/10000)%10)), + ('0' + ((_OPENMP/1000)%10)), + ('0' + ((_OPENMP/100)%10)), + ('0' + ((_OPENMP/10)%10)), + ('0' + ((_OPENMP/1)%10)), + ']', '\\0' }; +int main(void) +{ + puts(ompver_str); + return 0; +} +") + +set(OpenMP_Fortran_CHECK_VERSION_SOURCE + " + program omp_ver + @OpenMP_Fortran_INCLUDE_LINE@ + integer, parameter :: zero = ichar('0') + integer, parameter :: ompv = openmp_version + character, dimension(24), parameter :: ompver_str =& + (/ 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', 'P', '-',& + 'd', 'a', 't', 'e', '[',& + char(zero + mod(ompv/100000, 10)),& + char(zero + mod(ompv/10000, 10)),& + char(zero + mod(ompv/1000, 10)),& + char(zero + mod(ompv/100, 10)),& + char(zero + mod(ompv/10, 10)),& + char(zero + mod(ompv/1, 10)), ']' /) + print *, ompver_str + end program omp_ver +") + +function(_OPENMP_GET_SPEC_DATE LANG SPEC_DATE) + _openmp_prepare_source("${LANG}" CHECK_VERSION_SOURCE OpenMPCheckVersion + _OPENMP_TEST_SRC_NAME _OPENMP_TEST_SRC_CONTENT) + + unset(_includeDirFlags) + if(OpenMP_${LANG}_INCLUDE_DIR) + set(_includeDirFlags + "-DINCLUDE_DIRECTORIES:STRING=${OpenMP_${LANG}_INCLUDE_DIR}") + endif() + + set(BIN_FILE + "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMP/ompver_${LANG}.bin" + ) + string(REGEX REPLACE "[-/=+]" "" OPENMP_PLAIN_FLAG "${OPENMP_FLAG}") + try_compile( + OpenMP_SPECTEST_${LANG}_${OPENMP_PLAIN_FLAG} SOURCE_FROM_VAR + "${_OPENMP_TEST_SRC_NAME}" _OPENMP_TEST_SRC_CONTENT LOG_DESCRIPTION + "Detecting ${LANG} OpenMP version" + CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OpenMP_${LANG}_FLAGS}" + ${_includeDirFlags} + COPY_FILE "${BIN_FILE}") + + if(${OpenMP_SPECTEST_${LANG}_${OPENMP_PLAIN_FLAG}}) + file( + STRINGS ${BIN_FILE} specstr + LIMIT_COUNT 1 + REGEX "INFO:OpenMP-date") + set(regex_spec_date ".*INFO:OpenMP-date\\[0*([^]]*)\\].*") + if("${specstr}" MATCHES "${regex_spec_date}") + set(${SPEC_DATE} + "${CMAKE_MATCH_1}" + PARENT_SCOPE) + endif() + endif() +endfunction() + +macro(_OPENMP_SET_VERSION_BY_SPEC_DATE LANG) + set(OpenMP_SPEC_DATE_MAP + # Preview versions + "201611=5.0" # OpenMP 5.0 preview 1 + # Combined versions, 2.5 onwards + "201811=5.0" + "201511=4.5" + "201307=4.0" + "201107=3.1" + "200805=3.0" + "200505=2.5" + # C/C++ version 2.0 + "200203=2.0" + # Fortran version 2.0 + "200011=2.0" + # Fortran version 1.1 + "199911=1.1" + # C/C++ version 1.0 (there's no 1.1 for C/C++) + "199810=1.0" + # Fortran version 1.0 + "199710=1.0") + if(MSVC) + list(APPEND OpenMP_SPEC_DATE_MAP "2019=2.0") + endif() + + if(OpenMP_${LANG}_SPEC_DATE) + string(REGEX MATCHALL "${OpenMP_${LANG}_SPEC_DATE}=([0-9]+)\\.([0-9]+)" + _version_match "${OpenMP_SPEC_DATE_MAP}") + else() + set(_version_match "") + endif() + if(NOT _version_match STREQUAL "") + set(OpenMP_${LANG}_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(OpenMP_${LANG}_VERSION_MINOR ${CMAKE_MATCH_2}) + set(OpenMP_${LANG}_VERSION + "${OpenMP_${LANG}_VERSION_MAJOR}.${OpenMP_${LANG}_VERSION_MINOR}") + else() + unset(OpenMP_${LANG}_VERSION_MAJOR) + unset(OpenMP_${LANG}_VERSION_MINOR) + unset(OpenMP_${LANG}_VERSION) + endif() + unset(_version_match) + unset(OpenMP_SPEC_DATE_MAP) +endmacro() + +foreach(LANG IN ITEMS C CXX) + if(CMAKE_${LANG}_COMPILER_LOADED) + if(NOT DEFINED OpenMP_${LANG}_FLAGS + OR "${OpenMP_${LANG}_FLAGS}" STREQUAL "NOTFOUND" + OR NOT DEFINED OpenMP_${LANG}_LIB_NAMES + OR "${OpenMP_${LANG}_LIB_NAMES}" STREQUAL "NOTFOUND") + _openmp_get_flags("${LANG}" "${LANG}" OpenMP_${LANG}_FLAGS_WORK + OpenMP_${LANG}_LIB_NAMES_WORK) + if(NOT DEFINED OpenMP_${LANG}_FLAGS OR "${OpenMP_${LANG}_FLAGS}" STREQUAL + "NOTFOUND") + set(OpenMP_${LANG}_FLAGS + "${OpenMP_${LANG}_FLAGS_WORK}" + CACHE STRING "${LANG} compiler flags for OpenMP parallelization" + FORCE) + endif() + if(NOT DEFINED OpenMP_${LANG}_LIB_NAMES OR "${OpenMP_${LANG}_LIB_NAMES}" + STREQUAL "NOTFOUND") + set(OpenMP_${LANG}_LIB_NAMES + "${OpenMP_${LANG}_LIB_NAMES_WORK}" + CACHE STRING + "${LANG} compiler libraries for OpenMP parallelization" FORCE) + endif() + mark_as_advanced(OpenMP_${LANG}_FLAGS OpenMP_${LANG}_LIB_NAMES) + endif() + endif() +endforeach() + +if(CMAKE_Fortran_COMPILER_LOADED) + if(NOT DEFINED OpenMP_Fortran_FLAGS + OR "${OpenMP_Fortran_FLAGS}" STREQUAL "NOTFOUND" + OR NOT DEFINED OpenMP_Fortran_LIB_NAMES + OR "${OpenMP_Fortran_LIB_NAMES}" STREQUAL "NOTFOUND" + OR NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_MODULE) + set(OpenMP_Fortran_INCLUDE_LINE "use omp_lib\n implicit none") + _openmp_get_flags("Fortran" "FortranHeader" OpenMP_Fortran_FLAGS_WORK + OpenMP_Fortran_LIB_NAMES_WORK) + if(OpenMP_Fortran_FLAGS_WORK) + set(OpenMP_Fortran_HAVE_OMPLIB_MODULE + TRUE + CACHE BOOL INTERNAL "") + endif() + + if(NOT DEFINED OpenMP_Fortran_FLAGS OR "${OpenMP_Fortran_FLAGS}" STREQUAL + "NOTFOUND") + set(OpenMP_Fortran_FLAGS + "${OpenMP_Fortran_FLAGS_WORK}" + CACHE STRING "Fortran compiler flags for OpenMP parallelization" + FORCE) + endif() + if(NOT DEFINED OpenMP_Fortran_LIB_NAMES OR "${OpenMP_Fortran_LIB_NAMES}" + STREQUAL "NOTFOUND") + set(OpenMP_Fortran_LIB_NAMES + "${OpenMP_Fortran_LIB_NAMES_WORK}" + CACHE STRING "Fortran compiler libraries for OpenMP parallelization" + FORCE) + endif() + mark_as_advanced(OpenMP_Fortran_FLAGS OpenMP_Fortran_LIB_NAMES) + endif() + + if(NOT DEFINED OpenMP_Fortran_FLAGS + OR "${OpenMP_Fortran_FLAGS}" STREQUAL "NOTFOUND" + OR NOT DEFINED OpenMP_Fortran_LIB_NAMES + OR "${OpenMP_Fortran_LIB_NAMES}" STREQUAL "NOTFOUND" + OR NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_HEADER) + set(OpenMP_Fortran_INCLUDE_LINE "implicit none\n include 'omp_lib.h'") + _openmp_get_flags("Fortran" "FortranModule" OpenMP_Fortran_FLAGS_WORK + OpenMP_Fortran_LIB_NAMES_WORK) + if(OpenMP_Fortran_FLAGS_WORK) + set(OpenMP_Fortran_HAVE_OMPLIB_HEADER + TRUE + CACHE BOOL INTERNAL "") + endif() + + if(NOT DEFINED OpenMP_Fortran_FLAGS OR "${OpenMP_Fortran_FLAGS}" STREQUAL + "NOTFOUND") + set(OpenMP_Fortran_FLAGS + "${OpenMP_Fortran_FLAGS_WORK}" + CACHE STRING "Fortran compiler flags for OpenMP parallelization" + FORCE) + endif() + if(NOT DEFINED OpenMP_Fortran_LIB_NAMES OR "${OpenMP_Fortran_LIB_NAMES}" + STREQUAL "NOTFOUND") + set(OpenMP_Fortran_LIB_NAMES + "${OpenMP_Fortran_LIB_NAMES_WORK}" + CACHE STRING "Fortran compiler libraries for OpenMP parallelization" + FORCE) + endif() + endif() + + if(OpenMP_Fortran_HAVE_OMPLIB_MODULE) + set(OpenMP_Fortran_INCLUDE_LINE "use omp_lib\n implicit none") + else() + set(OpenMP_Fortran_INCLUDE_LINE "implicit none\n include 'omp_lib.h'") + endif() +endif() + +if(NOT OpenMP_FIND_COMPONENTS) + set(OpenMP_FINDLIST C CXX Fortran) +else() + set(OpenMP_FINDLIST ${OpenMP_FIND_COMPONENTS}) +endif() + +unset(_OpenMP_MIN_VERSION) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(OpenMP DEFAULT_MSG OpenMP_CXX_LIBRARY - OpenMP_CXX_INCLUDE_DIR) - -if(OpenMP_FOUND) - set(OpenMP_CXX_LIBRARIES ${OpenMP_CXX_LIBRARY}) - set(OpenMP_CXX_INCLUDE_DIRS ${OpenMP_CXX_INCLUDE_DIR}) - set(OpenMP_CXX_COMPILE_OPTIONS -Xpreprocessor -fopenmp) - set(OpenMP_CXX_FLAGS ${OpenMP_CXX_COMPILE_OPTIONS}) - - add_library(OpenMP::OpenMP_CXX SHARED IMPORTED) - set_target_properties( - OpenMP::OpenMP_CXX - PROPERTIES IMPORTED_LOCATION ${OpenMP_CXX_LIBRARIES} - INTERFACE_INCLUDE_DIRECTORIES "${OpenMP_CXX_INCLUDE_DIRS}" - INTERFACE_COMPILE_OPTIONS "${OpenMP_CXX_COMPILE_OPTIONS}") + +foreach(LANG IN LISTS OpenMP_FINDLIST) + if(CMAKE_${LANG}_COMPILER_LOADED) + if(NOT OpenMP_${LANG}_SPEC_DATE AND OpenMP_${LANG}_FLAGS) + _openmp_get_spec_date("${LANG}" OpenMP_${LANG}_SPEC_DATE_INTERNAL) + set(OpenMP_${LANG}_SPEC_DATE + "${OpenMP_${LANG}_SPEC_DATE_INTERNAL}" + CACHE INTERNAL "${LANG} compiler's OpenMP specification date") + endif() + _openmp_set_version_by_spec_date("${LANG}") + + set(OpenMP_${LANG}_FIND_QUIETLY ${OpenMP_FIND_QUIETLY}) + set(OpenMP_${LANG}_FIND_REQUIRED ${OpenMP_FIND_REQUIRED}) + set(OpenMP_${LANG}_FIND_VERSION ${OpenMP_FIND_VERSION}) + set(OpenMP_${LANG}_FIND_VERSION_EXACT ${OpenMP_FIND_VERSION_EXACT}) + + set(_OPENMP_${LANG}_REQUIRED_VARS OpenMP_${LANG}_FLAGS) + if("${OpenMP_${LANG}_LIB_NAMES}" STREQUAL "NOTFOUND") + set(_OPENMP_${LANG}_REQUIRED_LIB_VARS OpenMP_${LANG}_LIB_NAMES) + else() + foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_LIB_NAMES) + list(APPEND _OPENMP_${LANG}_REQUIRED_LIB_VARS + OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY) + endforeach() + endif() + + find_package_handle_standard_args( + OpenMP_${LANG} NAME_MISMATCHED + REQUIRED_VARS OpenMP_${LANG}_FLAGS ${_OPENMP_${LANG}_REQUIRED_LIB_VARS} + VERSION_VAR OpenMP_${LANG}_VERSION) + + if(OpenMP_${LANG}_FOUND) + if(DEFINED OpenMP_${LANG}_VERSION) + if(NOT _OpenMP_MIN_VERSION OR _OpenMP_MIN_VERSION VERSION_GREATER + OpenMP_${LANG}_VERSION) + set(_OpenMP_MIN_VERSION OpenMP_${LANG}_VERSION) + endif() + endif() + set(OpenMP_${LANG}_LIBRARIES "") + foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_LIB_NAMES) + list(APPEND OpenMP_${LANG}_LIBRARIES + "${OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY}") + endforeach() + if(OpenMP_${LANG}_INCLUDE_DIR) + set(OpenMP_${LANG}_INCLUDE_DIRS ${OpenMP_${LANG}_INCLUDE_DIR}) + else() + set(OpenMP_${LANG}_INCLUDE_DIRS "") + endif() + + if(NOT TARGET OpenMP::OpenMP_${LANG}) + add_library(OpenMP::OpenMP_${LANG} INTERFACE IMPORTED) + endif() + if(OpenMP_${LANG}_FLAGS) + separate_arguments(_OpenMP_${LANG}_OPTIONS NATIVE_COMMAND + "${OpenMP_${LANG}_FLAGS}") + set_property( + TARGET OpenMP::OpenMP_${LANG} + PROPERTY INTERFACE_COMPILE_OPTIONS + "$<$:${_OpenMP_${LANG}_OPTIONS}>") + if(CMAKE_${LANG}_COMPILER_ID STREQUAL "Fujitsu" + OR ${CMAKE_${LANG}_COMPILER_ID} STREQUAL "IntelLLVM") + set_property( + TARGET OpenMP::OpenMP_${LANG} PROPERTY INTERFACE_LINK_OPTIONS + "${OpenMP_${LANG}_FLAGS}") + endif() + unset(_OpenMP_${LANG}_OPTIONS) + endif() + if(OpenMP_${LANG}_INCLUDE_DIRS) + set_property( + TARGET OpenMP::OpenMP_${LANG} + PROPERTY INTERFACE_INCLUDE_DIRECTORIES + "$") + endif() + if(OpenMP_${LANG}_LIBRARIES) + set_property( + TARGET OpenMP::OpenMP_${LANG} PROPERTY INTERFACE_LINK_LIBRARIES + "${OpenMP_${LANG}_LIBRARIES}") + endif() + endif() + endif() +endforeach() + +unset(_OpenMP_REQ_VARS) +foreach(LANG IN ITEMS C CXX Fortran) + if((NOT OpenMP_FIND_COMPONENTS AND CMAKE_${LANG}_COMPILER_LOADED) + OR LANG IN_LIST OpenMP_FIND_COMPONENTS) + list(APPEND _OpenMP_REQ_VARS "OpenMP_${LANG}_FOUND") + endif() +endforeach() + +find_package_handle_standard_args( + OpenMP + REQUIRED_VARS ${_OpenMP_REQ_VARS} + VERSION_VAR ${_OpenMP_MIN_VERSION} + HANDLE_COMPONENTS) + +set(OPENMP_FOUND ${OpenMP_FOUND}) + +if(CMAKE_Fortran_COMPILER_LOADED AND OpenMP_Fortran_FOUND) + if(NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_MODULE) + set(OpenMP_Fortran_HAVE_OMPLIB_MODULE + FALSE + CACHE BOOL INTERNAL "") + endif() + if(NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_HEADER) + set(OpenMP_Fortran_HAVE_OMPLIB_HEADER + FALSE + CACHE BOOL INTERNAL "") + endif() +endif() + +if(NOT + (CMAKE_C_COMPILER_LOADED + OR CMAKE_CXX_COMPILER_LOADED + OR CMAKE_Fortran_COMPILER_LOADED)) + message( + SEND_ERROR + "FindOpenMP requires the C, CXX or Fortran languages to be enabled") endif() + +unset(OpenMP_C_CXX_TEST_SOURCE) +unset(OpenMP_Fortran_TEST_SOURCE) +unset(OpenMP_C_CXX_CHECK_VERSION_SOURCE) +unset(OpenMP_Fortran_CHECK_VERSION_SOURCE) +unset(OpenMP_Fortran_INCLUDE_LINE) diff --git a/cmake/logging.cmake b/cmake/logging.cmake index bab05a3..b9b8a88 100644 --- a/cmake/logging.cmake +++ b/cmake/logging.cmake @@ -67,11 +67,7 @@ endif(UNIX) # function(LOGGING_INITIALIZE) # Retrieve interesting information. - if(${CMAKE_VERSION} VERSION_LESS 2.8) - set(HOSTNAME "unknown (CMake >= 2.8 required)") - else() - site_name(HOSTNAME) - endif() + site_name(HOSTNAME) # Write logging file. file(REMOVE ${JRL_CMAKEMODULE_LOGGING_FILENAME}) diff --git a/cmake/memorycheck_unit_test.cmake.in b/cmake/memorycheck_unit_test.cmake.in new file mode 100644 index 0000000..2849b7a --- /dev/null +++ b/cmake/memorycheck_unit_test.cmake.in @@ -0,0 +1,32 @@ +# Copyright (C) 2023 LAAS-CNRS, JRL AIST-CNRS, INRIA. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +SET(PYTHON_EXECUTABLE @PYTHON_EXECUTABLE@) +SET(MEMORYCHECK_COMMAND @MEMORYCHECK_COMMAND@) +SET(PYTHON_TEST_SCRIPT @PYTHON_TEST_SCRIPT@) + +execute_process(COMMAND + ${MEMORYCHECK_COMMAND} -- ${PYTHON_EXECUTABLE} ${PYTHON_TEST_SCRIPT} + ERROR_VARIABLE MEMORYCHECK_OUTPUT) + +# Check if there is some memory leaks +string(FIND "${MEMORYCHECK_OUTPUT}" "definitely lost: 0 bytes in 0 blocks" DEFINITELY_LOST) +string(FIND "${MEMORYCHECK_OUTPUT}" "indirectly lost: 0 bytes in 0 blocks" INDIRECTLY_LOST) + +if(${DEFINITELY_LOST} GREATER -1 AND ${INDIRECTLY_LOST} GREATER -1) + message(STATUS "${PYTHON_TEST_SCRIPT} is not leaking memory") +else() + message(FATAL_ERROR "Output: ${MEMORYCHECK_OUTPUT}\n" + "${PYTHON_TEST_SCRIPT} is leaking memory\n") +endif() diff --git a/cmake/msvc-specific.cmake b/cmake/msvc-specific.cmake index f7ff9e2..286f844 100644 --- a/cmake/msvc-specific.cmake +++ b/cmake/msvc-specific.cmake @@ -146,10 +146,6 @@ macro(REQUIRE_MINIMUM_MSVC_VERSION VERSION) endif(${MSVC_TOOLS_VERSION}) endmacro(REQUIRE_MINIMUM_MSVC_VERSION) -if(${CMAKE_VERSION} VERSION_LESS "3.5.0") - include(CMakeParseArguments) -endif() - # GENERATE_MSVC_DOT_USER_FILE( []) # GENERATE_MSVC_DOT_USER_FILE(NAME [COMMAND ] [COMMAND_ARGS # ] [WORKING_DIRECTORY ] [ADDITIONAL_PATH ]) diff --git a/cmake/pkg-config.cmake b/cmake/pkg-config.cmake index 939d767..327288d 100644 --- a/cmake/pkg-config.cmake +++ b/cmake/pkg-config.cmake @@ -53,8 +53,9 @@ set(PKG_CONFIG_ADDITIONAL_VARIABLES bindir pkglibdir datarootdir pkgdatarootdir # macro(_SETUP_PROJECT_PKG_CONFIG) # Pkg-config related commands. + rel_install_path("${CMAKE_INSTALL_LIBDIR}/pkgconfig" _PC_REL_INSTALL_PATH) set(_PKG_CONFIG_PREFIX - "\${pcfiledir}/../.." + "\${pcfiledir}/${_PC_REL_INSTALL_PATH}" CACHE INTERNAL "") set(_PKG_CONFIG_EXEC_PREFIX "${_PKG_CONFIG_PREFIX}" @@ -821,33 +822,55 @@ macro(PKG_CONFIG_APPEND_LIBS LIBS) if(LIB) # Check if this project is building this library if(TARGET ${LIB}) + get_target_property(TARGET_TYPE ${LIB} TYPE) + + message(STATUS "target_type: ${TARGET_TYPE}") + # CMake 3.16 until 3.19 do not properly handle the properties for + # INTERFACE. + if(${TARGET_TYPE} STREQUAL "INTERFACE_LIBRARY") + set(TARGET_LIB_IS_AN_INTERFACE TRUE) + else() + set(TARGET_LIB_IS_AN_INTERFACE FALSE) + endif(${TARGET_TYPE} STREQUAL "INTERFACE_LIBRARY") set(LIB_COMPLETE_NAME ${LIB}) + + if(TARGET_LIB_IS_AN_INTERFACE) + set(OUTPUT_NAME_FIX INTERFACE_OUTPUT_NAME) + set(SUFFIX_FIX INTERFACE_SUFFIX) + set(PREFIX_FIX INTERFACE_PREFIX) + else() + set(OUTPUT_NAME_FIX OUTPUT_NAME) + set(SUFFIX_FIX SUFFIX) + set(PREFIX_FIX PREFIX) + endif() + # If OUTPUT_NAME property is defined, use this for the library name. get_property( OUTPUT_NAME_SET TARGET ${LIB} - PROPERTY OUTPUT_NAME + PROPERTY ${OUTPUT_NAME_FIX} SET) if(OUTPUT_NAME_SET) - get_target_property(OUTPUT_LIB_NAME ${LIB} OUTPUT_NAME) + get_target_property(OUTPUT_LIB_NAME ${LIB} ${OUTPUT_NAME_FIX}) endif(OUTPUT_NAME_SET) + # If SUFFIX property is defined, use it for defining the library name. get_property( SUFFIX_SET TARGET ${LIB} - PROPERTY SUFFIX + PROPERTY ${SUFFIX_FIX} SET) if(SUFFIX_SET) - get_target_property(LIB_SUFFIX ${LIB} SUFFIX) + get_target_property(LIB_SUFFIX ${LIB} ${SUFFIX_FIX}) endif(SUFFIX_SET) get_property( PREFIX_SET TARGET ${LIB} - PROPERTY PREFIX + PROPERTY ${PREFIX_FIX} SET) if(PREFIX_SET) - get_target_property(LIB_PREFIX ${LIB} PREFIX) + get_target_property(LIB_PREFIX ${LIB} ${PREFIX_FIX}) endif(PREFIX_SET) if(OUTPUT_NAME_SET) set(LIB_COMPLETE_NAME ${OUTPUT_LIB_NAME}) diff --git a/cmake/post-project.cmake b/cmake/post-project.cmake index ab5f941..0c5b539 100644 --- a/cmake/post-project.cmake +++ b/cmake/post-project.cmake @@ -4,6 +4,7 @@ set(PROJECT_VERSION_MAJOR "${SAVED_PROJECT_VERSION_MAJOR}") set(PROJECT_VERSION_MINOR "${SAVED_PROJECT_VERSION_MINOR}") set(PROJECT_VERSION_PATCH "${SAVED_PROJECT_VERSION_PATCH}") +include(CMakeDependentOption) include(${CMAKE_CURRENT_LIST_DIR}/GNUInstallDirs.cmake) set(CMAKE_INSTALL_FULL_PKGLIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROJECT_NAME}) set(CMAKE_INSTALL_PKGLIBDIR ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}) @@ -39,7 +40,8 @@ else(${ARGC}) endif(${ARGC}) option(BUILD_DOCUMENTATION "Build the documentation." ON) -option(INSTALL_DOCUMENTATION "Install the documentation." ON) +cmake_dependent_option(INSTALL_DOCUMENTATION "Install the documentation." ON + BUILD_DOCUMENTATION OFF) option(INSTALL_GENERATED_HEADERS "Generate and install standard headers" ON) option(INSTALL_PKG_CONFIG_FILE "Generate and install standard .pc file" ON) @@ -49,6 +51,9 @@ enable_testing() logging_initialize() # FIXME: normalize naming to _SETUP() +if(COMMAND _setup_python_for_cython) + _setup_python_for_cython() +endif() _setup_project_warnings() _setup_project_header() _setup_project_dist() diff --git a/cmake/python-helpers.cmake b/cmake/python-helpers.cmake new file mode 100644 index 0000000..bf81248 --- /dev/null +++ b/cmake/python-helpers.cmake @@ -0,0 +1,115 @@ +# Copyright (C) 2008-2023 LAAS-CNRS, JRL AIST-CNRS, INRIA. +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +# .rst: .. command:: PYTHON_INSTALL(MODULE FILE DEST) +# +# Compile and install a Python file. +# +macro(PYTHON_INSTALL MODULE FILE DEST) + python_build("${MODULE}" "${FILE}") + + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/${FILE}" + DESTINATION "${DEST}/${MODULE}") +endmacro() + +# .rst: .. command:: PYTHON_INSTALL_ON_SITE (MODULE FILE) +# +# Compile and install a Python file in :cmake:variable:`PYTHON_SITELIB`. +# +macro(PYTHON_INSTALL_ON_SITE MODULE FILE) + python_install("${MODULE}" "${FILE}" ${PYTHON_SITELIB}) +endmacro() + +# PYTHON_BUILD_GET_TARGET(TARGET) +# ----------------------------------------- +# +# Get the target associated to the PYTHON_BUILD procedure +# +function(PYTHON_BUILD_GET_TARGET python_build_target) + # Regex from IsValidTargetName in CMake/Source/cmGeneratorExpression.cxx + string(REGEX REPLACE "[^A-Za-z0-9_.+-]" "_" compile_pyc + "compile_pyc_${CMAKE_CURRENT_SOURCE_DIR}") + + if(NOT TARGET ${compile_pyc}) + add_custom_target(${compile_pyc} ALL) + endif() + + set(${python_build_target} + ${compile_pyc} + PARENT_SCOPE) +endfunction(PYTHON_BUILD_GET_TARGET NAME) + +# PYTHON_BUILD(MODULE FILE DEST) +# -------------------------------------- +# +# Build a Python file from the source directory in the build directory. +# +macro(PYTHON_BUILD MODULE FILE) + set(python_build_target "") + python_build_get_target(python_build_target) + + set(INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/${FILE}") + + if(CMAKE_GENERATOR MATCHES "Visual Studio|Xcode") + set(OUTPUT_FILE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/$") + else() + set(OUTPUT_FILE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}") + endif() + + set(OUTPUT_FILE "${OUTPUT_FILE_DIR}/${FILE}c") + + # Create directory accounting for the generator expression contained in + # ${OUTPUT_FILE_DIR} + add_custom_command( + TARGET ${python_build_target} + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_FILE_DIR}") + + python_build_file(${INPUT_FILE} ${OUTPUT_FILE}) +endmacro() + +# PYTHON_BUILD_FILE(FILE [OUTPUT_FILE]) +# -------------------------------------- +# +# Build a Python a given file. +# +macro(PYTHON_BUILD_FILE FILE) + set(python_build_target "") + python_build_get_target(python_build_target) + + set(extra_var "${ARGV1}") + if(NOT extra_var STREQUAL "") + set(OUTPUT_FILE "${ARGV1}") + else() + set(OUTPUT_FILE "${FILE}c") + endif() + + add_custom_command( + TARGET ${python_build_target} + PRE_BUILD + COMMAND + "${PYTHON_EXECUTABLE}" -c + "import py_compile; py_compile.compile(\"${FILE}\",\"${OUTPUT_FILE}\")" + VERBATIM) + + # Tag pyc file as generated. + set_source_files_properties("${OUTPUT_FILE}" PROPERTIES GENERATED TRUE) + + # Clean generated files. + set_property( + DIRECTORY + APPEND + PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${OUTPUT_FILE}") +endmacro() diff --git a/cmake/python.cmake b/cmake/python.cmake index f715def..baaabd4 100644 --- a/cmake/python.cmake +++ b/cmake/python.cmake @@ -55,14 +55,6 @@ # nothing for CMake < 3.12 which doesn't have those. This also export: - # `FIND_NUMPY` and/or `SEARCH_FOR_BOOST_PYTHON` if necessary. -if(CMAKE_VERSION VERSION_LESS "3.2") - set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/python ${CMAKE_MODULE_PATH}) - message( - STATUS - "CMake versions older than 3.2 do not properly find Python. Custom macros are used to find it." - ) -endif(CMAKE_VERSION VERSION_LESS "3.2") - macro(FINDPYTHON) if(DEFINED FINDPYTHON_ALREADY_CALLED) message( @@ -120,7 +112,7 @@ macro(FINDPYTHON) endif() endif() - if(NOT DEFINED Python_EXCUTABLE) + if(NOT DEFINED Python_EXECUTABLE) set(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) endif() else() @@ -142,26 +134,32 @@ macro(FINDPYTHON) ERROR_VARIABLE _PYTHON_VERSION_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) + if(NOT "${_PYTHON_VERSION_RESULT_VARIABLE}" STREQUAL "0") + message(FATAL_ERROR "${PYTHON_EXECUTABLE} --version did not succeed.") + endif() + string(REGEX REPLACE "Python " "" _PYTHON_VERSION + ${_PYTHON_VERSION_OUTPUT}) + string(REGEX REPLACE "\\." ";" _PYTHON_VERSION ${_PYTHON_VERSION}) + list(GET _PYTHON_VERSION 0 _PYTHON_VERSION_MAJOR) + # Provide some hints according to the current PYTHON_EXECUTABLE if(NOT DEFINED PYTHON_INCLUDE_DIR) + if(_PYTHON_VERSION_MAJOR EQUAL "2") + set(_PYTHON_INCLUDE_DIR_CMD + "import distutils.sysconfig as sysconfig; print(sysconfig.get_python_inc())" + ) + else() + set(_PYTHON_INCLUDE_DIR_CMD + "import sysconfig; print(sysconfig.get_path('include'))") + endif() execute_process( - COMMAND - "${PYTHON_EXECUTABLE}" "-c" - "import distutils.sysconfig as sysconfig; print(sysconfig.get_python_inc())" + COMMAND "${PYTHON_EXECUTABLE}" "-c" "${_PYTHON_INCLUDE_DIR_CMD}" OUTPUT_VARIABLE PYTHON_INCLUDE_DIR ERROR_QUIET) string(STRIP "${PYTHON_INCLUDE_DIR}" PYTHON_INCLUDE_DIR) file(TO_CMAKE_PATH "${PYTHON_INCLUDE_DIR}" PYTHON_INCLUDE_DIR) endif() - if(NOT "${_PYTHON_VERSION_RESULT_VARIABLE}" STREQUAL "0") - message(FATAL_ERROR "${PYTHON_EXECUTABLE} --version did not succeed.") - endif(NOT "${_PYTHON_VERSION_RESULT_VARIABLE}" STREQUAL "0") - string(REGEX REPLACE "Python " "" _PYTHON_VERSION - ${_PYTHON_VERSION_OUTPUT}) - string(REGEX REPLACE "\\." ";" _PYTHON_VERSION ${_PYTHON_VERSION}) - list(GET _PYTHON_VERSION 0 _PYTHON_VERSION_MAJOR) - # Hint for finding the right Python version set(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) set(Python${_PYTHON_VERSION_MAJOR}_EXECUTABLE ${PYTHON_EXECUTABLE}) @@ -179,11 +177,11 @@ macro(FINDPYTHON) message(FATAL_ERROR "Python executable has not been found.") else() set(_PYTHON_VERSION_MAJOR 3) - endif(NOT Python3_FOUND) + endif() else() set(_PYTHON_VERSION_MAJOR 2) - endif(NOT Python2_FOUND) - endif(PYTHON_EXECUTABLE) + endif() + endif() set(_PYTHON_PREFIX "Python${_PYTHON_VERSION_MAJOR}") @@ -211,12 +209,12 @@ macro(FINDPYTHON) file(TO_CMAKE_PATH "${NUMPY_INCLUDE_DIRS}" NUMPY_INCLUDE_DIRS) endif() - else(NOT CMAKE_VERSION VERSION_LESS "3.12") + else() find_package(PythonInterp ${ARGN}) if(NOT ${PYTHONINTERP_FOUND} STREQUAL TRUE) message(FATAL_ERROR "Python executable has not been found.") - endif(NOT ${PYTHONINTERP_FOUND} STREQUAL TRUE) + endif() message(STATUS "PythonInterp: ${PYTHON_EXECUTABLE}") # Set PYTHON_INCLUDE_DIR variables if it is not defined by the user @@ -224,17 +222,23 @@ macro(FINDPYTHON) # Retrieve the corresponding value of PYTHON_INCLUDE_DIR if it is not # defined if(NOT DEFINED PYTHON_INCLUDE_DIR) + if(PYTHON_VERSION_MAJOR EQUAL "2") + set(_PYTHON_INCLUDE_DIR_CMD + "import distutils.sysconfig as sysconfig; print(sysconfig.get_python_inc())" + ) + else() + set(_PYTHON_INCLUDE_DIR_CMD + "import sysconfig; print(sysconfig.get_path('include'))") + endif() execute_process( - COMMAND - "${PYTHON_EXECUTABLE}" "-c" - "import distutils.sysconfig as sysconfig; print(sysconfig.get_python_inc())" + COMMAND "${PYTHON_EXECUTABLE}" "-c" "${_PYTHON_INCLUDE_DIR_CMD}" OUTPUT_VARIABLE PYTHON_INCLUDE_DIR ERROR_QUIET) string(STRIP "${PYTHON_INCLUDE_DIR}" PYTHON_INCLUDE_DIR) - endif(NOT DEFINED PYTHON_INCLUDE_DIR) + endif() set(PYTHON_INCLUDE_DIRS ${PYTHON_INCLUDE_DIR}) - endif(DEFINED PYTHON_EXECUTABLE) + endif() # Inform PythonLibs of the required version of PythonInterp set(PYTHONLIBS_VERSION_STRING ${PYTHON_VERSION_STRING}) @@ -243,7 +247,7 @@ macro(FINDPYTHON) message(STATUS "PythonLibraries: ${PYTHON_LIBRARIES}") if(NOT ${PYTHONLIBS_FOUND} STREQUAL TRUE) message(FATAL_ERROR "Python has not been found.") - endif(NOT ${PYTHONLIBS_FOUND} STREQUAL TRUE) + endif() string(REPLACE "." ";" _PYTHONLIBS_VERSION ${PYTHONLIBS_VERSION_STRING}) list(GET _PYTHONLIBS_VERSION 0 PYTHONLIBS_VERSION_MAJOR) @@ -255,10 +259,9 @@ macro(FINDPYTHON) FATAL_ERROR "Python interpreter and libraries are in different version: ${PYTHON_VERSION_STRING} vs ${PYTHONLIBS_VERSION_STRING}" ) - endif(NOT ${PYTHON_VERSION_MAJOR} EQUAL ${PYTHONLIBS_VERSION_MAJOR} - OR NOT ${PYTHON_VERSION_MINOR} EQUAL ${PYTHONLIBS_VERSION_MINOR}) + endif() - endif(NOT CMAKE_VERSION VERSION_LESS "3.12") + endif() # Find PYTHON_LIBRARY_DIRS get_filename_component(PYTHON_LIBRARY_DIRS "${PYTHON_LIBRARIES}" PATH) @@ -267,7 +270,7 @@ macro(FINDPYTHON) if(PYTHON_SITELIB) file(TO_CMAKE_PATH "${PYTHON_SITELIB}" PYTHON_SITELIB) - else(PYTHON_SITELIB) + else() # Use either site-packages (default) or dist-packages (Debian packages) # directory option(PYTHON_DEB_LAYOUT "Enable Debian-style Python package layout" OFF) @@ -275,17 +278,23 @@ macro(FINDPYTHON) option(PYTHON_STANDARD_LAYOUT "Enable standard Python package layout" OFF) if(PYTHON_STANDARD_LAYOUT) - set(PYTHON_SITELIB_CMD + set(_PYTHON_SITELIB_CMD "import sys, os; print(os.sep.join(['lib', 'python' + '.'.join(sys.version.split('.')[:2]), 'site-packages']))" ) - else(PYTHON_STANDARD_LAYOUT) - set(PYTHON_SITELIB_CMD - "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix='', plat_specific=False))" - ) - endif(PYTHON_STANDARD_LAYOUT) + else() + if(PYTHON_VERSION_MAJOR EQUAL "2") + set(_PYTHON_SITELIB_CMD + "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix='', plat_specific=False))" + ) + else() + set(_PYTHON_SITELIB_CMD + "import sysconfig; from pathlib import Path; print(Path(sysconfig.get_path('purelib')).relative_to(sysconfig.get_path('data')))" + ) + endif() + endif() execute_process( - COMMAND "${PYTHON_EXECUTABLE}" "-c" "${PYTHON_SITELIB_CMD}" + COMMAND "${PYTHON_EXECUTABLE}" "-c" "${_PYTHON_SITELIB_CMD}" OUTPUT_VARIABLE PYTHON_SITELIB OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) @@ -293,7 +302,7 @@ macro(FINDPYTHON) if(PYTHON_DEB_LAYOUT) string(REPLACE "site-packages" "dist-packages" PYTHON_SITELIB "${PYTHON_SITELIB}") - endif(PYTHON_DEB_LAYOUT) + endif() # If PYTHON_PACKAGES_DIR is defined, then force the Python packages # directory name @@ -301,8 +310,8 @@ macro(FINDPYTHON) string(REGEX REPLACE "(site-packages|dist-packages)" "${PYTHON_PACKAGES_DIR}" PYTHON_SITELIB "${PYTHON_SITELIB}") - endif(PYTHON_PACKAGES_DIR) - endif(PYTHON_SITELIB) + endif() + endif() message(STATUS "Python site lib: ${PYTHON_SITELIB}") message(STATUS "Python include dirs: ${PYTHON_INCLUDE_DIRS}") @@ -314,10 +323,10 @@ macro(FINDPYTHON) execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-c" - "from distutils.sysconfig import get_config_var; print('.' + get_config_var('SOABI'))" + "from sysconfig import get_config_var; print('.' + get_config_var('SOABI'))" OUTPUT_VARIABLE PYTHON_SOABI) string(STRIP ${PYTHON_SOABI} PYTHON_SOABI) - endif(PYTHON_VERSION_MAJOR EQUAL 3 AND NOT WIN32) + endif() # Get PYTHON_EXT_SUFFIX set(PYTHON_EXT_SUFFIX "") @@ -325,10 +334,10 @@ macro(FINDPYTHON) execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-c" - "from distutils.sysconfig import get_config_var; print(get_config_var('EXT_SUFFIX'))" + "from sysconfig import get_config_var; print(get_config_var('EXT_SUFFIX'))" OUTPUT_VARIABLE PYTHON_EXT_SUFFIX) string(STRIP ${PYTHON_EXT_SUFFIX} PYTHON_EXT_SUFFIX) - endif(PYTHON_VERSION_MAJOR EQUAL 3) + endif() if("${PYTHON_EXT_SUFFIX}" STREQUAL "") if(WIN32) set(PYTHON_EXT_SUFFIX ".pyd") @@ -339,6 +348,7 @@ macro(FINDPYTHON) if(PYTHON_EXPORT_DEPENDENCY) install_jrl_cmakemodules_file("python.cmake") + install_jrl_cmakemodules_file("python-helpers.cmake") string( CONCAT PYTHON_EXPORT_DEPENDENCY_MACROS "list(APPEND PYTHON_COMPONENTS ${PYTHON_COMPONENTS})\n" @@ -442,7 +452,7 @@ macro(DYNAMIC_GRAPH_PYTHON_MODULE SUBMODULENAME LIBRARYNAME TARGETNAME) if(UNIX AND NOT APPLE) target_link_libraries(${PYTHON_MODULE} PUBLIC "-Wl,--no-as-needed") - endif(UNIX AND NOT APPLE) + endif() target_link_libraries(${PYTHON_MODULE} PUBLIC ${LIBRARYNAME} dynamic-graph::dynamic-graph) target_link_boost_python(${PYTHON_MODULE} PUBLIC) @@ -482,139 +492,6 @@ macro(DYNAMIC_GRAPH_PYTHON_MODULE SUBMODULENAME LIBRARYNAME TARGETNAME) endmacro(DYNAMIC_GRAPH_PYTHON_MODULE SUBMODULENAME) -# .rst: .. command:: PYTHON_INSTALL(MODULE FILE DEST) -# -# Compile and install a Python file. -# -macro(PYTHON_INSTALL MODULE FILE DEST) - - python_build("${MODULE}" "${FILE}") - - install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/${FILE}" - DESTINATION "${DEST}/${MODULE}") -endmacro() - -# .rst: .. command:: PYTHON_INSTALL_ON_SITE (MODULE FILE) -# -# Compile and install a Python file in :cmake:variable:`PYTHON_SITELIB`. -# -macro(PYTHON_INSTALL_ON_SITE MODULE FILE) - - if(NOT PYTHON_EXECUTABLE) - findpython() - endif() - - python_install("${MODULE}" "${FILE}" ${PYTHON_SITELIB}) - -endmacro() - -# PYTHON_BUILD(MODULE FILE DEST) -# -------------------------------------- -# -# Build a Python file from the source directory in the build directory. -# -macro(PYTHON_BUILD MODULE FILE) - # Regex from IsValidTargetName in CMake/Source/cmGeneratorExpression.cxx - string(REGEX REPLACE "[^A-Za-z0-9_.+-]" "_" compile_pyc - "compile_pyc_${CMAKE_CURRENT_SOURCE_DIR}") - if(NOT TARGET ${compile_pyc}) - add_custom_target(${compile_pyc} ALL) - endif() - file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}") - - add_custom_command( - TARGET ${compile_pyc} - PRE_BUILD - COMMAND - "${PYTHON_EXECUTABLE}" "${PROJECT_JRL_CMAKE_MODULE_DIR}/compile.py" - "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" - "${MODULE}/${FILE}") - - # Tag pyc file as generated. - set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/${FILE}c" - PROPERTIES GENERATED TRUE) - - # Clean generated files. - set_property( - DIRECTORY - APPEND - PROPERTY ADDITIONAL_MAKE_CLEAN_FILES - "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/${FILE}c") -endmacro() - -# PYTHON_BUILD_FILE(FILE) -# -------------------------------------- -# -# Build a Python a given file. -# -macro(PYTHON_BUILD_FILE FILE) - # Regex from IsValidTargetName in CMake/Source/cmGeneratorExpression.cxx - string(REGEX REPLACE "[^A-Za-z0-9_.+-]" "_" compile_pyc - "compile_pyc_${CMAKE_CURRENT_SOURCE_DIR}") - if(NOT TARGET ${compile_pyc}) - add_custom_target(${compile_pyc} ALL) - endif() - - add_custom_command( - TARGET ${compile_pyc} - PRE_BUILD - COMMAND "${PYTHON_EXECUTABLE}" -c - "import py_compile; py_compile.compile(\"${FILE}\",\"${FILE}c\")" - VERBATIM) - - # Tag pyc file as generated. - set_source_files_properties("${FILE}c" PROPERTIES GENERATED TRUE) - - # Clean generated files. - set_property( - DIRECTORY - APPEND - PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${FILE}c") -endmacro() - -# PYTHON_INSTALL_BUILD(MODULE FILE DEST) -# -------------------------------------- -# -# Install a Python file residing in the build directory and its associated -# compiled version. -# -macro(PYTHON_INSTALL_BUILD MODULE FILE DEST) - - message( - AUTHOR_WARNING - "PYTHON_INSTALL_BUILD is deprecated and will be removed in the future") - message(AUTHOR_WARNING "Please use PYTHON_INSTALL_ON_SITE") - message( - AUTHOR_WARNING - "ref https://github.com/jrl-umi3218/jrl-cmakemodules/issues/136") - - file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}") - - install( - CODE "EXECUTE_PROCESS(COMMAND - \"${PYTHON_EXECUTABLE}\" - \"${PROJECT_JRL_CMAKE_MODULE_DIR}/compile.py\" - \"${CMAKE_CURRENT_BINARY_DIR}\" - \"${CMAKE_CURRENT_BINARY_DIR}\" - \"${MODULE}/${FILE}\") - ") - - # Tag pyc file as generated. - set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/${FILE}c" - PROPERTIES GENERATED TRUE) - - # Clean generated files. - set_property( - DIRECTORY - APPEND - PROPERTY ADDITIONAL_MAKE_CLEAN_FILES - "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/${FILE}c") - - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/${FILE}" - "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}/${FILE}c" - DESTINATION "${DEST}/${MODULE}") -endmacro() - # .rst: .. command:: FIND_NUMPY() # # Detect numpy module and define the variable NUMPY_INCLUDE_DIRS if it is not @@ -651,3 +528,5 @@ macro(FIND_NUMPY) message(STATUS " NUMPY_VERSION=${NUMPY_VERSION}") endif() endmacro() + +include(${CMAKE_CURRENT_LIST_DIR}/python-helpers.cmake) diff --git a/cmake/python/FindPythonInterp.cmake b/cmake/python/FindPythonInterp.cmake deleted file mode 100644 index 1e01a99..0000000 --- a/cmake/python/FindPythonInterp.cmake +++ /dev/null @@ -1,171 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindPythonInterp ----------------- - -.. deprecated:: 3.12 - - Use :module:`FindPython3`, :module:`FindPython2` or :module:`FindPython` instead. - -Find python interpreter - -This module finds if Python interpreter is installed and determines -where the executables are. This code sets the following variables: - -:: - - PYTHONINTERP_FOUND - Was the Python executable found - PYTHON_EXECUTABLE - path to the Python interpreter - - - -:: - - PYTHON_VERSION_STRING - Python version found e.g. 2.5.2 - PYTHON_VERSION_MAJOR - Python major version found e.g. 2 - PYTHON_VERSION_MINOR - Python minor version found e.g. 5 - PYTHON_VERSION_PATCH - Python patch version found e.g. 2 - - - -The Python_ADDITIONAL_VERSIONS variable can be used to specify a list -of version numbers that should be taken into account when searching -for Python. You need to set this variable before calling -find_package(PythonInterp). - -If calling both ``find_package(PythonInterp)`` and -``find_package(PythonLibs)``, call ``find_package(PythonInterp)`` first to -get the currently active Python version by default with a consistent version -of PYTHON_LIBRARIES. - -.. note:: - - A call to ``find_package(PythonInterp ${V})`` for python version ``V`` - may find a ``python`` executable with no version suffix. In this case - no attempt is made to avoid python executables from other versions. - Use :module:`FindPython3`, :module:`FindPython2` or :module:`FindPython` - instead. - -#]=======================================================================] - -unset(_Python_NAMES) - -set(_PYTHON1_VERSIONS 1.6 1.5) -set(_PYTHON2_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) -set(_PYTHON3_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) - -if(PythonInterp_FIND_VERSION) - if(PythonInterp_FIND_VERSION_COUNT GREATER 1) - set(_PYTHON_FIND_MAJ_MIN "${PythonInterp_FIND_VERSION_MAJOR}.${PythonInterp_FIND_VERSION_MINOR}") - list(APPEND _Python_NAMES - python${_PYTHON_FIND_MAJ_MIN} - python${PythonInterp_FIND_VERSION_MAJOR}) - unset(_PYTHON_FIND_OTHER_VERSIONS) - if(NOT PythonInterp_FIND_VERSION_EXACT) - foreach(_PYTHON_V ${_PYTHON${PythonInterp_FIND_VERSION_MAJOR}_VERSIONS}) - if(NOT _PYTHON_V VERSION_LESS _PYTHON_FIND_MAJ_MIN) - list(APPEND _PYTHON_FIND_OTHER_VERSIONS ${_PYTHON_V}) - endif() - endforeach() - endif() - unset(_PYTHON_FIND_MAJ_MIN) - else() - list(APPEND _Python_NAMES python${PythonInterp_FIND_VERSION_MAJOR}) - set(_PYTHON_FIND_OTHER_VERSIONS ${_PYTHON${PythonInterp_FIND_VERSION_MAJOR}_VERSIONS}) - endif() -else() - set(_PYTHON_FIND_OTHER_VERSIONS ${_PYTHON3_VERSIONS} ${_PYTHON2_VERSIONS} ${_PYTHON1_VERSIONS}) -endif() -find_program(PYTHON_EXECUTABLE NAMES ${_Python_NAMES}) - -# Set up the versions we know about, in the order we will search. Always add -# the user supplied additional versions to the front. -set(_Python_VERSIONS ${Python_ADDITIONAL_VERSIONS}) -# If FindPythonInterp has already found the major and minor version, -# insert that version next to get consistent versions of the interpreter and -# library. -if(DEFINED PYTHONLIBS_VERSION_STRING) - string(REPLACE "." ";" _PYTHONLIBS_VERSION "${PYTHONLIBS_VERSION_STRING}") - list(GET _PYTHONLIBS_VERSION 0 _PYTHONLIBS_VERSION_MAJOR) - list(GET _PYTHONLIBS_VERSION 1 _PYTHONLIBS_VERSION_MINOR) - list(APPEND _Python_VERSIONS ${_PYTHONLIBS_VERSION_MAJOR}.${_PYTHONLIBS_VERSION_MINOR}) -endif() -# Search for the current active python version first -list(APPEND _Python_VERSIONS ";") -list(APPEND _Python_VERSIONS ${_PYTHON_FIND_OTHER_VERSIONS}) - -unset(_PYTHON_FIND_OTHER_VERSIONS) -unset(_PYTHON1_VERSIONS) -unset(_PYTHON2_VERSIONS) -unset(_PYTHON3_VERSIONS) - -# Search for newest python version if python executable isn't found -if(NOT PYTHON_EXECUTABLE) - foreach(_CURRENT_VERSION IN LISTS _Python_VERSIONS) - set(_Python_NAMES python${_CURRENT_VERSION}) - if(CMAKE_HOST_WIN32) - list(APPEND _Python_NAMES python) - endif() - find_program(PYTHON_EXECUTABLE - NAMES ${_Python_NAMES} - PATHS - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-32\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-64\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-32\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-64\\InstallPath] - ) - endforeach() -endif() - -# determine python version string -if(PYTHON_EXECUTABLE) - execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c - "import sys; sys.stdout.write(';'.join([str(x) for x in sys.version_info[:3]]))" - OUTPUT_VARIABLE _VERSION - RESULT_VARIABLE _PYTHON_VERSION_RESULT - ERROR_QUIET) - if(NOT _PYTHON_VERSION_RESULT) - string(REPLACE ";" "." PYTHON_VERSION_STRING "${_VERSION}") - list(GET _VERSION 0 PYTHON_VERSION_MAJOR) - list(GET _VERSION 1 PYTHON_VERSION_MINOR) - list(GET _VERSION 2 PYTHON_VERSION_PATCH) - if(PYTHON_VERSION_PATCH EQUAL 0) - # it's called "Python 2.7", not "2.7.0" - string(REGEX REPLACE "\\.0$" "" PYTHON_VERSION_STRING "${PYTHON_VERSION_STRING}") - endif() - else() - # sys.version predates sys.version_info, so use that - # sys.version was first documented for Python 1.5, so assume version 1.4 - # if retrieving sys.version fails. - execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c "try: import sys; sys.stdout.write(sys.version)\nexcept: sys.stdout.write(\"1.4.0\")" - OUTPUT_VARIABLE _VERSION - RESULT_VARIABLE _PYTHON_VERSION_RESULT - ERROR_QUIET) - if(NOT _PYTHON_VERSION_RESULT) - string(REGEX REPLACE " .*" "" PYTHON_VERSION_STRING "${_VERSION}") - string(REGEX REPLACE "^([0-9]+)\\.[0-9]+.*" "\\1" PYTHON_VERSION_MAJOR "${PYTHON_VERSION_STRING}") - string(REGEX REPLACE "^[0-9]+\\.([0-9])+.*" "\\1" PYTHON_VERSION_MINOR "${PYTHON_VERSION_STRING}") - if(PYTHON_VERSION_STRING MATCHES "^[0-9]+\\.[0-9]+\\.([0-9]+)") - set(PYTHON_VERSION_PATCH "${CMAKE_MATCH_1}") - else() - set(PYTHON_VERSION_PATCH "0") - endif() - else() - unset(PYTHON_VERSION_STRING) - unset(PYTHON_VERSION_MAJOR) - unset(PYTHON_VERSION_MINOR) - unset(PYTHON_VERSION_PATCH) - endif() - endif() - unset(_PYTHON_VERSION_RESULT) - unset(_VERSION) -endif() - -include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(PythonInterp REQUIRED_VARS PYTHON_EXECUTABLE VERSION_VAR PYTHON_VERSION_STRING) - -mark_as_advanced(PYTHON_EXECUTABLE) diff --git a/cmake/python/FindPythonLibs.cmake b/cmake/python/FindPythonLibs.cmake deleted file mode 100644 index d3ec7be..0000000 --- a/cmake/python/FindPythonLibs.cmake +++ /dev/null @@ -1,399 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindPythonLibs --------------- - -.. deprecated:: 3.12 - - Use :module:`FindPython3`, :module:`FindPython2` or :module:`FindPython` instead. - -Find python libraries - -This module finds if Python is installed and determines where the -include files and libraries are. It also determines what the name of -the library is. This code sets the following variables: - -:: - - PYTHONLIBS_FOUND - have the Python libs been found - PYTHON_LIBRARIES - path to the python library - PYTHON_INCLUDE_PATH - path to where Python.h is found (deprecated) - PYTHON_INCLUDE_DIRS - path to where Python.h is found - PYTHON_DEBUG_LIBRARIES - path to the debug library (deprecated) - PYTHONLIBS_VERSION_STRING - version of the Python libs found (since CMake 2.8.8) - - - -The Python_ADDITIONAL_VERSIONS variable can be used to specify a list -of version numbers that should be taken into account when searching -for Python. You need to set this variable before calling -find_package(PythonLibs). - -If you'd like to specify the installation of Python to use, you should -modify the following cache variables: - -:: - - PYTHON_LIBRARY - path to the python library - PYTHON_INCLUDE_DIR - path to where Python.h is found - -If calling both ``find_package(PythonInterp)`` and -``find_package(PythonLibs)``, call ``find_package(PythonInterp)`` first to -get the currently active Python version by default with a consistent version -of PYTHON_LIBRARIES. -#]=======================================================================] - -# Use the executable's path as a hint -set(_Python_LIBRARY_PATH_HINT) -if(IS_ABSOLUTE "${PYTHON_EXECUTABLE}") - if(WIN32) - get_filename_component(_Python_PREFIX "${PYTHON_EXECUTABLE}" PATH) - if(_Python_PREFIX) - set(_Python_LIBRARY_PATH_HINT ${_Python_PREFIX}/libs) - endif() - unset(_Python_PREFIX) - else() - get_filename_component(_Python_PREFIX "${PYTHON_EXECUTABLE}" PATH) - get_filename_component(_Python_PREFIX "${_Python_PREFIX}" PATH) - if(_Python_PREFIX) - set(_Python_LIBRARY_PATH_HINT ${_Python_PREFIX}/lib) - endif() - unset(_Python_PREFIX) - endif() -endif() - -include(${CMAKE_CURRENT_LIST_DIR}/CMakeFindFrameworks.cmake) -# Search for the python framework on Apple. -CMAKE_FIND_FRAMEWORKS(Python) - -# Save CMAKE_FIND_FRAMEWORK -if(DEFINED CMAKE_FIND_FRAMEWORK) - set(_PythonLibs_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) -else() - unset(_PythonLibs_CMAKE_FIND_FRAMEWORK) -endif() -# To avoid picking up the system Python.h pre-maturely. -set(CMAKE_FIND_FRAMEWORK LAST) - -set(_PYTHON1_VERSIONS 1.6 1.5) -set(_PYTHON2_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) -set(_PYTHON3_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) - -if(PythonLibs_FIND_VERSION) - if(PythonLibs_FIND_VERSION_COUNT GREATER 1) - set(_PYTHON_FIND_MAJ_MIN "${PythonLibs_FIND_VERSION_MAJOR}.${PythonLibs_FIND_VERSION_MINOR}") - unset(_PYTHON_FIND_OTHER_VERSIONS) - if(PythonLibs_FIND_VERSION_EXACT) - if(_PYTHON_FIND_MAJ_MIN STREQUAL PythonLibs_FIND_VERSION) - set(_PYTHON_FIND_OTHER_VERSIONS "${PythonLibs_FIND_VERSION}") - else() - set(_PYTHON_FIND_OTHER_VERSIONS "${PythonLibs_FIND_VERSION}" "${_PYTHON_FIND_MAJ_MIN}") - endif() - else() - foreach(_PYTHON_V ${_PYTHON${PythonLibs_FIND_VERSION_MAJOR}_VERSIONS}) - if(NOT _PYTHON_V VERSION_LESS _PYTHON_FIND_MAJ_MIN) - list(APPEND _PYTHON_FIND_OTHER_VERSIONS ${_PYTHON_V}) - endif() - endforeach() - endif() - unset(_PYTHON_FIND_MAJ_MIN) - else() - set(_PYTHON_FIND_OTHER_VERSIONS ${_PYTHON${PythonLibs_FIND_VERSION_MAJOR}_VERSIONS}) - endif() -else() - set(_PYTHON_FIND_OTHER_VERSIONS ${_PYTHON3_VERSIONS} ${_PYTHON2_VERSIONS} ${_PYTHON1_VERSIONS}) -endif() - -# Set up the versions we know about, in the order we will search. Always add -# the user supplied additional versions to the front. -# If FindPythonInterp has already found the major and minor version, -# insert that version between the user supplied versions and the stock -# version list. -set(_Python_VERSIONS ${Python_ADDITIONAL_VERSIONS}) -if(DEFINED PYTHON_VERSION_MAJOR AND DEFINED PYTHON_VERSION_MINOR) - list(APPEND _Python_VERSIONS ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}) -endif() -list(APPEND _Python_VERSIONS ${_PYTHON_FIND_OTHER_VERSIONS}) - -unset(_PYTHON_FIND_OTHER_VERSIONS) -unset(_PYTHON1_VERSIONS) -unset(_PYTHON2_VERSIONS) -unset(_PYTHON3_VERSIONS) - -# Python distribution: define which architectures can be used -if (CMAKE_SIZEOF_VOID_P) - # In this case, search only for 64bit or 32bit - math (EXPR _PYTHON_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") - set (_PYTHON_ARCH2 _PYTHON_PREFIX_ARCH}) -else() - if (PYTHON_EXECUTABLE) - # determine interpreter architecture - execute_process (COMMAND "${PYTHON_EXECUTABLE}" -c "import sys; print(sys.maxsize > 2**32)" - RESULT_VARIABLE _PYTHON_RESULT - OUTPUT_VARIABLE _PYTHON_IS64BIT - ERROR_VARIABLE _PYTHON_IS64BIT) - if (NOT _PYTHON_RESULT) - if (_PYTHON_IS64BIT) - set (_PYTHON_ARCH 64) - set (_PYTHON_ARCH2 64) - else() - set (_PYTHON_ARCH 32) - set (_PYTHON_ARCH2 32) - endif() - endif() - else() - # architecture unknown, search for both 64bit and 32bit - set (_PYTHON_ARCH 64) - set (_PYTHON_ARCH2 32) - endif() -endif() - -foreach(_CURRENT_VERSION ${_Python_VERSIONS}) - string(REPLACE "." "" _CURRENT_VERSION_NO_DOTS ${_CURRENT_VERSION}) - if(WIN32) - find_library(PYTHON_DEBUG_LIBRARY - NAMES python${_CURRENT_VERSION_NO_DOTS}_d python - NAMES_PER_DIR - HINTS ${_Python_LIBRARY_PATH_HINT} - PATHS - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs/Debug - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/libs/Debug - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/libs/Debug - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs/Debug - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/libs/Debug - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/libs/Debug - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/libs - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/libs - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/libs - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/libs - ) - endif() - - set(PYTHON_FRAMEWORK_LIBRARIES) - if(Python_FRAMEWORKS AND NOT PYTHON_LIBRARY) - foreach(dir ${Python_FRAMEWORKS}) - list(APPEND PYTHON_FRAMEWORK_LIBRARIES - ${dir}/Versions/${_CURRENT_VERSION}/lib) - endforeach() - endif() - find_library(PYTHON_LIBRARY - NAMES - python${_CURRENT_VERSION_NO_DOTS} - python${_CURRENT_VERSION}mu - python${_CURRENT_VERSION}m - python${_CURRENT_VERSION}u - python${_CURRENT_VERSION} - NAMES_PER_DIR - HINTS - ${_Python_LIBRARY_PATH_HINT} - PATHS - ${PYTHON_FRAMEWORK_LIBRARIES} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/libs - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/libs - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/libs - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/libs - ) - # Look for the static library in the Python config directory - find_library(PYTHON_LIBRARY - NAMES python${_CURRENT_VERSION_NO_DOTS} python${_CURRENT_VERSION} - NAMES_PER_DIR - # This is where the static library is usually located - PATH_SUFFIXES python${_CURRENT_VERSION}/config - ) - - # Don't search for include dir until library location is known - if(PYTHON_LIBRARY) - - # Use the library's install prefix as a hint - set(_Python_INCLUDE_PATH_HINT) - # PYTHON_LIBRARY may contain a list because of SelectLibraryConfigurations - # which may have been run previously. If it is the case, the list can be: - # optimized;;debug; - foreach(lib ${PYTHON_LIBRARY} ${PYTHON_DEBUG_LIBRARY}) - if(IS_ABSOLUTE "${lib}") - get_filename_component(_Python_PREFIX "${lib}" PATH) - get_filename_component(_Python_PREFIX "${_Python_PREFIX}" PATH) - if(_Python_PREFIX) - list(APPEND _Python_INCLUDE_PATH_HINT ${_Python_PREFIX}/include) - endif() - unset(_Python_PREFIX) - endif() - endforeach() - - # Add framework directories to the search paths - set(PYTHON_FRAMEWORK_INCLUDES) - if(Python_FRAMEWORKS AND NOT PYTHON_INCLUDE_DIR) - foreach(dir ${Python_FRAMEWORKS}) - list(APPEND PYTHON_FRAMEWORK_INCLUDES - ${dir}/Versions/${_CURRENT_VERSION}/include) - endforeach() - endif() - - find_path(PYTHON_INCLUDE_DIR - NAMES Python.h - HINTS - ${_Python_INCLUDE_PATH_HINT} - PATHS - ${PYTHON_FRAMEWORK_INCLUDES} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/include - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/include - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH}\\InstallPath]/include - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}-${_PYTHON_ARCH2}\\InstallPath]/include - PATH_SUFFIXES - python${_CURRENT_VERSION}mu - python${_CURRENT_VERSION}m - python${_CURRENT_VERSION}u - python${_CURRENT_VERSION} - ) - endif() - - # For backward compatibility, set PYTHON_INCLUDE_PATH. - set(PYTHON_INCLUDE_PATH "${PYTHON_INCLUDE_DIR}") - - if(PYTHON_INCLUDE_DIR AND EXISTS "${PYTHON_INCLUDE_DIR}/patchlevel.h") - file(STRINGS "${PYTHON_INCLUDE_DIR}/patchlevel.h" python_version_str - REGEX "^#define[ \t]+PY_VERSION[ \t]+\"[^\"]+\"") - string(REGEX REPLACE "^#define[ \t]+PY_VERSION[ \t]+\"([^\"]+)\".*" "\\1" - PYTHONLIBS_VERSION_STRING "${python_version_str}") - unset(python_version_str) - endif() - - if(PYTHON_LIBRARY AND PYTHON_INCLUDE_DIR) - break() - endif() -endforeach() - -unset(_Python_INCLUDE_PATH_HINT) -unset(_Python_LIBRARY_PATH_HINT) - -mark_as_advanced( - PYTHON_DEBUG_LIBRARY - PYTHON_LIBRARY - PYTHON_INCLUDE_DIR -) - -# We use PYTHON_INCLUDE_DIR, PYTHON_LIBRARY and PYTHON_DEBUG_LIBRARY for the -# cache entries because they are meant to specify the location of a single -# library. We now set the variables listed by the documentation for this -# module. -set(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") -set(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") - -# These variables have been historically named in this module different from -# what SELECT_LIBRARY_CONFIGURATIONS() expects. -set(PYTHON_LIBRARY_DEBUG "${PYTHON_DEBUG_LIBRARY}") -set(PYTHON_LIBRARY_RELEASE "${PYTHON_LIBRARY}") -include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations.cmake) -SELECT_LIBRARY_CONFIGURATIONS(PYTHON) -# SELECT_LIBRARY_CONFIGURATIONS() sets ${PREFIX}_FOUND if it has a library. -# Unset this, this prefix doesn't match the module prefix, they are different -# for historical reasons. -unset(PYTHON_FOUND) - -# Restore CMAKE_FIND_FRAMEWORK -if(DEFINED _PythonLibs_CMAKE_FIND_FRAMEWORK) - set(CMAKE_FIND_FRAMEWORK ${_PythonLibs_CMAKE_FIND_FRAMEWORK}) - unset(_PythonLibs_CMAKE_FIND_FRAMEWORK) -else() - unset(CMAKE_FIND_FRAMEWORK) -endif() - -include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(PythonLibs - REQUIRED_VARS PYTHON_LIBRARIES PYTHON_INCLUDE_DIRS - VERSION_VAR PYTHONLIBS_VERSION_STRING) - -# PYTHON_ADD_MODULE( src1 src2 ... srcN) is used to build modules for python. -# PYTHON_WRITE_MODULES_HEADER() writes a header file you can include -# in your sources to initialize the static python modules -function(PYTHON_ADD_MODULE _NAME ) - get_property(_TARGET_SUPPORTS_SHARED_LIBS - GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS) - option(PYTHON_ENABLE_MODULE_${_NAME} "Add module ${_NAME}" TRUE) - option(PYTHON_MODULE_${_NAME}_BUILD_SHARED - "Add module ${_NAME} shared" ${_TARGET_SUPPORTS_SHARED_LIBS}) - - # Mark these options as advanced - mark_as_advanced(PYTHON_ENABLE_MODULE_${_NAME} - PYTHON_MODULE_${_NAME}_BUILD_SHARED) - - if(PYTHON_ENABLE_MODULE_${_NAME}) - if(PYTHON_MODULE_${_NAME}_BUILD_SHARED) - set(PY_MODULE_TYPE MODULE) - else() - set(PY_MODULE_TYPE STATIC) - set_property(GLOBAL APPEND PROPERTY PY_STATIC_MODULES_LIST ${_NAME}) - endif() - - set_property(GLOBAL APPEND PROPERTY PY_MODULES_LIST ${_NAME}) - add_library(${_NAME} ${PY_MODULE_TYPE} ${ARGN}) -# target_link_libraries(${_NAME} ${PYTHON_LIBRARIES}) - - if(PYTHON_MODULE_${_NAME}_BUILD_SHARED) - set_target_properties(${_NAME} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}") - if(WIN32 AND NOT CYGWIN) - set_target_properties(${_NAME} PROPERTIES SUFFIX ".pyd") - endif() - endif() - - endif() -endfunction() - -function(PYTHON_WRITE_MODULES_HEADER _filename) - - get_property(PY_STATIC_MODULES_LIST GLOBAL PROPERTY PY_STATIC_MODULES_LIST) - - get_filename_component(_name "${_filename}" NAME) - string(REPLACE "." "_" _name "${_name}") - string(TOUPPER ${_name} _nameUpper) - set(_filename ${CMAKE_CURRENT_BINARY_DIR}/${_filename}) - - set(_filenameTmp "${_filename}.in") - file(WRITE ${_filenameTmp} "/*Created by cmake, do not edit, changes will be lost*/\n") - file(APPEND ${_filenameTmp} -"#ifndef ${_nameUpper} -#define ${_nameUpper} - -#include - -#ifdef __cplusplus -extern \"C\" { -#endif /* __cplusplus */ - -") - - foreach(_currentModule ${PY_STATIC_MODULES_LIST}) - file(APPEND ${_filenameTmp} "extern void init${PYTHON_MODULE_PREFIX}${_currentModule}(void);\n\n") - endforeach() - - file(APPEND ${_filenameTmp} -"#ifdef __cplusplus -} -#endif /* __cplusplus */ - -") - - - foreach(_currentModule ${PY_STATIC_MODULES_LIST}) - file(APPEND ${_filenameTmp} "int ${_name}_${_currentModule}(void) \n{\n static char name[]=\"${PYTHON_MODULE_PREFIX}${_currentModule}\"; return PyImport_AppendInittab(name, init${PYTHON_MODULE_PREFIX}${_currentModule});\n}\n\n") - endforeach() - - file(APPEND ${_filenameTmp} "void ${_name}_LoadAllPythonModules(void)\n{\n") - foreach(_currentModule ${PY_STATIC_MODULES_LIST}) - file(APPEND ${_filenameTmp} " ${_name}_${_currentModule}();\n") - endforeach() - file(APPEND ${_filenameTmp} "}\n\n") - file(APPEND ${_filenameTmp} "#ifndef EXCLUDE_LOAD_ALL_FUNCTION\nvoid CMakeLoadAllPythonModules(void)\n{\n ${_name}_LoadAllPythonModules();\n}\n#endif\n\n#endif\n") - -# with configure_file() cmake complains that you may not use a file created using file(WRITE) as input file for configure_file() - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_filenameTmp}" "${_filename}" OUTPUT_QUIET ERROR_QUIET) - -endfunction() diff --git a/cmake/release.cmake b/cmake/release.cmake index d4bfae5..5960325 100644 --- a/cmake/release.cmake +++ b/cmake/release.cmake @@ -49,6 +49,7 @@ macro(RELEASE_SETUP) if(UNIX) find_program(GIT git) + string(TIMESTAMP TODAY "%Y-%m-%d") # Set LD_LIBRARY_PATH if(APPLE) @@ -60,20 +61,20 @@ macro(RELEASE_SETUP) add_custom_target( release_package_xml WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Update package.xml" COMMAND - echo "Updating package.xml to $$VERSION" && sed -i.back - \"s|.*|$$VERSION|g\" package.xml - && rm package.xml.back && ${GIT} add package.xml && ${GIT} commit -m - "release: Update package.xml version to $$VERSION" && echo + sed -i.back \"s|.*|$$VERSION|g\" + package.xml && rm package.xml.back && ${GIT} add package.xml && ${GIT} + commit -m "release: Update package.xml version to $$VERSION" && echo "Updated package.xml and committed") add_custom_target( release_pyproject_toml WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Update pyproject.toml" COMMAND - echo "Updating pyproject.toml to $$VERSION" && ${PYTHON_EXECUTABLE} - ${PROJECT_JRL_CMAKE_MODULE_DIR}/pyproject.py $$VERSION && if ! - (git diff --quiet pyproject.toml) ; then + ${PYTHON_EXECUTABLE} ${PROJECT_JRL_CMAKE_MODULE_DIR}/pyproject.py + $$VERSION && if ! (git diff --quiet pyproject.toml) ; then (${GIT} add pyproject.toml @@ -86,9 +87,32 @@ macro(RELEASE_SETUP) echo "Updated pyproject.toml and committed") ; fi) + add_custom_target( + release_changelog + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Update CHANGELOG.md" + COMMAND + sed -i.back + "\"s|\#\# \\[Unreleased\\]|\#\# [Unreleased]\\n\\n\#\# [$$VERSION] - ${TODAY}|\"" + CHANGELOG.md && sed -i.back + "\"s|^\\[Unreleased]: \\(https://.*compare/\\)\\(v.*\\)...HEAD|[Unreleased]: \\1v$$VERSION...HEAD\\n[$$VERSION]: \\1\\2...v$$VERSION|\"" + CHANGELOG.md && if ! (git diff --quiet CHANGELOG.md) ; then + (${GIT} + add + CHANGELOG.md + && + ${GIT} + commit + -m + "release: Update CHANGELOG.md for $$VERSION" + && + echo + "Updated CHANGELOG.md and committed") ; fi) + add_custom_target( release WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Create a new release" COMMAND export LD_LIBRARY_PATH=$ENV{LD_LIBRARY_PATH} && export ${LD_LIBRARY_PATH_VARIABLE_NAME}=$ENV{${LD_LIBRARY_PATH_VARIABLE_NAME}} @@ -100,8 +124,11 @@ macro(RELEASE_SETUP) release_package_xml) ; fi # Update version in pyproject.toml if it exists && if [ -f "pyproject.toml" ]; then (make -C ${CMAKE_BINARY_DIR} - release_pyproject_toml) ; fi && - ${GIT} tag -s v$$VERSION -m "Release of version $$VERSION." && cd + release_pyproject_toml) ; fi + # Update CHANGELOG.md if it exists + && if [ -f "CHANGELOG.md" ]; then (make -C ${CMAKE_BINARY_DIR} + release_changelog) ; fi && ${GIT} + tag -s v$$VERSION -m "Release of version $$VERSION." && cd ${CMAKE_BINARY_DIR} && cmake ${PROJECT_SOURCE_DIR} && make distcheck || (echo "Please fix distcheck first." diff --git a/cmake/relpath.cmake b/cmake/relpath.cmake new file mode 100644 index 0000000..eefc7c3 --- /dev/null +++ b/cmake/relpath.cmake @@ -0,0 +1,49 @@ +# +# Copyright (C) 2023 LAAS-CNRS +# +# Author: Guilhem Saurel +# + +# .rst: .. ifmode:: user +# +# .. command:: REL_INSTALL_PATH(FILE VARIABLE) +# +# Compute the relative path from the installation prefix to FILE. Works for +# relative and absolute FILE, except for absolute paths outside of +# CMAKE_INSTALL_PREFIX. +# +macro(REL_INSTALL_PATH FILE VARIABLE) + if(IS_ABSOLUTE ${FILE}) + file(RELATIVE_PATH _VARIABLE "${FILE}" ${CMAKE_INSTALL_PREFIX}) + string(REGEX REPLACE "/$" "" ${VARIABLE} "${_VARIABLE}") + else() + string(REGEX REPLACE "[^/]+" ".." ${VARIABLE} "${FILE}") + endif() +endmacro() + +# .rst: .. ifmode:: user +# +# .. command:: GET_RELATIVE_RPATH(TARGET_INSTALL_DIR VARIABLE) +# +# Provide INSTALL_RPATH from TARGET_INSTALL_DIR to CMAKE_INSTALL_LIBDIR as +# relative to $ORIGIN / @loader_path. Works for relative and absolute +# TARGET_INSTALL_DIR and CMAKE_INSTALL_LIBDIR, except for absolute paths outside +# of CMAKE_INSTALL_PREFIX. Only on UNIX systems (including APPLE). +# +macro(GET_RELATIVE_RPATH TARGET_INSTALL_DIR VARIABLE) + if(UNIX) + if(APPLE) + set(ORIGIN "@loader_path") + else() + set(ORIGIN "\$ORIGIN") + endif() + rel_install_path("${TARGET_INSTALL_DIR}" _TGT_INV_REL) + if(IS_ABSOLUTE ${CMAKE_INSTALL_LIBDIR}) + file(RELATIVE_PATH _LIB_REL "${CMAKE_INSTALL_PREFIX}" + ${CMAKE_INSTALL_LIBDIR}) + else() + set(_LIB_REL ${CMAKE_INSTALL_LIBDIR}) + endif() + set(${VARIABLE} "${ORIGIN}/${_TGT_INV_REL}/${_LIB_REL}") + endif() +endmacro() diff --git a/cmake/stubs.cmake b/cmake/stubs.cmake index 6112ab8..31a76bc 100644 --- a/cmake/stubs.cmake +++ b/cmake/stubs.cmake @@ -58,8 +58,10 @@ endmacro(LOAD_STUBGEN) # .rst: .. command:: LOAD_STUBGEN(module_path module_name module_install_dir) # -# Generate the stubs associated to a given project. If the TARGET python exists, -# then the stubs generation will be performed after python target. +# Generate the stubs associated to a given project. If optional arguments (which +# should be CMake targets) are supplied, then the stubs will only be generated +# after every specified target is built. On windows, the PATH will also be +# modified to find the provided targets. # # .rst: .. variable:: module_path # @@ -95,17 +97,49 @@ function(GENERATE_STUBS module_path module_name module_install_dir) set(PYTHONPATH ${module_path}) endif($ENV{PYTHONPATH}) + # On Windows with Python 3.8+, Python doesn't search DLL in PATH anymore. + # + # DLL are build in a different directory than the module, we must then specify + # to pybind11_stubgen where to find it with the + # PYBIND11_STUBGEN_ADD_DLL_DIRECTORY environment variable. + # + # See https://github.com/python/cpython/issues/87339#issuecomment-1093902060 + set(ENV_DLL_PATH) + set(optional_args ${ARGN}) + if(WIN32) + foreach(py_target IN LISTS optional_args) + if(TARGET ${py_target}) + set(_is_lib + "$,SHARED_LIBRARY>") + set(_target_dir "$") + set(_target_path $<${_is_lib}:${_target_dir}> ${_target_path}) + endif() + endforeach() + # Join the list with escaped semicolon to keep the environment path format + # when giving it to `add_custom_target` + string(REPLACE ";" "\\\;" _join_target_path "${_target_path}") + set(ENV_DLL_PATH PYBIND11_STUBGEN_ADD_DLL_DIRECTORY=${_join_target_path}) + endif() + add_custom_target( ${target_name} ALL COMMAND - ${CMAKE_COMMAND} -E env PYTHONPATH=${PYTHONPATH} "${PYTHON_EXECUTABLE}" - "${STUBGEN_MAIN_FILE}" "-o" "${module_path}" "${module_name}" - "--boost-python" --ignore-invalid signature "--no-setup-py" - "--root-module-suffix" "" + ${CMAKE_COMMAND} -E env ${ENV_DLL_PATH} ${CMAKE_COMMAND} -E env + PYTHONPATH=${PYTHONPATH} "${PYTHON_EXECUTABLE}" "${STUBGEN_MAIN_FILE}" + "-o" "${module_path}" "${module_name}" "--boost-python" --ignore-invalid + signature "--no-setup-py" "--root-module-suffix" "" VERBATIM) - if(TARGET python) - add_dependencies(${target_name} python) - endif(TARGET python) + foreach(py_target IN LISTS optional_args) + if(TARGET ${py_target}) + message( + STATUS + "generate_stubs: adding dependency on ${py_target}. Stubs will be generated after it is built." + ) + add_dependencies(${target_name} ${py_target}) + else(TARGET ${py_target}) + message(WARNING "generate_stubs: target ${py_target} not known.") + endif(TARGET ${py_target}) + endforeach() install( DIRECTORY ${module_path}/${module_name} diff --git a/cmake/test.cmake b/cmake/test.cmake index d2877fe..8a1055d 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -49,7 +49,7 @@ if(NOT TARGET build_tests) endif() # Add new target 'run_tests' to improve integration with build tooling -if(NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT TARGET run_tests) +if(NOT CMAKE_GENERATOR MATCHES "Visual Studio|Xcode" AND NOT TARGET run_tests) add_custom_target( run_tests COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -V @@ -117,35 +117,22 @@ endmacro( NAME SOURCE) -# .rst: .. command:: ADD_PYTHON_UNIT_TEST (NAME SOURCE [MODULES...]) +# .rst: .. command:: COMPUTE_PYTHONPATH (result [MODULES...]) # -# Add a test called `NAME` that runs an equivalent of ``python ${SOURCE}``, -# optionnaly with a `PYTHONPATH` set to `CMAKE_BINARY_DIR/MODULE_PATH` for each -# MODULES `SOURCE` is relative to `PROJECT_SOURCE_DIR` +# Fill `result` with all necessary environment variables (`PYTHONPATH`, +# `LD_LIBRARY_PATH`, `DYLD_LIBRARY_PATH`) to load the `MODULES` in +# `CMAKE_BINARY_DIR` (`CMAKE_BINARY_DIR/MODULE_PATH`) # # .. note:: :command:`FINDPYTHON` should have been called first. # -macro(ADD_PYTHON_UNIT_TEST NAME SOURCE) - if(ENABLE_COVERAGE) - set_property(GLOBAL PROPERTY JRL_CMAKEMODULES_HAS_PYTHON_COVERAGE ON) - set(PYTHONPATH "${CMAKE_INSTALL_PREFIX}/${PYTHON_SITELIB}") - add_test( - NAME ${NAME} - COMMAND ${PYTHON_EXECUTABLE} -m coverage run --branch -p - --source=${PYTHONPATH} "${PROJECT_SOURCE_DIR}/${SOURCE}" - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - else() - add_test(NAME ${NAME} COMMAND ${PYTHON_EXECUTABLE} - "${PROJECT_SOURCE_DIR}/${SOURCE}") - set(PYTHONPATH) - endif() - +function(COMPUTE_PYTHONPATH result) set(MODULES "${ARGN}") # ARGN is not a variable foreach(MODULE_PATH IN LISTS MODULES) - list(APPEND PYTHONPATH "${CMAKE_BINARY_DIR}/${MODULE_PATH}") - if(CMAKE_GENERATOR MATCHES "Visual Studio") + if(CMAKE_GENERATOR MATCHES "Visual Studio|Xcode") list(APPEND PYTHONPATH "${CMAKE_BINARY_DIR}/${MODULE_PATH}/$") - endif(CMAKE_GENERATOR MATCHES "Visual Studio") + else() + list(APPEND PYTHONPATH "${CMAKE_BINARY_DIR}/${MODULE_PATH}") + endif() endforeach(MODULE_PATH IN LISTS MODULES) if(DEFINED ENV{PYTHONPATH}) @@ -162,7 +149,7 @@ macro(ADD_PYTHON_UNIT_TEST NAME SOURCE) if(WIN32) # ensure that severals paths stay together as ENV variable PYTHONPATH when # passed to python test via PROPERTIES - string(REPLACE ";" "\;" PYTHONPATH_STR "${PYTHONPATH}") + string(REPLACE ";" "\\\;" PYTHONPATH_STR "${PYTHONPATH}") else(WIN32) string(REPLACE ";" "${PATHSEP}" PYTHONPATH_STR "${PYTHONPATH}") endif(WIN32) @@ -171,12 +158,69 @@ macro(ADD_PYTHON_UNIT_TEST NAME SOURCE) list(APPEND ENV_VARIABLES "LD_LIBRARY_PATH=$ENV{LD_LIBRARY_PATH}") list(APPEND ENV_VARIABLES "DYLD_LIBRARY_PATH=$ENV{DYLD_LIBRARY_PATH}") endif(APPLE) + + set(${result} + ${ENV_VARIABLES} + PARENT_SCOPE) +endfunction() + +# .rst: .. command:: ADD_PYTHON_UNIT_TEST (NAME SOURCE [MODULES...]) +# +# Add a test called `NAME` that runs an equivalent of ``python ${SOURCE}``, +# optionnaly with a `PYTHONPATH` set to `CMAKE_BINARY_DIR/MODULE_PATH` for each +# MODULES `SOURCE` is relative to `PROJECT_SOURCE_DIR` +# +# .. note:: :command:`FINDPYTHON` should have been called first. +# +macro(ADD_PYTHON_UNIT_TEST NAME SOURCE) + if(ENABLE_COVERAGE) + set_property(GLOBAL PROPERTY JRL_CMAKEMODULES_HAS_PYTHON_COVERAGE ON) + set(PYTHONPATH "${CMAKE_INSTALL_PREFIX}/${PYTHON_SITELIB}") + add_test( + NAME ${NAME} + COMMAND ${PYTHON_EXECUTABLE} -m coverage run --branch -p + --source=${PYTHONPATH} "${PROJECT_SOURCE_DIR}/${SOURCE}" + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + else() + add_test(NAME ${NAME} COMMAND ${PYTHON_EXECUTABLE} + "${PROJECT_SOURCE_DIR}/${SOURCE}") + set(PYTHONPATH) + endif() + + set(MODULES "${ARGN}") # ARGN is not a variable + compute_pythonpath(ENV_VARIABLES ${MODULES}) set_tests_properties(${NAME} PROPERTIES ENVIRONMENT "${ENV_VARIABLES}") endmacro( ADD_PYTHON_UNIT_TEST NAME SOURCE) +# .rst: .. command:: ADD_PYTHON_MEMORYCHECK_UNIT_TEST (NAME SOURCE [MODULES...]) +# +# Add a test called `NAME` that runs an equivalent of ``valgrind -- python +# ${SOURCE}``, optionnaly with a `PYTHONPATH` set to +# `CMAKE_BINARY_DIR/MODULE_PATH` for each MODULES `SOURCE` is relative to +# `PROJECT_SOURCE_DIR` +# +# .. note:: :command:`FINDPYTHON` should have been called first. .. note:: Only +# work if valgrind is installed +# +macro(ADD_PYTHON_MEMORYCHECK_UNIT_TEST NAME SOURCE) + if(MEMORYCHECK_COMMAND AND MEMORYCHECK_COMMAND MATCHES ".*valgrind$") + set(TEST_FILE_NAME memorycheck_unit_test_${NAME}.cmake) + set(PYTHON_TEST_SCRIPT "${PROJECT_SOURCE_DIR}/${SOURCE}") + configure_file( + ${PROJECT_JRL_CMAKE_MODULE_DIR}/memorycheck_unit_test.cmake.in + ${TEST_FILE_NAME} @ONLY) + + add_test(NAME ${NAME} COMMAND ${CMAKE_COMMAND} -P ${TEST_FILE_NAME}) + + set(MODULES "${ARGN}") # ARGN is not a variable + compute_pythonpath(ENV_VARIABLES ${MODULES}) + set_tests_properties(${NAME} PROPERTIES ENVIRONMENT "${ENV_VARIABLES}") + endif() +endmacro() + # .rst: .. command:: ADD_JULIA_UNIT_TEST (NAME SOURCE [MODULES...]) # # Add a test called `NAME` that runs an equivalent of ``julia ${SOURCE}``. diff --git a/cmake/uninstall.cmake b/cmake/uninstall.cmake index 4b06de9..4bd9895 100644 --- a/cmake/uninstall.cmake +++ b/cmake/uninstall.cmake @@ -51,6 +51,9 @@ endmacro(_SETUP_PROJECT_UNINSTALL) # We setup the auto-uninstall target here, it is early enough that we can ensure # it is going to be called first See the first paragraph here # https://cmake.org/cmake/help/latest/command/install.html#introduction +if(DEFINED CMAKE_CONFIGURATION_TYPES) + set(UNINSTALL_CONFIG_ARG "--config \${CMAKE_INSTALL_CONFIG_NAME}") +endif() install( - CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" --build \"${PROJECT_BINARY_DIR}\" --config \${CMAKE_INSTALL_CONFIG_NAME} --target uninstall)" + CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" --build \"${PROJECT_BINARY_DIR}\" ${UNINSTALL_CONFIG_ARG} --target uninstall)" ) diff --git a/cmake/version-script.cmake b/cmake/version-script.cmake index 4a548a8..335e4a4 100644 --- a/cmake/version-script.cmake +++ b/cmake/version-script.cmake @@ -18,10 +18,7 @@ include(CheckCCompilerFlag) if(${CMAKE_VERSION} VERSION_LESS 3.18.0) # Do nothing else() - cmake_policy(PUSH) - cmake_policy(SET CMP0057 NEW) # if IN_LIST include(CheckLinkerFlag) - cmake_policy(POP) endif() # _CHECK_VERSION_SCRIPT_SUPPORT diff --git a/cmake/version.cmake b/cmake/version.cmake index 40959d6..4a7fb1a 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -120,14 +120,9 @@ endfunction() function(_COMPUTE_VERSION_FROM_ROS_PACKAGE_XML_FILE) if(EXISTS ${PROJECT_SOURCE_DIR}/package.xml) file(READ "${PROJECT_SOURCE_DIR}/package.xml" PACKAGE_XML) - execute_process( - COMMAND cat "${PROJECT_SOURCE_DIR}/package.xml" - COMMAND grep - COMMAND cut -f1 -d < - OUTPUT_STRIP_TRAILING_WHITESPACE - # COMMAND_ECHO STDOUT - OUTPUT_VARIABLE PACKAGE_XML_VERSION) + string(REGEX REPLACE "^.*(.*).*$" "\\1" + _PACKAGE_XML_VERSION ${PACKAGE_XML}) + string(STRIP ${_PACKAGE_XML_VERSION} PACKAGE_XML_VERSION) if(NOT "${PACKAGE_XML_VERSION}" STREQUAL "") set(PROJECT_VERSION ${PACKAGE_XML_VERSION} @@ -203,7 +198,11 @@ macro(VERSION_COMPUTE) set(PROJECT_STABLE False) if("${PROJECT_SOURCE_DIR}" STREQUAL "") - set(PROJECT_SOURCE_DIR "${PROJECT_JRL_CMAKE_MODULE_DIR}/..") + if(EXISTS "${PROJECT_JRL_CMAKE_MODULE_DIR}/../CMakeLists.txt") + set(PROJECT_SOURCE_DIR "${PROJECT_JRL_CMAKE_MODULE_DIR}/..") + else() + set(PROJECT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + endif() endif() if(NOT DEFINED PROJECT_VERSION_COMPUTATION_METHODS) list(APPEND PROJECT_VERSION_COMPUTATION_METHODS "ROS_PACKAGE_XML_FILE" diff --git a/doc/generate_distance_plot.py b/doc/generate_distance_plot.py index bfc66de..1456a2f 100644 --- a/doc/generate_distance_plot.py +++ b/doc/generate_distance_plot.py @@ -1,6 +1,5 @@ import matplotlib.pyplot as plt import numpy as np -from math import sqrt interactive = False diff --git a/doc/gjk.py b/doc/gjk.py index 3d200b1..d80564c 100644 --- a/doc/gjk.py +++ b/doc/gjk.py @@ -10,7 +10,8 @@ edge_fmt = "{j}a * {b}a_{c}a + {j}{b} * {c}a_aa - {j}{c} * {b}a_aa" # These checks must be negative and not positive, as in the cheat sheet. -# They are the same as in the cheat sheet, except that we consider (...).dot(A) instead of (...).dot(-A) +# They are the same as in the cheat sheet, except that we consider (...).dot(A) +# instead of (...).dot(-A) plane_tests = ["C.dot (a_cross_b)", "D.dot(a_cross_c)", "-D.dot(a_cross_b)"] checks = ( plane_tests @@ -247,9 +248,9 @@ def set_test_values(current_tests, test_values, itest, value): def satisfies(values, indices): for k in indices: - if k > 0 and values[k - 1] != True: + if k > 0 and values[k - 1] is not True: return False - if k < 0 and values[-k - 1] != False: + if k < 0 and values[-k - 1] is not False: return False return True @@ -286,17 +287,17 @@ def set_tests_values(current_tests, test_values, itests, values): def apply_test_values(cases, test_values): def canSatisfy(values, indices): for k in indices: - if k > 0 and values[k - 1] == False: + if k > 0 and values[k - 1] is False: return False - if k < 0 and values[-k - 1] == True: + if k < 0 and values[-k - 1] is True: return False return True def satisfies(values, indices): for k in indices: - if k > 0 and values[k - 1] != True: + if k > 0 and values[k - 1] is not True: return False - if k < 0 and values[-k - 1] != False: + if k < 0 and values[-k - 1] is not False: return False return True @@ -337,7 +338,7 @@ def max_number_of_tests( prevScore=0, ): for test in current_tests: - assert test_values[test] == None, "Test " + str(test) + " already performed" + assert test_values[test] is None, "Test " + str(test) + " already performed" left_cases = apply_test_values(cases, test_values) @@ -375,7 +376,8 @@ def max_number_of_tests( remaining_tests = None if remaining_tests is not None: - # Do not put this in try catch as I do not want other ValueError to be understood as an infeasible branch. + # Do not put this in try catch as I do not want other ValueError to be + # understood as an infeasible branch. score_if_t, order_if_t = max_number_of_tests( remaining_tests, left_cases, @@ -396,7 +398,8 @@ def max_number_of_tests( remaining_tests = None if remaining_tests is not None: - # Do not put this in try catch as I do not want other ValueError to be understood as an infeasible branch. + # Do not put this in try catch as I do not want other ValueError to be + # understood as an infeasible branch. score_if_f, order_if_f = max_number_of_tests( remaining_tests, left_cases, @@ -438,37 +441,37 @@ def printOrder(order, indent="", start=True, file=sys.stdout, curTests=[]): file=file, ) print(indent + "const vertex_id_t a = 3, b = 2, c = 1, d = 0;", file=file) - for l in "abcd": + for v in "abcd": print( indent - + "const Vec3f& {} (current.vertex[{}]->w);".format(l.upper(), l), + + "const Vec3f& {} (current.vertex[{}]->w);".format(v.upper(), v), file=file, ) - print(indent + "const FCL_REAL aa = A.squaredNorm();".format(l), file=file) - for l in "dcb": + print(indent + "const FCL_REAL aa = A.squaredNorm();".format(), file=file) + for v in "dcb": for m in "abcd": - if m <= l: + if m <= v: print( indent + "const FCL_REAL {0}{1} = {2}.dot({3});".format( - l, m, l.upper(), m.upper() + v, m, v.upper(), m.upper() ), file=file, ) else: print( - indent + "const FCL_REAL& {0}{1} = {1}{0};".format(l, m), + indent + "const FCL_REAL& {0}{1} = {1}{0};".format(v, m), file=file, ) - print(indent + "const FCL_REAL {0}a_aa = {0}a - aa;".format(l), file=file) + print(indent + "const FCL_REAL {0}a_aa = {0}a - aa;".format(v), file=file) for l0, l1 in zip("bcd", "cdb"): print( indent + "const FCL_REAL {0}a_{1}a = {0}a - {1}a;".format(l0, l1), file=file, ) - for l in "bc": + for v in "bc": print( - indent + "const Vec3f a_cross_{0} = A.cross({1});".format(l, l.upper()), + indent + "const Vec3f a_cross_{0} = A.cross({1});".format(v, v.upper()), file=file, ) print("", file=file) @@ -502,15 +505,12 @@ def printOrder(order, indent="", start=True, file=sys.stdout, curTests=[]): elif region == "A": print(indent + "originToPoint (current, a, A, next, ray);", file=file) elif len(region) == 2: - a = region[0] + region[0] B = region[1] print( - indent - + "originToSegment (current, a, {b}, A, {B}, {B}-A, -{b}a_aa, next, ray);".format( - **{ - "b": B.lower(), - "B": B, - } + indent + "originToSegment " + "(current, a, {b}, A, {B}, {B}-A, -{b}a_aa, next, ray);".format( + **{"b": B.lower(), "B": B} ), file=file, ) @@ -524,8 +524,8 @@ def printOrder(order, indent="", start=True, file=sys.stdout, curTests=[]): else: test = "-" + test print( - indent - + "originToTriangle (current, a, {b}, {c}, ({B}-A).cross({C}-A), {t}, next, ray);".format( + indent + "originToTriangle " + "(current, a, {b}, {c}, ({B}-A).cross({C}-A), {t}, next, ray);".format( **{"b": B.lower(), "c": C.lower(), "B": B, "C": C, "t": test} ), file=file, diff --git a/doc/images/hpp-fcl-vs-the-rest-of-the-world.png b/doc/images/hpp-fcl-vs-the-rest-of-the-world.png new file mode 100644 index 0000000000000000000000000000000000000000..b5ec13efc34895a2ccc6dc5c80f1250e59c8c1bf GIT binary patch literal 98179 zcmb4rcU)83wlzdR1QC!bh%}L^h=NFuRH>1o^e!M$rATioRRN_+M>V)zb-=IeS1s$>i~S}b&A2X6Ysxwa6Kks z4ZeDIF}`~$3ClB))l;+iiEok?-VU~NN6tf-$nG!YF#@EBWCf(`|mHh z1YzYW=KsH6rQ(Gv9kTv$N5tQF;F*1evfuu6G-4Kb=2^cJe?FyhlAb(4Udro#K3q>; zMM!giezpB&GizEoJbVHQ_BOV$-1N&SH~po z?ubmN_uP5wv!Xv$7H{O)QEXUmIWsdeG%#>KQhYN+;_&Ck-MyJeeIFle@=RTX@G#AS zO`~%s^A_uWzgB<7fQ)?kC`I8nKbS1s-A9_RYYyY_&4(6ZUc0s#r6o<6x5E@M^a5Yw z-@Ow!m{^dsGCur4*BF$gQeI+MKV;@TEMTc!C7MKDXHzW(UPWa#GRW;WrSs)bp#l3I~?lNE?wpS01dDl5B2 zscdZIy&>nd*}+ML5D*d;9uFj?D=aLu{p`))$b|CYZNJK9ywdrSNuK?`k2U-Zfx8Cx ziOu0=+>Y$iD2B&Pn86Y~8=C^3!x`r8QrX?!cz)xm67M-)q{`j9cXzQUzat+qrNq49 zNuSN=G!+8PG`V1MX0_7?#s+O$GhMq0B|BYl{5gL*WGPIL$Kt2Y>??)UUIpeIjC!5> zMuyM)^|W_mV|u*&{Ktni-upnOf~DY=z;5?fS@-9>Aoa%dr=<$4)h?ta2^8WFZy>9m zoMzvwM4lcs75wjulz8@bx}d^J=)}W;=2r!RqM{F{uS2({YO>@--bP{zS8yoX$rXGGsHd-uA(#c7I+u;RJovV%6 z&Hi{MvB%$DyL|r;*jl>c9@JpfP;8~Mv;PlvA^?r()w78=xE+mINKAK15_ zYD#%M>DnMWt=lwf(^!80dB-0j)5{a!(wzLgca{fR_1+c~P)(Kl=)N=i^r8cc#fF*= zZ=4OV+K&P=C+hO0Kh2yo1P`>n;5#`zz2o7rpFE_atu1Wc6c7;be;Sx_xAo|!Dy<$g z?Ck6}Zrq6D`eA_4?E97Mu(!t^0e;Wo$Fi|HL4Q7i1^y( z$;ruS=jeF#Rd^b9)vC&4%hI7HC+BlmSXg0o^)RDR+5OOtyz=sX#w*<1?}CDe>b=m# z`(#ex;o&))O+i6H4IU3|Y*bekS5}l;^9l>M<9dmO@U9rvZTkq^7Z4X;y`0JUg?b9z zyGcVsGc-6jr;0Y`G6sG&YZZ*~pMR!GOml&<3m^&FsVrSkPq{eqwLK`?V=dS1&Mi=Rp2dzvP z@7!YAcD*vL*${z9*!2?S9N5i1;F%Ck^e9^0Y@!_3G_^(KqfH$q>tbu>`y|NLKi1Wf zGq&z-&nG!}qTQ^uHti{Aaekt=IXEXP(v`I7)t6>_yiND7L)Ht!6!;hpVp_#O5@?96ugD)Cw}_AAwLNtuAy9hnL<5VK6*GXeR@{>0hK4p2Tj;o0asfM8-`!VMRu`KF2ER60s1 zote6g+icVJ^-#9^W}bTl10_*Y!kT+d3K0|`#r_zzue*DXT$JUzDBGL;r})ir&eCTn zLgM`+^nIn$H+arv-(qQo)ghJEpWbY)HC-caRe-ngHKW${V#1*E$n_Nt@-c_93Q4{{#wX$Hl*jFIL=^>bk^k=g z+S=NiPUdqmEMH*S#Rj2HyB%V%H#9wuY>G?p$n99$>0zwYJv(v5^|zsmg_BiP+fO<% z#ykLjovNs+Vxy#jBHf)=3xEmb^$zR`I>O_N#3*NLe+Qf%C+X-G`J>GcIk{6?Nfqh%J~ztW3Hv;2T9 zM@yr+Ia&Svy#fx_>$`pKu(A33yG>*uIB*mcj{NB>MYGxi^~NVCDY;YvmY0{$p{GeS z12otvUbK7;XMB1}WgwX}X5KrTu;v42nzxgD{?V4yQ9o`k^2q&j%|Jjk(vw$!$8~?; z`=`&!g%9HR_w^k7{F>3liyIpovc_z&v9U(>Shw5pv9TUZc<9|-&za}XpQ|#B+2~%* z)za5rY4kL|f4`McEnMBm$R4xMELNSB)#`HX&G*pIP}vu6-n?n``abB9nPZ(C6{l@f zVZcc0)xu)g)1~;%rh2l;_ik z8f-3!$hvwdNlNYy>xoJY>}cosa1PgwcAr~tQd=`#u)LP4e0tZbcNDdR48tuoygZPv zydE)Cz9zO%cDYnSZUvXRf=ylbzziFlPs@1Z-RGfMC!47u8p}6x7=-T^H(=`Ly}1Ce zb%XKZeo4i_Xt(dYko0}bCuEA4+v;4r@viiubIf?Qe#$l(9CdZcyY8>AuW!Bb?*57P&S&9-aG`3zE zKES!GWo}uNVhBE3G#mZLz0Pcll$@f+Q%S>40VWScm8QCQ_sUS642;6~mqBsBwTC zRS)H;ENuF^goTovW;k2FRNz;^lQUMthe#AQ6}!@mvoT9%5R=?|6?rk}y3hU(y585@+i$^XuUyoMDz+m1uEX?0HkMv82g;SsC-n^MhPTqO#i5 z5e`jNBBXpF4TDXvN=@h`{|wH2YAlqBQ^Ts9`Dm|gcd*2Fkg>#A?(n13;m3pdUQlbf zuycsy7YS4rCa!xncC5^Lc@u2M#HYqU=b?{3d2>1^Szj}Qb(*w?E}j29C(NGEMKx3V zFbYa&{>9aY+RnA%DuM(g`tXw3&9p2o+fz!BAb{E;nre(0DW6hLuSz~&w<&3~J1>V@iz1vGky&gT zLh`V$@%)kM)?6g8@053r9xm+;fA3%wxy4ckJ7f^5b5-d2csX7B!YU|L#`W(ZtyJcw<*bVp0->y{}2sW}OjUh%)%pAFaMzuSjOb6TcaC zxmM4i%JnMQAJ~o$?C?yDxVpUjd;#p&n1P>}pXsql4s}Pk1JxtkVfT@D)AO8^sk;q3 zE5o_rY1UZnH+`++t!ZbADBtTD_{#`r^oIfLv_9AyPnX&DlCSU6mp4z4z%ET>e&;I!I9Qku| z?yD;H7L%0k1mis^ZBMxvy%yK9q6&}^P)i9B5oa9Sx8^%US6BQIUs%%GpeCjD(g<`K&SF+-v zlgmO^icu|?&_d4vr{jeUOp)?KGTPhP3f3}9cn&aL0VP}T1!4 zs=dI&Gf?ThfI*+{&bdXDr(fmChO%wI<|sHtqQK;)P5b(gwiMl%Os zmCy;0=IguP`(#e9Grp6c`MewB>D`CDdzAS6pmcrxBT=QqQj=zmmY^%XM1nQj3?=c? zmc_-qVV14M%*e5?{LQNtiYNUzBq%eph|XZQ7=;J&GZ+G0PTUJ;R02d(jt(ZbqKAGA zA44Xq9LKl8;2E{`Ms4BHxN`Ax#=Lsdp{i=QM52^P(B`7BL2P`y7nwkUS)RJ0g}Qmd zQ30ZeT`1`EA(7CW;@nP}<83+_qc72@Oz*7EJvXODB42gs%xEDK6B)WiM}s{Rb%*mm z*A{X%7t_@NFs3RcFzZ0tF`o7HxN_~Z`A6kiF=O5=jMb7Ji9n1lE4G>1bt@$+G#s7x zpXr}{v6cu5*fdR>@jmkK?GBGx-Qw=A13W{nAM!&N%f9mqeQw0b8YXYQp` z#5t)s(5R7Ji|jY?`qI6--Ra=lKkj!y)TO z!8hAl;YR$5;8187<{c&X1;Ao`%Dli$_y>NEg(44bo!)yY0uM&W-2)=fA}XH+pGy#i ze=4se2ZZXA)G4LJ)Kx8j{32oW?_;NU32I4E-{UIQU$lGoAls2K;Q{!CKjytr{bM^* z_B)s-6gAgIY~piqb8M4LOjXE@{HCR$%azaV7K+{#et4+##p&I{aozq0yk9)X%57{J zVhx_3CC4Xdc+S{d?tBx(x)lGag3V1QH+(1>p2BZtpKm1w?2$heK2!Bgeivy)3Ia>w zn7BB-M*{q52>~DQU}p#hboB4VB_&OsDN`mUpyhM)shL8qF6sQ-BsFrp|IPlz=_~FV z`&UM^)KxS4ynE-bN?EZu!_8<3C|;+*6%>LeYaW^Vk_rgJfShpk>Qxg|BN|z_C2#`# zZ9F}YueoI$!a)Hyxd+a`rt8d73(+G^<753sjkY(mds;=YL5=xL-- z#m;T#Qioi(VhJccOC9imb~?w+bXY2zTn)~Y;xJOmT0=u|E&xV50ytCr_|yz`iezw? zw-3%H^S>h1_Ggx%wx878G$9-?(Z73_W>qO90!~aYkQZGLl(PQr)wyt`Bp#c!H+6HY zcUVYCY1&ntXQiFnoWM{h2G|T2kIsFKXHRgu77l++xb2GqdE}YM^I5Whc9I4_`@ZTF zJEy?%4JK;8v?q7GO#YQTCqPy~`k)QLf&n}UajObQoM2=h@pYAVOWI5wAkiu(cOg*B z`vZ%fa^T?Lc)UI#;@}P#nxTmaTPT49#q;T!9iUOw=4}z9)6?m~>n#B%hWh%N{biJNt%cjluR=L965lmu*&COHA2DLxPN{pM4UX6od zsD>G`m3J$k>KrR5X?h4$p?g|d_ah}Sc~IJ`tE+49c)T`dF*!MDv@a-43)GN#H_>9w z;27;sccLagl1tfgB2rZhW>J&%!z+tBn4J}*IC|18tz+X`!u4NieH~JkgbRmXAC8q^ z6C1I+<3>0kr>Q2NAtZ8Lh>N>~M{0)}>8WAJ{q9$a%YE~Pwcd3luA2XTGhH#($O+0_ z^S+1$0-OiYan)wx74~@_X|STWPzxy9`G&&*Zd3#B@WPyP5htk1$5Bh38pA<#5p$x# z!o#(;hz(Imi!szvYI``NaDx}$gfPkaZ&nevk>A+#p)lv07+v2HvVJ>taPYA+b5-U? zgNa}=GcQp;T!UN0tGtRBemPBHUrx=>0wHiN!hzef!h6rIJ-yGvn$GkCWI?-$8UcX_ zeqxqV_W&f)s03e+tm7ASXEs_?VEp}7WM9hN>33-IjR8f~QgAr*6=yn;KK7-}9p9om3zFwY;UPz^`8ho9!93F?M`&!6Q{%z)Sf z3Q_{$q9%7anI?!K0WeRkhRRCU0OfLe81enHfP$XEA`=|xBU{)A549JsTs!@o-5(z2 zkIC-Y_!VGW8naif9IyC0!Y6!%Pua6mK~Ov-5)Z-iDfbHZDcBpzJ5rjSSf=BI^dd5n z6BFtTT|G;>uQ9~78Nsnh%YQAf`3&I`)j6^%mxU;&Wozs?4_Vy3^* zM@LX(z35K>V@L|>&X&OOcNbx!APfEwk{*5bI;iCX!M{eQ$41{_s%h;8S1-7PfY+yb z@~F#6vTqVUy9IQZs{=5PkW7DM%2aPJWm|^&C4$W>JwrorXz`c$1V6aaXW}A^k0%d2 zGfE!;6}qR%$aKMKmTKob9v=n}*!$A2lKd&I!%cGVe0VyyO^ERc&J#o8(WOh5&dah9 zz=$uBYTdi1=j`mf$<@T}@6R4!H(n(YCQDC1ahta1k#ZaV@eS6gb;_op+Y&Raek&`} zQV4>5Vq#+Y@eOHbYdLqD_WUJQe{j(QrhF-%uY8Nhrf9#Ez802r^4DMxj#Saww?z@( zrvhb@hv^O09M;mS_{Fuf^Pe?{zg=n(8n%^IQ2>JVw+C83O2Gfgqr$x^3`;?atzV4A6Ozi$g>A{uQ2wr#=-*&*&r%8~aVV zWOD9XE`wU5(RgL5&h;yA*#!gu;uUdmv<@{D6$3*h#ty^9hULAz>JG_4^>uYBoWx3= zhAp$R_cQtr>gq)Mr7mY`85ylhY!*g`00b#Nbidly*&DJ~xYMTgAn z1N9j?h?j@Qc5STkF(j0&Oy97dc<`m#?@=QN>?K=f&Ft;j%JF)4@5b&k1@Y^awGI{P znnSh9W`S`h7hr9LZ5D!dxmpR2KXh;!wl6S1ks}#6 zsZkPr*p$6~Y`3(S zS=jLHk|#Qs(+@L+-M>zDFrz6ois}^i#YoyB`u#kb6siz@W70=NmEJk@xILA$j-9Ng zFHyl*i>T^1vnbs5EaK=u`s;_?*4^2owTph2jwU%G(MQNADJ%--oe+fGSdzp($`^nx z;JTeu-F#oy;8c(i@3f2W{ENi)-LX9;AC5>Skc$ND~lY63SHCZKTL+Oc!4E9N%w;bE7M z7*Y%y$7wA-I&zMq@M zTc0@I3IzRc6zS^LL?{10hMXk=cj3FDG@n7h%OG%9@mBZddjXfnZ=}1auMm2U?mi4c z>*-D0zY<*A}ZIV#f1;04&imRG$&_8PS`1qhz+&h+&Ar=$i^pFjpm!fPwC6;+Wj`3_^eVXL$E zfiDyfc8-vNfkEX69QHV~19HxiF$atQj1VvnMcLUFf2a1fwaWfngjM1s8y;sKNA$X(7=S@Q%9H`8Yj&w6T1lYbPS{g8J%PM} zf+}=Kpc0yyeE>K8SH0m5d;0Y0?c=ojF9>bmP`h)H0oK&Aw^tMX;>F%mZ#HnI#95Tn zirIo(FTiy8LymvV4UT$Uf*$=#0w;2N8aPl~ zsY9=V9L|?MbI+PPKBeQ5w?a!G+}XJt=;pP=a;HIdJNp2HeGq&vg@5NgPNZ)EY{?G0 z`T`I3oOW@2y{fCLORkL-nikab3=CyeRaFV>=fL4PVSs^hIvdPF%yLx|lt4_)$JY;L zR^9q>4Hf{BLH(ndpufipP>|nvVt%kt5Ct?fNs8V-BO~`Rc;wXLBoa)%P{Ud-3YaOy z^!zRQhXLZP>R^|coDlO)jT~>%?cLqPzl2F2{Ku}YwPZ?yV3b%ap)$Hqn)?iN>JVP} zPcIbD@c66GNXNb=rmkrLInU0_ETe^nOA?^Lw{PFt0@-K%1QjJd!Rq^y%(4JG!6^Qb z@hcz3{v+d8RSkXl^5s8d{7ami)>?Y>OvcoIMyq6JYWeSFwKb@K3 zuNul$-(Dg#$e+JOC?2Bf!41*{AnV%rRgQ}=hh}V*AyBP|PM!pF_0!kEGK;SeG6SxZ zH8O2&?eV^Bo)K8U^ zQ@l&@+<+w9FvAEZ4i)KJxuK9kt`m6`W}is<1zn@wX3fRY6=~;ZWPI2rEdwWJb%iWe0X8TsC||5W3;axbIbyhw(L<$$U3 zOGZP(M3TbF9r3wAbU*)?RvZ8?iAcJ_3Xta+>FG;B;&rG274+;#Z|`?dpyfDeOU5WE zDXHWvKmFi`Xlj`Cn)^c2ZE>)>HlP`@qKGopHv6q(jWq!SiWQ@<{r0ZVD*8kDlyWg= z1me4dq(wn)xmEY2u5-t{pKKMBTYqpzvpYNY0gzL60f zvymf?fvz<4YV9 z*}U@PK4nwzJ(D9G_VM;&Z_j+>i%F`UeuJ_7MQw>+DVOv7&a)g}J7o}-K$_+8{2F)J z{^?mzg*D_(xT@uGEQk6vU-xX*+OODA403xrn?0)}yF>%v;p zOD|}nmCMai(~fg5CRix!@EbkmfS0ZJ3PCMYL%?X$)wxYfKK#LTh6j5@0o18Sd2(O; z;q3%pzJB$fk!fFkw%RHKM|I{R!rTWqvWe5+uCA_o*W`eSB>)Z}`Y=lQbeJ?H1#zr@ zajm2iif|u|WO0vc{r5k!`?s(W82RiB%JFq(9DmhTl$x5V5`8Wb)T#e4xugRN zT34#X%HJjF&#b>Q6i{~hrH+ff*RNlff;#9|A(h(F-cEcLuxI{&gaafIzU-xou)xc} zbn?T@m(yUxk1$4(7~jwtSlI|E6JLXb1h*x)c_LKxt_H;)(mI^wCaBy=y5ic2sS8`` zTvte{R;kYiun>X>ZrhzIajW>r2=tA4q{O{ai`;2q0`Bw~T7)J6x5}zC%}9l{<{R1+ zCw7Xw7ARi;+Nj5s0!%B4kUEgfXScsUYsPyaWWzWDw`jufg#sXQ;#Lu** z0iDO%d2UpiW$Zr|liNExy88M_f9LXlU>_nPq6S~%dqsLQL8~uM5}2{4`*N=%8*L7+ z3EjA>bW8eTOC+nb*17u}_rRWk58?A$6>6dYV7LG5rmL$_IBZ|wOEWLlDZ z>bow@8v|1o(K0eauC>T749pc7Wy|od3nxn^4yTIpTta_U{1gwt9FsPbxPRt9!KHu? z)b!#<{5Se=PHLnjIVrE!^ZkmBUX|}$vBnVt2UWkN9DtcRU>CH!?VH3bKP3Zbc<+t0 zwzeL8z77M^mh;ZtyA~jM85vN-$>Rl7!0I2bDT$@N0$bhu7tI_1nB{)s1bvN+-=^=D z^MFdSj=1v;P@>Ls^o)$xv5>r|AJEaE$awDDqu+k2)2fa0f5nQv-!xLqywi#z1Zp7s z_qrb3x6a8h5di8VV4iMG7~lMolC+t#$2+FHqp$!Ha852h6xZ%ee+VQ6w)ENKY1|@a z4@9G+W6?CF1>!{|V?l{FNyu>)OaU%qG%IhuU6*24Q1M~wC55`PD{mj*@@TXLF9w~! zYDoJj1z^sCiIWW6_Omh3CjS{wO-rXQ2TVCrTB-@iJ2YCDD~2oG6~m2uVW{e~*0qEH zEQuoEbfn+feF=j{5I}R-+gn`0#$1S)Isz^y2f1tBI+Ca;QntTo0n6CA=El81_;=!e zp>Al=16VNei?u%nbypz;b>6>~Z=c3}y>?+0GEEeM1sri+;N|_*c|yx!fRIt%wpheci+nEpN8mVpe_nGIL#?(TIHe|wjzWJ z&jh-yj+k<)CEwmQ-RzMe*Hu=24k@TfRv@b&D+;trJU;^8kBIAXjIe>Bp&jH90&=Z* zZ(9#!oT&$nZ&z1UDUC*ei~!{MX+)M0I8xB890|gJX1QZGvx}&H3nj3e9^ud)#LiQq6)Y{t4=lsHJ z_6`o{proXvMn*inNDXuIOhCQ%0%m)j3^>z9k&Rw!+e`g`VAe=U1`H1`r3|W@|H_pc z%iPI3E?e`eC09mMKQu~k3`g^*lr;A@y}%mP?0R+VwEFTlwB_3FRM?J|wf<(Afj1}_ z6}aH-P`s11rIx}DAXVDdX6+F05iPEvUC8pYY_y62cLqdM$STDA?zQJwOy5b9?g5GT zj*W4^GEX=4nk|POc-7W7FT72)A5_5-XKW(LS&LOB73>OE79wY z4wFd;ku1sTlPM@YoK{zOzr3Pg4Zx~V?)$K!Ecvt8EkqZVK5WV1QrMgQLGHI24^KDp z0t1)%B3oTqSznZS-aCc;UGoQZsmtof7;2fX`6%ja6#>CXKZXaz0vHUY;dPm&Zj^bu zvd89@K+-#A_E8;y*#h|+pENQVA>w|sI&gG(Y>GB&C2=jkj`T!w%_)N_Uaz<}6I zB}78dv=fVjd^W&<@-rfO72TKi94NDC*PWV#eVXFEy}wjV^^<3BpWsfhayIfsO{A`c zt|^{8e}u6v9@pI5So#vni`-8?l2MS7Ul!sSu3ay7_AxI`duTbKOgxSa`83e~lON~C zh!R~$Az$`$Mfi3r#o6zypRj%ncEnAGMqeS{9!Y4bzhU1mH8A86=XZpda!u=L&dqC9 zo@DHCIYL?}DlQG6Lt8!KBbekgkHQfu&FWm5FWw2LrrBS*b2_D(JECt@AS`F|jrZhp zfs~RZdSQj&LN7Cn(c2$Id_|=}`F>$ylIxLZ^SzJOU+x)Pi~ZPY4FWM$G@2!Vf#aRM*}T#l!JnkN>}l_qWn1ursYEUOPs?F>iXd4ly9r$>dy zy0CAptt=oRC;j?0=MgQp)&&95ausMwz%455>nA1ZJ=f6J3d1Un_kLOO~jMQb1A-O#WLhx zYgdWXr_s?}2MET1V1V^lv?k5FrC34%Rn%Visdz39O0onOf3}<;2Zk9KKCz&X5FthE z%i}n`Pu){6dApb9HCMV(&;zZo^(k%<1(pgJAab{i{Hj5#NzeSN7pxR=0U*$nMC-V! zC>;alGWbMo+fO-n(=2hS{o~(1VKXEK;z*z)gEAd+6sfiYE zI=ZE=_X+I~Af~jY6oFm1B9>Qsw6DymAYnO}Yk6s|m~lwx#@QV+KCUA{p35QOfe>fo zFIvAP*6{TPb3c${hzn-Jcx9Wn9DcmnR0J>l}o%G#UY3xD~Y#c_Kxw|~<%fGQ!RNO%e9>^|tpwyL`$ zez2Ge&`nHk$&8d_s1=X?z71Kzk%8|3Iv@@JCLKU~d}d=SK}5?=Y6XUN zovfC8&0N}Z0iP)0EGYm&f-aGzO9BAH(6RzBZ|L@m3Zn=KYXQER%&5DYBF};2yBts8 z96rH3|J9o}AJpwb^S&CEnVC5>Ir&RYX`!ldUO4j?ECssJ->~$byM$X=S!tCiNm@|? zevg*rPyHxh2w(T+p7kRH_`<*hpzQ7ZS%X-A;@5yt9|v{=z<>b1*6#Ww<1fEfgg!7I z!GR0FJODUe`*NeqHGz@@_Cmm03jA0m+VJgUXz080bSYth>{qW{GyC}QW1yyq5(E%F zx3zgx-j68MRsDWydwZMO=nc03OoXC`x9)$+`4FDgA^@>C<9`lK&)}fNwyJ=ia2(v^ zMgSz7`eHG*y3!xqS)>Fv+t(n~?%_BJn% z%a%YX5{?H0<-t9JZ>VP{(gEiKT@)N*Fp0UYnL1y-lej`tCJE+Ov%kGJLQU;+b9xL# zzz{Qm<%hL7_zz*&Lkgb0qK`77=-!TD~jxW#Ya)oeD zSNTe%E**@J%@O2(j1TwAx}4XwGC|6kK=#AlT5txw+Y3wRCyK#Z{N}t7d&a6;gt89jSM7z>kSyV|(d^AE93>Y!x2J zS_-Jq+T-F&M7jw{mf5NdSz}YBRnPb8-v6 z@5b}#CsdFV1Gow6>+AgqxkO>_iT~nac3 z9apgSKmeTZJWCdEADmVP10Z8l@W$iBl-XEc^0j!FmJv-IH@;=CfIp}oTHAYMsaxBt zeG9~cZBcOX@Lc;su1=MeVbS%Uxd(0H$a%-$8%M5~DjTaK!hh?77p*rGD=9*P zfF-jxFSm9!N-}mnqgC94#(kxzdKGle-2f(_&z z!UB{N_knCz4e5WWt}8oms^I%^LOSB03XPg>MxlU z4+nYpBU4gRkfowUnz;p_RR#GoibL5Jh=o8fyvyG_hR^#Mw7P(vvK~M$_MnT4uQ=$? z>Y*VU!}qsuc{GiDqzoqt_!1=r1!p@m%ibE`Run-eg=&OeWYm{R+tF(_*47(A%1TPM zSj>)*u7bRL!=r2QC12jUg34AM!CK7OsHFP02w300TV$8;8xdz=1>15RA-{Y-D8J)w zsySVFHMJFDyVJUFy!CuKrW!n;>C-)jMkeoSWI!!W1M+( zei~?sGr~p3_l5vXELqt3%l7PRoSPp`2Snk>Y>MWKC}Lk zf@UJW=IPGd80$JI-zxf!{^)qc2*U>pa~)?!mqXV&4yaTP$u8A%9APgWh40T`Jv4)V zDThmO2{sawjb6jsPcsMTks713eo8tK&ao+i9wg_7UZYoStQ*(Jrxeg3@ z+~8lM32oZ^a!v88iG?Y6V!*Fw0dTa5`{BHQB=Sy*YP5CUSZ!0oew*ACjQu!ECzRiS zaaiu}_FB%B>SRu{9$`*f?`Mu@keM+Yi2l(P7nXBRzKye-TR?&u26?D}H!6sY|2|v2 zf2w64l^NhJx+K{(0e@fw!_X{yp&G3Ksh%|K^bIIG<)jVUeagG7RETeq?9MMId^Wy=Ol<7Zhm*Y&stm{2o}4ji3GKeT(Hm09B*gs?@ql z^DV`dF~j}`%(_jIi-YrgINp*5Gv>qTlg?F5>Dt@U2ZT0^slHUPLpuirsOlV!!CNIa zI5)_dC2c()Oaqv2J={iG++TL}EQndh7NVdHgu&n(=*-fIQNFu@nCu z^V4y7iW@*O<-V|Un;T+9F+Shnly7-huM1GpU${`UySw{3CdiD1;z|IocJVV_8Nl$q z{s-6b?|bVR8)JwqjOx|}4Z(?oAsxra{=dFhZs)feaQpBa+kNJD6yKm1 zjvFN)$hsiOMM(*7arwJLt`vkrjFwY3s3f2v!6O8240;n6=>-iy+88`6zd}GFn+}8= zplOk2RZvp}Oq_~|iO~Vx<@I~*i4|2gdESzvnzV@KcupQop_AA0_HF@f9oN@ijNwZ< z$Z0fZ(O5VDwX`}zk zAj?2dPgW69_|?B$@#ti0$t2Kqd<`lzQmPt{tsKK8dhl3Xuo=ET|B!saXLF0-wrXg7p6au0ON-_~0sl+}$TV4Vb0H#l=Iq`=_WZA)d7BnOy_+mk z?0kTmrz{pPi-7A8ZhKz2U`=MT_OgVFbg?oDKrHnuFb{|n0A1R|c1Y0ms}At&^IWR&gLh+I%1=z0@>Uv*1PX|>fWuai%3-}1(0KXhiqDRUVai9hU ztvZ=9^X+#T&qaj5ZES30os@1+hQ$3_m9lu{19M zNiByT8<)ZMQVh(doBZEWgl%}v2lVx6@LrjpyB?@H69arA6d_G`=<6BA!M>+%p4i~n z1IM?5ExSja=}vicmN?*9OG}GCu&R3>xfc!Y*R!U}rM#fwCmKRiwbge4lF-~BZ{;PDf7wzhnKjZb0WH{gWHNc7nZhvyOA zE7YM}K71uYkiqjWGvyskO>>Bu`9fIe!ea5nGDDtlWtQxU8jJX{wG*;!7%WM z393~fYCNWoy+iy>6;7rkQG#Q3P7?Xx9B$wz@Szrww$nS_R`k7DBF&f0oRU~)daZh@ zs?iHg)Ij@TK^lS#T;&mRr^JsAw|4u}a08I`2byhn%%CQ(NQ`lvO9!J-m2JY((rc*6 zjh@I(iRlUzl`}h?>+!g(IQtZWiyDEmv;@g zb1Fc~+E{ygh_F4-M3H2ZU0rI&{cuJzia@0iE}V;vzi!p#e%#b%H-4jE%^8?HkNe;> zn}CV6;X5D+My97tGNK28!Au7*!zmJ8LrKM)R2ST*@x>mmzK)*)iBI#z)X1n*J*0@5`ShHw@jG~$Np$^} zj4aaEll5+17I$&6kAa-T#Ke@NX1;?FJNM9VK=bCx5?Db-jlXUFoOU*K0idsCdd+48 zGFQeMfL81eUfy5sAw(}=7S1Ir%%a8F^nW$3Ar=~dMQ5%-9I-G5{H3&kE4hIK(Z%)K z4?BRP9jGuaDfKTs!&a~%5nD$kcipw45BVR$*oBclyF9jhrb5=Zu^ZM69-S|wxF=8a zG&XxrPJssoi1Kfj)c$Z}t|}?JNP#4Ir zoD3lv=8KJ~m<8doJpsX0Gn7X$YGV z!}MXfJ9`2y>RH~4Ih!LR+vf8;1x*FsN@YESLG$XH=6T;0tyu*$!fA|~Z>ruHDrJ~1 zHfmg+QwTRzf(E+7flqakR%2!CNKCERrYe`GgjL&b>Xrin*iP+q$QmCK!e=h~1SR7cMf;jb?58&7>Z@L{jYU6RipIPgNL zo*C~(sU4XZ`00uj#El*Gjl@(#CBNdZ0p@*^#`*P+z6V1V^&*l!{P!#{Q_Djm%Y7Uo z&EzX}sakQ+16+_;luYm$>z_WM^4aKDSJ?_-b-gpymcc@ivo?BgGJ}V*{i+Vf_1v8k z)eKpK{=b-G4F0pJ_VpXrdMUVU@h4Y@MBezmgr403z&z(bWYR#XtTf9oOrivvcEe@q zNU`v7$NZ7K1#gaDay0$9_xzbDR<>ppQ|@@^8uXE_(G}XcV}jjz z`cWlKQ;Bg}Z5i&6)zw!eC0{+XJW@yNdLW#|0Ot8tsaY00iNT}hn!G(lNR7Y!whB`q!=1pqdV#%$ahrrTbIV)x9tv z5`d*qo5 z<6r$AP3!+C75^LPfr>IXlr+;D2o%z<5(2*6a`_~@-|k^yk%E>Gc#R8yatL6Ryo*E^;hjExdH|ed z6YA70XjJO4Q{m%&=y~Kg<3HfzU#Do7!_wN?=`V3a{8bmCr!l}QpNV+(j`F`l=-;p! z6^ekS&pVz^q!({ump^i~vdTayt9~NIQ^Az4J@?KZU!e@6@0+fofKSt8_&i!tn4xaNI22-Mg(?@x&PZg_6$`E%l2>DL`g34p>uO{5uBIm z<>-YG_4W0`S1TOyg!=qYrua}ZpVLOOb0b8^ph*6^)+{bsvOm5pZmO%_UdL$~K>!gB z04c~);%!%IB(YQ9Q&bxccsR({$N*2SG9?kkf~X=aF)B|Rd7H;>WwbgbI&_>Nln!`s zh>*+tMKJ*Aa-ZNDK55eiMj}CeV_f} zfUf(h&@q2i$9k^m=I!2y*1lNoG?mkhI|T3)XBoBxpc0UONn?vTnl*581_w@oiujhY zG6A9Tr%cmRFICZC46MDc+j5UEXQ}8^Jh=R>vh@%%{ZGy*6NtdJ$8rBIQy!IE_T6~7t@vW6a0Izy+J z|A#F7u`UWaqu*c^`z`drXmeatboKO}!Vz3KGrPFBe!Cq=E<=aa?1cNujrWVL+)sF1PYH+^iP8sdIHA*=8=3PA)fJ;`teGs+9 zR?&DskX`xQgHS^Pxk``0$mQkbZn^^Lvh>3U_rfi3evTU|Dm{T4$7ZM~EJZNy7IK^$ zRCZn(b)_NfhTi|OvfVWHg-+aWiK!U&f#SvpOYo>EZJ0w~y0MX{2_%$AU87k!Y%7G0w-oTJ=R$$<=y-)SD098! zo9f?&i89-{dGGcAT;c1WcgTMGth_U6pr7xsR2x&KS}OZ0r63wC==z^-%k|{m@ForP zhs-t&=GAMgq=9k&6pkps_fe6NMaN!&oR$_PODii`xIhRJ-8}OfSZesOn5(w>=Bp@a zV6pFoC|4KnHqO}bDY+z&!oxiq8|{>*jvVL70nW0BB5ai*lmf_r*%dL~^^D8Y2nYw@ z;CSZ74qpCZ3h4XdH)amy#Q!e^9{)Ydo)8-*Z$Dj&`;Y%|w^lW#lRwhD!huTf6Z|T>-?Ub64y} zQ9;TIog__(Rq%kX7TWg(!7qTam+r4$$Z?!g|1Ghl0O~3m_?x!p7^0ZqDyJMgl9=`* z@a1)-p^-#rF`(_A{d-^BolpN~U))$>2{nQ-@ZM7T)ID~f?C8L38(^~ z&vlM)Amdrl|34{w&y+!%_16wLl;jdl*RK7aryq9ic8R@=sd4kz3lH}8_Q-?t=UcV2 z6~D@LaaJF$5FhbTq~E6b>meYy4R@rpfyiA#bn2-L{X(9PdTb+j{buXp$ztT6QAB04f0avCw{Y%ldgnBQNvfN=EtqivU3LoN zbp%BczbS`tWr9Q6^c4rtCTD z8U{#vCOhco4rK)9)891t8-`%ms#6FrFE2>HIQKXsTmi|*X7_8nzG*7>mak}AU>hVcf!U6kKCQ#nLQx+ha zjp{L6x82hJd$Z@_Uq5|guqoEcq3D&+H^~Rd8pQoCjj{@>wd%EV?Q4|!DT?)_UMjM* zwzb4LI6S_ReX`|gRsykbpa(HRGK!!kwN-Y_spwUZ%y{ax7m7+1&%m(0o|RK9<@^5oIezOl%#gasQ%t1 zK^SvilVfo=WgQR62p))qu!p}ZtkQCw%v)1`b!c=ey~WA!>Wq^14;l4+K69~6QEw-b z_iav=uT`@UQ|NWK7w@;>OK?hIj#)PcWoGuC+qlBBx;2j;dIjh1TUfM+g|UZ`jb=!w z_sW@*1_pqULF+QV5)Wx$3<_*bAWN6$>c2~YzK_>oLKW=J82x8gf*NC&!T8#>YoY1% zLI@nroZI!!{J1@aPN^g+Jxo_?c?4-2fET1Hc^Oi1b4esI);13aewfhVQ}T!J4ZAO7 zl1%z>{nH7U)JRHCZ{;z%?(SnLAy8XF8W>yk-L{e@kL;|?!~2@2)P7*}y0XO#oPUnt zzcmu7#G#0AX=mgefi~SwT*+;;*POC(uJDLww~^0*(^c1%(Xusr5KkOuC1Qep4NINB zEVRonr+H%XsmGqs6*SWM41t7WhQ_4;2jZq7FL{0c)Jl=7_ko^=Cl0VNnb5$iz9x4x7CST+0^rt@0?0Qn#@3zaibe(b9}IzvNZjoSI{ALbT#oQ2-QGasB804!^(U>55&H~Oti4{=#pKOE9Aw32em0AD z=Wt)2Io-jmLV3!Y>C%Lr9f+0g@#Ld6!WQRt0R*>V9vaU;qK4@bU41w%E9;<&;jXKA zeRf*y?u8EhgV~Whb~9gTt~yL#A6+oT!8`L7V>eX`tPe4Zg$xUY-rfDR_V1YBl*Siy zsu`U=f8xFH;Ekz{mzmvQ_e`0QBtoG^HjyL3b&%9Ed{$C|&A@n4@bd znjrV#ug4N~hP1u5drAiGTD-#^l&|Z@KvND=AZK^;Lr`YH!J< z@>SE0hN^hfcEZwgBXmki^1kC_6X^m}Ex0rLn6!A2M{SfBus0T*CK`e}CZ7b+lTbjU zH+CriAA@HyBA^w(s6nNp==dM~xdikeS=+ety{jvH?`)nt@)T+mN z_TPmS8U!G8-{B#M&v+h1fAc;9vL?7bGd32KL12CLM{!WpF!qsBhLhtXt1I;{;(i%93 zbTgA61aP3IY#w5AF&FMrxtw`mnd6%Bz4Ila!AJY-d>%+8+bI(;`O8V3?maQB}OD&Sj7jGX~f7-YZPV7N(w5u>sP z+MknV5U4m@Vw2o?N+a%mdG+MH?W~LUj0>Z2$RvVU7!RC2Y^p8W zz6l<+aN+6Wt?=*l`k$2>K0}+}@C$JquOtwk>vPKar%2%8dbeuhN40C&XSL$`Hi&+} zCHfAJ!SyMf!E%p6%q|7Fh~;A-_k*5%YX0ZXuC?jfnYqy2!0fB#rsCH=1bo5hPt|OR z#n(fD6aKb-`9=7y!g*@y^>GW=FL8n&(X(7CYtBH?2ONA%J2c<8skqCPk* z1Q2b7SzAE0#w!oN;oirJf_-_VSQke}=G9N|V9&GHZ8$FUasT75+59nReBZdqo7Z5k zeA0b<`^@l!MuB_R!*N;L4*$kgB0p<7Y3S0dO?r$Z; z3ooZ7E6I8-f(5%2=VRxVcEr8pyL%P1%587B==7$$v*;dNC=n@-9~!Le7ZdB(y!CeQ zXm~KwYLmaW_cSk_#<`bf4O>f zeXE9mu5Lm3_h7hl`S6x|^M_Nv;VM`~8k$P0qg>XsSe&+Y{i1Kz29WB&`WgTKKi2bmuC0vDmVk-S<3BIiRcTNVG1HuakV_{{L zKYwRsR8EY9@?Or@7eXY}vwn$^qW2jBPrOzvI_alU9T|D%Y~RDGfk$hmihkRT=mk@& z&VUOvxE##8+O>~xcHny}{vo%m_g7z>2#|??q!f3q4viZv#5aDWU_(^i1pRqqk^ksQ z6;Vmdgl|?;L`ZGorS%#K>!z-UC>`r}Jwcy{cWYAX>~n2J<~*t^Gu$MXM4At%to8g*@Gv;pKtvS5diV#?4>ycB|?pK^5yjHTyfuCp*wyyKN zZ>Yyf-mve`$)c|{GdzBi)uDHBfV~Se}>-q5=s(unwvtt2UK)EDeKD<+*rd@GW zj!d##ZH}VZJGtUzg#O-Eh*ENy*09hh_G2Ly5yO|}gu%K;3BB;rjNUFJ!aZ9?ToODp z`h)$El?;M9C+k_R744a3^02MXNkA#~G25;F+1Za}`;huLsmk0omT)1e;|=j6zn8@h zGcC~XcH$cq7G=29E!!F_JbW>`Z1owD^Bzr_=%UOTdGlyV-3!b z(PJ;*RTX)z&#J`=TE27&<7b60$&G?CBLZ5s5hw9OedUlABNkD4%7f)h%*JzJWx|iv za!u6EQ4-=8g=2|^><24hvfMbG-m7o2+{_;{O5=6yZ8DkP$7OMA3zi>fri$6n(bQz# z+1XLiu5Bvq|ISgL&U)UIGRodY+~kx)w+hl(JGy1zeM&Ib4dv#@^t>nMO;c>4z*jlP zeQVNDFlb-W*j4wd&}ZID4*E;F&C2t()Gn(HVD{>*^FztMPqN&A&I7nD2Wgs0mn$sW z5T87ZJmMtV6fZqb(7HX`|E-`vcm58gbtm~#%@Xlurgj!=c$5F@1CIus6s!bs(~>~LG(CS_HlG-KU;dHda;rqKxr$=d zA=1vcI_l(Oq+*pj!tR?A!@vIIYy#?Fth~$dqnD6H{f@Dc*0{hA<5^#U(P7CSTOwO% zx894mcg0ZWK22X!cJt8NVdYR$q`!?En0`j--bzk1n0nW(B`BvKX?Nl_2hY2rqARJc zqlaI)${#A9gG;?LT_AwB-gAHMbY_e)S)dqp+l8N252u@Ef<)-CXUJnN-3eHWN;=7m zLj3hXauPATd+V1_0b5U(E;DympK5PKf3bc}>F3OMKMiPN9LRE*(Ciu?1}^EhY4GCJ zt&d+YA~QvpKOz|#fqmBHRWfWTPFEVK6`q_hM7H(&3;f{8t&s^TMjgYnBCV_E1cO`iOpChzyxgDI@u3ypQ(Yf+4JpTSEjOxuB8^}+;l>`; z?_0fV#caY_we>RgWZ^Ymq~gCns%{~y9Q0ZR!aw0S`fLT;ddjRkd<$yME0?}Y&3yCu zIGP~takulWoS{*r$Ck~6tG9Q>N9wv)hQ(+*VwrPoXSR zeRFVw_3VJo9lz=?eB>stINPdZVhkV`2Cw@xrQlGJ^|xF(*noNP@$vaRx9!Zhs)gRa zt^y~(;E9S%*zy&Y%GSOT?d_i!{aAnGUS3&hd$dp!wKVP+ZYzX%}?95HWtdwQnvDaL>cPRq*Do;gi@<>r?Uo$^gRJ(|~4 zRii;#nm)1pgYZ1R>|?Oj_DbxtDl02b0p{fh%72s-$h0YCC*CI}0@mD9Y^1 zY>wy}9Q?Q$OnFH<^D&MW{iBnF>TX<|oEoMXhEehHT#p|==7^7sd>7;kreZ$xqabP# z_0g`JtSpt+k1pqA_J2hXyvyt#miAPfDOw)(0}p!Y9zi2ShxSU$o+_`x3y+OuhtU%p zH2!aNw6$M$;fz6|$ax{Fn6CU!pT5M}8yO{x6R;{KfA|nEb$7zvk5dqDnayKFb=tnh z>&YI~7Js70ru$NCEB;xtip0eo@A;4MJmu@^6XMP8vUe9q4Wq~xJT}voope;>~}>t{+rq{h94{S&Nj2(i^bX53bzz4 zyWU}D{q*BXXG{#w`;BY!vCHrF((>$|1!qT2zOxWnSm1c5P}zwycaMWJ+g0;*|J>AM z-(mUm`rYIuf5P>`HzPdi2OipzTcG|i|6DxOLHvBmv`t-Cb5{Qax5~ysKptJ+ynRg& z|N4U@Tai%zwp9PR%AGH^r#Qsy@&kxPldh6G?)ofyj6`5+;aTSXnKyZB>Ba{i_n}r+ ze?8z{sb6uBakm?F{?*#=^>2BMkA96yMjI+FRKrKtig(|O8JI8a8<=Y*UzU6VMY{y;AuhFK0LMrj{5qYoP#AkWM4{5F8nf6^d{aD_2uJEL8*Dh zMn474Z5#M81>@`&xVwEbPA@~?2L=E9plEk*8S(e&oIc|jY~&4!^=mR z?F(EqUe}H9NGRC8YJYe~BzEKSg2k}y^doB;A)kexU&rwrgC%rQ&RS8wew{rbO>M)) zCLoiS758{JrFMEvQ0G5R{_FS2FXk-UW@fF|L`vdUu5U$~jCVYovt1J$AUe~xcXh9C zZ-1iBds2sP>!@$1(cA0vo=H;u=$-X@_b<^2CuVQG+8T??s+qQ2FaGg9xxKsJ*|}ne z+EC?iXLhj!^>F^+@S)>bA>-t=% zB|a3Eo?38N$&{N(&a>xleBa(!c;tl4!B;ZM>s*$?oPhS?TtTj^49UwBY>MngrG>@}YC3_d(D%ng*4TlU%LBKO7_C ztnUmjS02o%`rJC)pONHLPCUBA_QbXvlAa*;>P=0YpyebO+JtJ*LbRSMQajbe-X_$C z^}OB;BfEWtK{;*{-$xD$_Q56N?Z@8k^1mq6}Z<|jHy6qoM7Q&rEJfhUCN z-9Jg(gwbGXZG{$YsmSvuKT%i$c_w&{UmPD^MVZkmRw-aDw}FYJB||T-=@9^m+lbUs z9;32>aozJY__Dm{!NOZpi{a-^pjM-3n>_^X-nkI$SR{q8Ve?g6fT2XbZ&=HeF$#S7 z7%FBGB7_#zQq-=WPVhVR8Oe*ycSMP<3lWK0ojA$TcB;CI)P^Lx{lM_o$7`%wV&M)8 z*cvgfoEdZSN;HjltzUf$_WX^~t|q=SP4UEi6%D+jQ>njl&cknGWitfCt=|HKR5hF0 zt&8ul@TOJLX@1%Jsamb2p5TR#GkynPsHVvF0FwiDyFw_to!5|{aI>tH=a@@!DfMsy|2h+J(o zK2-l1pr4Q7#g5LLl~;JCXJWbDP8Fj6I!ok+O#~Kx=>Pl}h(9Nrz!T6!gT?>=w? z1Z=iC+1%p7*|)Ou>xEASG1Z9f$Vr}`eChZLgr@7F<^iUgT_73g6&21d-;5L6Tg+=w z#GwoH*{-F#+mos$Pdy#nnXK^Yk%h-(Qy9h1JkcM|$wrBENSeNU5$p@Ke=M!(hqRK$ zTDW*4hOe5n*i=Zd$`G779fniatp7nZNy5k+ZJ{!5HTY5no3CKh#U*-@@K`0?<+1g- z%`|s{b6la|zc!Rzgozm?_eWeR_YB#&xXu){@4}D*Q7$|!ZEaS_S=*HBzhc%1qlYG+ zvL%E{9{zf<>T|T^LvcRxGc67dPRqc_M;!v~V#zMX>x9X~9|(<{znRVJg45A+=IRJ( zhL>ZnmQI@9H9nzgoNvHhPn9fWm0lz&pHMd54X+YR*d%{y-Nv|ZJ>z;|-ga^O4l-v+ z+1h$OJ*;-l8w^?JKN715j^u5~2qqP=RmG2!_}+$HV;{BFFvyscNZh>;ThxF1`S<^$wW;(-ZyG!Yjl++cFcEDGlN|bS3c@ z&Q;HRI${$N(*Cko)tdh1jg(i;kJoq2%}*Kh!$?TkVb=*lx3w?AQEE1zEatymhgW?| zGp5-8+igR9bAHoImNp`wNwoT%ohr2EoTE;@5Q)YLirqdKcaa91pA&*LOibx|$#unK zfzj+bpzLQ=za|>r=jjdB$@~lKl87VuQ0ETfL_|b7lP?Rhp!rBql>%kfA8QUNJKW~3{IQ$b(ia|<;4R61ibAl`T7voPJ7;O1`6R_U9 z+KRVt-x8UggbT2!U{d9aZJ^QYU0*|8?2A%Y51*WxDn^liL?JerI==0OZ?>{H$o~8N zDIs)~sTX85j&Da)J4v5Fux1d$B@VND_gp$~vzGR!Kny;gLm+@DUyO4K!u3FJ)h2~` zXa_=3($ao_=BcKG^cb=4k^81=@?2P0zGB6*1kgm6qCy8QVEZ0=M)#~!O)K$5$$^PHYsnNB%R#@ zyc5sA3V)TFLI!AO7~)hvv|6~5gqzjfFkn|d){eV_rSRp-O$pIEp3`mTonAY8rA@5N z)Y{Lx>FMjKXpazVi*Mxvjp20A_7O0*<*X^m$>o#^$*XRXawB_b=`*wj0=&98STFed znB3JU#t!QCnk$Jqw4<$UN8VJVFVr1YQuMW6Eg;_CS$}Ud-0Se-XG%qWHKUfweVHAC zCCT*GxQ*4Ug{g}tj%JK5Zz#+jo<7jGvbMgI@T+{TJFw@#-|=AYGBmSrjKWz};#2!% z3g2chxk&=CxS=zbTO&N&sKU!uxVb!5c>7#-_M!h?CYzVPb4lWU>jPi`?VDMmFP`=I zo935uy=~aEYA2Ly z;iEFyU7yQ^Q8Bf;g7>~si!A9kmA-o?Yxqd%q)o1$AL49)loLGZxiDt$Y=c*5Vj?ee z$%KOBfp?C8$BNy{t_!LeoB-`Qg6a}&JGv*9^mB7_^VD@upPUW48hB#ixt6A8szaze z^jA%)uj1*_TTjx2hlMeavtH+Ol$pXVp_cH}h3RVhry?kwSi}GM~cK0vJD@aT^$qvsByX-V-@{?%c=Rse`-Ad(8$at_foj^1{3L1fp!Q(|i7!$cCN#K$z(Yj!d z9LD|Tw;+*;cRT6l)HCnoRsQqIbmuB;W4KlpUED>A^L=>D7wt42Jl&-MRjq7>Pj>cJ zgpWuyW%dHC&Fqu%n;zA|`bl0SXLlu^$K9$OCbo1Ec~eO}JkhayMp3LHLNpF_;`O)M z1E^$E_*v&#`QlDS(K$30N(87l>^F@f+KoHXDuFP+vjutV(7W3KDAOr}72FQ}7x@=8 zM17x&igbe`lj&~bQZVM(C?oh#$+=iE^cec(7J{;^GnUib5=MkF!g&9^TlxCuuvZV*W9^+b`Ir{h z2~Qg&UT)s^a4e8-r=WeAN0=W#RyY+BRmSX6w5<^}^ zI;))@v)j0{KHeD>-x}&plY*$48#JetVzB+x*f9K^2YFSA1=Q8KF08Rv$?WK{+mfio z-9Mjj_{tUGdw_4!#|x+6Z`y;t1pnRp_i1`!oi{L>=Y71Y3G#Xxq_nxF->;cZjkFpP zbEVQwFeRxK22+g8v5y>X7G7Pfocr)9k~5R&>UrSJa%iwAU*s#JzMF-dXT-)Oud*^0 z*BYv};9FoeQAR9R9#wF!)n5<$*QVqD0_)|~4rP3QUab$Zh)yXJX!mr!xqs&Vc~}+( zybczOqf-*SZ)K8Bw3AtuRLKnkwCGM7o6GMvih!m2LbRzDqV4|M{|)1Qf7d?|)%5xn zF9gA_RJT0_XquK!KN{@_8XbPe*$&?e#=wBDZhp7r+;UlYC}}momya^4B5;NCQzeAF zef#zwHYZ1d;iTTp<88|qTNQJj|9qflS$%~l_5|MU38Q|>DEL}ub$lXpX*0DHuf6C9 zhygd#*uvtYf%neTJo&nuSQxzWIb;bx<#)zMZAknpky!g4s)-Um-W1;}xK=fcfjoRR7`R~LgGhnDS=zmPdtC? z58*#A-wS~EDlMM`KnP;{rO!z+v7+y@bPNq~@p1hIlNb#q91Fakzi*~0HDt#$IO`lV z6Jqtq>`uW%QHzwjQ9FwPzSj3RKsVB|)-K5#^9%KF>hqs7`L82{F9vd9D+en1y26eqeDJea> z9(TEUQ1Lt!kaCQCu+oSB`ws@%BTk$=9SVk6c@dnCs)#oXe0LgxI%L~)YgMY$w_HG` z|1#U_r=`+UNb}b#vU_6yz&6+Q*X?R)10dd{I9=fix3>EnI$*%z6u6BaV-PR??yZDb z9t0p`3oqItO~~_CZ+V}R^1N|^bi3s7=+?`)IF6b2M4e-H>WW+;mF)go9)sGl%U>`GN39zYsi8sE5#kzi#^3?-W9N)}<2ZXlUkS5|&kR~M2xjEI?? z{aTe_J8jQH`ozIr8dpfA;V|!TfuMCRHmYdymQyu*&BI#}#qoBPK*Zh>*V!J*>vw23 zQ1O)fwsp{>B{F6hyv63f5!lqnRKmg5x3#D-vXB+u(sJ|3b&ahvhk`r9rgC!DPIvzy z({dH|(xA~&Qd*gGBmIZdr`J>^&Niwo%|8!F2?=R%!-X&RC{HyW2!<`an6(snBZ+%J zKP&UF{y@{1ZTdV8WxfP?vuDj$;GqU! zw&?=jX-dA?S-#VkQtRH>TMNaFvUUet6f3ifn6*Ta1(4G;*pkST#oG#9ub#av#5fjH zanenCP&8;nJV-E5hU$X}NsWvaRoDvSlZT4Nk35>J+R#RP7siU923W|)#v>%4^mnA@ zyZA;!CSPkbRB7=>Wq>ieonPm$wDoy2;FzhRzi7k+qFY)<@k>w4~qMWp$?Nb`!kjJ$^)K z+_;fn7aOEBEh;eIm=Rq_ICRRXysXr|t8r(@;j2ToKr-1f+pubC)u9=Z~|M_yd#I=J` zBA25CmBK;{kN^mD;UM{D&)h-IMY_D+f{!gaM;i@+-I%L(@#-uo219UdaJv8`Z2}hM z#QS@Tjjc9=pC7bTA(_Y}U@(qw$U-pq@0#>&%UQr}90h*I_4@@8A&*9u==oq6Qj)j+ zQb*snTac8i{>A8mfO$~fS<|I!Rp!W!DiQbf)V4UmR&$X%&Wcq#NEiSvuDAHgP!+T6 z1fYKEXWBbo`)=h^7vuR}VRiVji`18qCTi>;~rS!qz z_0NPU7I>2>R#*XkL?f;hD>?Dj_ty|eEt^&vPPc0ZG+A2un;?@&W7PtA=35OfuIyf3 z!;;IXAwEGl!=LUv+1=mmuhfpM73H#vc%pZGJ3++ZgMA`yau)~~ra)w5YL?c0ds2lg zhXT7zw@V;zF-l-M02zM+E03<&C^oM(w78HL4Mf5>VTU$7O_KYfQ&BI4RKI3*6#lx| z`00737UPEg)g_1Aw+XIc{!bYb{6lOk;xi(s)twCL zN|G=Tc{0IcDGWj)nxWC%%f(d0(+v}K$9>uDKxAgzlajXvYF^XgiIA(2t(jj#9Oz(E z5XY386JYu`8_3bkC}~6(<4t#;=aEMl)(2k{rx<$Xgsj$E=9CBf zLm7g-T~*(M^T+isT1S6gCWKFU>=|cPDBa=86HH5HoL%ah^xp)AX7 zgWI1BS>HT`c*LaoK!R1hJlyQwOwFSOlV<|>Ig9anHa1OyeF+i|$Ni}^-w-jITs;oQ zF-(Yg12WmCYR!_IFjadML}l`adzDA}r%s)keBra7e2c01sLZ4$W<&P>?Bl{DD}CFa z?fQ|l!e^8_L_cdHy*O0=4L#_wbx3lC4h&s1`GVIS68GYV>|42WA=hE2S=E~nWG~_9 z6S$|&dDooj#w@fF&~AV*-^z<6K$jSQesLAR#-bdSMBt-`ZV$2z1caGQd=Rb*=6CN7 zNG7*2;9m*FntywSEC&bMn1K6X$=Z*!e4`bmwQHo=w%mqAwj1wUnZ1kFHY|8nGrgP| z&Q+cG_@HHW6exaMd=6)PI3~JYc&{_L!7R}p z(d!UmmkXWFZ$%H2!LCbS^M>X=kuZK-#zWBclLfPBfCk{2qpXd3g zj1Xt!Gp+Hbecc{$jb0OZ`()th?_{6MT zgIHM(7K}3g5@*)W#c#|!s*8wWsRU@$IGuQ&4ZD2IGQhf@I zZ?aiZ>^9W9JwIB8iDqV!GuTDK)g?;D5(s=|W@cSF_B(Lrk__qFJZ!HYXU4utYVxX8 z<$ZNo(NnBpJsZ1-xexqrc$OHdgrYrK_{W!)_thZfpk*Puk_U8q8kU^ZIqTD}Z0*Cu zd~Tk`w72gc4GVo`gzk0-v#-k}%wnX8buo`aE~=P})jW1?XIxW&@1VA%Lb!y?2eFaz zA=Goe2K>*NN5v4av9W%{zm)?kH{~DVVk_U{a4R?3f$7&Q#=Bk7$Lk&t-kRqFKo##U z%LzCQ#62uhESU1pekw{w3G`bp#z&iBnFwRTI;w@JfM*6LWBBolBC&=aLnxL@nzQC} zB%`tkFdSiDur%1Pqsgi~o?DAVpX2F$b`kq?VaF1tL? zJz_SHuIh?a0=In>#%xXT=9jA&zs=BKYvz5Mdc-E6qlgM4>T*kjWw%GTNn z606I4dV9~y$1unQq{h0HOkCmSzF|kyM~$I;mt^1(V1cF-e;Ug5iQ%STB5_(~rq*6n8597Q?u3 z`QF5~qeTN=;)3|0-Jx!4DMO=ent7qP*Mq9%>};Pc$377Jye$0Lp>tT6papl?9FX)O zVM(Us+uU3wvKu%s<%GdQaw+?%qmQiZ5}Shv%zwOf-s;z(yzA%~V?^!-plC)PVZY*zXc(3xZtv{X@il*8%?X61N-W0hCanGSO=GpW8 zg!d#fdZ*s)wRSerVDQ|E*eBVJxnQC-6C_QSYnlZU`G?9IahK;9eZGU1KQAud|4O3g z>b2svuU8*EO_(VP#1d8Q40EqPtAlqsn#4M^hu3>Q!33)_nf{kafErGJO%b-#1Pm#a zI*%q<;v(AKtP>=m!5!}@#2-h+4Wf|A_~Qy?BnB_aX0LR78pt-5jN+xi9nz6X%o_LY zRG_kOq;`FJUyAz5a97t`X9hy61y5g`)fbNRfy8bf%<-hl=U3^A-3)5kW zHn7Kk)}DGIX5?Xpf`2wGsr8nIWt!rz)Xi$=gzMRp)V3tQuj^@44c5gTg&#M0ZC%CG zl$Nb6jYr)sje1<_T7E)UkQt4uuTt`JqF`ot+2_bZ(2mFq2d#6K(sZ%Ud;W$9+~o#8 zbnb(~@b&W$PuGl2KdEFGIA5rBwNQ9!5-#5o#ZiTSZvM#2=?I_hdX{DfZw~%#i?d4^ zybSRNq+Z-?r$5(op-zV^XJjsZ_D-IjW=%W#ZjhWID;8d6l{YW*{BEV1aceVSyZD)B zK3>a;mgQx7-hCId_q|U{erziX+*sB#RKXNU7bb~tI#b3z1Gi1*+`EGi=^yH2JN??` zIu&E_uWP2uPx4FRlUgzDrriz**+h@Z-A+@(=T)W*;8RZ*_M^g=XQzxYNrQ2xIby%+ z$mQ!1#13uSF?^=dwr^(yiP(Hc0umlf*l8IgdQnE2$kr10VLAjf*k$+B^iVRvnE!BN zpRPka!F`OZxsmj~PT0ZQKVgO)LrU{Q=4~+K_A;|WlQ6{Dm?E`Ig9%!lL~o)eC)j}f!MvORM}5#=#PY3N?z6J z^3%V2t~QrJ1B6PsBr&__{K)k;g?R1ki^(DPsx#9cUgIrh@I>`1t5WI3y$om9xkoHe zqcT!8s%)L%RKnAAwD$PPl$Fe@EmnWq--!RyoU0x%h^wJzn^NyS_(DtgcyGC~vcH3G zbqu5Bg8{FyHpNrt^vYa6nckFsw>@?M8vO;Q{W73~=*5tBymvlc`AwD1#$X0{<%jVL z0YaAZwZvY1f&!kzckA4+@o&(aRBo0ae%pF6C>Z%R8tdeRIWzLN(Fj2{bO(v=Rk3Hl zeM`KXD)?>_fg$yVeU7{YZ=Z~!4mw>U$`xaVpm5vnv#eY{GXZs2Z@03m1mOA@0OE~J zA{kC{n0hJ%Ye@O_k6hE*Ap!MfQPH5M$X$6XLh7Fd`L>sSS-?>VV3!r<$X~pNh)c73 zh1^xal4of{)VcQ@@8x(VsH=UQ4eq{Y$O12;*MgmL-;5i)%j=w3Z;iX~?!)_Q92Yq6 zqB_-@1C=9tuFSNzWp5To2WfvAN=1gMif=@=#fxa{czD8CnMr}fouDeZr4-472ag2K zg?61}^n_6%;0IQ+U@E7DHJmsx3d1Dv4~4r2q9C&6aB3Mje5^WvxwhaBJKgGQhI zCuQI~#o*`apbros)C4XZjpD=I1v8+Fu(P)$M>pK3>M4vh>5lH4h6bO12-fM6?PHFW z#doiTo8Yy+a=@oB)Cm5{@cdPo1{C5oX;pc>yXn2VMK9nM0H(F#*F-NfNg&pD19X#} z&lNn^+AzFsJbd)$0oUdb)l=7ooP#bi3q&Pdq^$wlOZ;`f4A8pvNsU!5$4(-=!6)>f zQy}j1_p&8}8S%}5J5Y)g&bIE{03Pu+Lbr#^lEc*h7tb}9mlGKtR#a|15u8tMN58>8`5wlbUWmMW#ybK< ziDwc3ekKP>_1k33B!)=7D+tS%ze(WGSFbF8lfbC*!Yf5DB#~q}2`J#Z>*^nuDL3v0 z3CNS>h@k80WZeUD+|FO}z`#$o(fJE_r*DAp#d?xm0GFA>3^_WCw2V7`NtV#aNM@OU z)1UspOK6HcV}>G08U?qgv*!~muH+zh3?Meh=9>3`q2i|y2YXu-pGz?nFnpl#k&f$6 z1l2~5ZuuY-Oe>jId_JxPf5QNK^a`2pLNjJ=x3Jm=d4w7&>ElNw=jCAmaJ z0dfs0I=156TR;(Ho%uNSn0=XTP>Uy239`sIg+GQ&0eDy#W#_f25c;wvf2}el)i2P@pB9+=*|d~ zBiX12r)cZzTTc?Iz>g-Ah^Il1zQ`O3jmzUl6D|Y-W8dbZqWy%A)=JlI20JD<#F^%T zCLt=_v^F5ElL>rzI;LFer(_ynw2Iah1_mim>~cYk5e zVb~snqMC(`O#!maQfhQ#9s&&K_zD<3-xhR;_Q0b=Q7l%y9lqkcyZ*q*6FRfuVBQ0y zcykPB^5$(=2rgXY=H|8-fvhx~ove%u%`xZ?D;gTUju-dPehjxS3xIYa8qs$(?Ck9P z&WpO=pgsjTLB4H6(0O|hyjSmbRMqd&{FMtJmRiV zd_Hxz-JXMoM|-N_FNz%8q`l%#?$Q1wIepqLzE_b5q3k0FFT@JVY|-p#`eqHRNZ0u$~p$QcTH)AFYb-8xCa@Jvl~m!SABNh zti9DkY^f#k#&_L;*sIj={D63c%6x&5MY8U9ArK_1?EJL#v0RjEENbjF+HSz6vPnv& zrWp)1Ykk=~-oQ$%hRL{AX zbI45}BrgZEd`dp#T`^JYBEVm`LXuK(qpQ1HD{iowUdh-zlVFBP*pd+8&zhi?4k z;ZC2moy@-#A75Z{x)Z@ST^wb~%jWu<)!l9N^~t9M0nLZ6(>eV{^EO^f&;)j%8AKJ1 zc$d!8Ka?q_w&wYKwsYEE<|@Ubt47{CW<%Oa9b@H~IZycx$p_wDW{(a7yND~exl;~5 zxLoU6OjNX7(&AVg0l@8!Jh}gtiMrvk@H!Rfsg%0oaOCxNEG~rG{ZtrVx3{l#Abxy% z>rgyB!Nby0+06Kk{&xh7BaCMk(8%jq4n4@Rj$qjBI==o^>ki9vDT&%%4V>B;RZGvA z?4@IN4OSvzPo>WVS-91=5+_TNd#jO((_lJS5BVdRih6s>C<3U2*!E!k^DAaIMuuTv6s(-l)e$y zmq-cf%&%-uyr4?pNWx?}aC6)t z3Gzq(tEXRYG zCeS_fOM-~K2v5EOreq2JVhmM#y0pxwd*yl@a!3g)Yi)gzr*K}eT#=p($u~ik#mNrq zav!-aY#19D(v}{3W4OlZ)bBSAbif#;Eq|ZI*8xTP&&W}8>O6(Nx41f_D6&u#gKx=Z z39;Kg_~ZM)92T&Rveb z3Iwq#ceCG>z-akn(vcPN<1R=DR$sy6y{l~>lQ5)_0@i};9r1ZkSKXq(04O-}Xi_|| z47ap|^LmaE@MuKO|92wqKbgMu02EjtY#Ap6;f=v|r@LQ}pC82s+Y%RR2UO_i1uY`$ zKkyyO5h(jrOQGefm?Uj7@JLth#S@ag2|MZd&Ss)`KEiKLBoXMHA>slB?NKzwG z_r%;Kej|8!Xg6f{D<`YFgu2Ef-KBiDw0V<$k&)qrqrmA9dT;eIK#ByCDiqTnq%c}; z#;po4Ar25}9I_Jgdh~pdje*D;BZZ$eBJ*9|2N9o<(U(@tMoOBRo*n^k1S#5f0B^^O z9+UPZVemy6ei1=h_j|6P}6ka_{XWXS}ZJsjaEKf=*xCKvHu6!X9T z@j((;H3#*Do)1HZsqz+uV2^X)@_oaop8SADjXM9sx$v`zJq%tacU2%Mr$XTFX?QJf z=r3JT12#{ZL?9VhSV{;YVfb??q0P+@7^@bJawPXvYo7QeD~z;I zMKI6iY^$Q|JGo>_6bC zZvQ`UoYPT8%1A`AcOfGp9N8ngC^C{FS=rkutB`EU%q~K*_b9WBL^j!5MmE3KTiu`g zzQ5nk|NH;_?#Fq!<&<;I^?qNk>ouN3y9H8{!4xuk2Hbvf*37MC-`E)b$YUa3ja@6%NT-4qy%n<@K(cf7+8wpipG$T=G|C~TltF5!<6Rjcv z1^ep-P@^i|b9yvLWKpKJ z9R+u(u>6@B3?!74Y}Qyg2HYZYs`yyOO>a$y*nS3n)(1*j1LkivHO6mpX)qKG)Pm0lMOv}cd{oJ?)(F`7Tx zF;*5ZkQk`y7oEmh(xhu-s;P~7vhawhm6!~l5{rA}D84unQ|1U-qc7HN%bzip#xG93 zL_un&91fd`1PcX_d{~Y;@1yZXOuDti;V{tf+Ox5S;1=ktyV?lyK$*$mE0wW#qiw&&J~(0X3g$6LmXCnjU~?YV zeW~n^e(h5Y;2WPOTLbESpuLcMHWQEF`!k3G0V7gKk0KKEyx;PDzeU4k%!EsZg9Qfm zV|uJV$ue__{Mf;6r@ZK=p5T|Oc3Gqj+n)e#f^?}nAzr1weZAsoWdBwx=YfGIy-BZ* z_OuKO#0Gfu;U|9nHgLQl8(D|91i7D01Uni($|Y-zf9E%WAN}J2CFjV~YcIOpwTm@af7kfTZ_5btUgWzOJ4}n!6 z{C%Ch+wRW>m>B4X09piVPTfdlPQH#put z7-osmV}y}HDDeI=mmwR+V^RaN$=9_y$4nbvFuVtWVTw{>q<(n`unL|yRJ@d8<hbv8)~0Roy)U>wLx+pp zqUFbrI}{Dp2D&8c6~^L@4KI`pM7i52mlnlbwS+G+bo6+{LMHdEr{}G^KeI+L(Nw~31f2A*7^tllo z+F~H9Rk_!5#EbCBH?)B(^+MBRu$We&7=h5|(jbxUo_jf~6Y51g$9DSqolc)!EAU~+ zgkRMcWfn{Bb`I+!@w?F4ZgU@_! zSSiVU(IYXxI05|-V+e|3?-7^MaqpYc&vB?_tv!3b)uopSLuMd9atF|*b-d42PW(Bp zIRYimo+85+!bXzioTd4`c#rLqkfxbkp6fl%lFxg$p=%83{El)e?r=cJyx+jsJKlk+ z1GRKN+-Fp=854_TOQ)%B4$x>7(CV8sJ*T%b|`@ zj5|nV4le1y7j-^z#L7sg*2_T1|2L;PM(d~imu!opcL0GJiqXM0d{m7dPCWi>j{xD{ zym31s4np@GX7?^@S81LfP=I`g5;eW~F4K&T^m|mX(*=mBqPT%nAN_TZkF}0oW4DhLKmo>by z{`!Nbk-Na}_nR9@4^UQ9tw(Z~HHgS^DG*6n>WGKe8CwH&@eR+^LyRuUkP?%pr^gO9 z2sM|L_3f=4yVCLa$P`ywFMeHoCdAO354S?NQ*+mUXhmuKA9z}^_y}3^2&ve{uxfCYo^~jX@%}m2PqmQ zfA(gSftQ02vHn!+Tr@X9aR^R#s()~B6!39{-pIuvd~gtmq6hH~XpKH10LUg|ca8?Lwi@bUEs`>vf|KkN_3Ew(RW2yKjCmami_|?ZQrUf2yHdt_mhY)P> z@8;a$g=1ibQ9w@`K7*kwZ2|h|aVmOfL8-r$pkTwQk$Sas&(F`R$+S5s(7z!VpYUn; z?4%`iB5bAb5@(WTX=P>Qk!L9>%r|01NQ@9diC4hI{v73KCPxylfUwq9Wk+_Y3-XCX zC{4N6upEO8L(2O)j?>9ctXk5fmZ(U-(0Z1%CRqEBo|ur`w|Z z<L(WsQC9`kdiY(VqUx2B7*elCY z1==SeNKItOgIbmt=;~qyL};G&r)U~7Yhf&y4gZDx8hy#T!`v>2H9FEjC_eh4^NlTS zpyHUXa>U#Fp4?{@_=(B<2@ybzw*A$21!pKeUSp|~$C-<$F%)&%T;@${PLZ$PL&LPb z>!l)KAQrVFsY7A#0n>CT&^b6YsTHHgjxTI^EfomZ&ih+a6Wen++C&k<(BWTMY2hI{ z-I@IVreN~+^%@IQymy=`Z?98{rwL)bI4{>M7t3EvZ?k}_`-Q-;RQNur!xvvyg+v}S zR3|BrJi0%9{1;6KqeJkooYVRd{6h#JHuF#Kb-F^|Qr&dxAZB zOlQuVL5}asz_@U#7?QU(ehJ~=Hc9p4*55f@qOoPIH$k^tD-}{AAytwzdUFU#D{0oz z#owK~4$%1UBjjfKJrH2ywdfO=V=@UOWiT{P?u2Or0`2-_;Pta0_tO3?KnxJcb)2D0Ey|cU}r2j&}l+3MH z^>aI`oBMY{ZR^QtmgH|on<2^w;9JoQ(3$yY!O2WN^g|0RYEg%*-TC_xL>XkVC-iJS zQp}PsGoOPO#TUhbK-gdU1g&2z%*_5YzW?4^6-wSDyvffIaRM2Oj(!arEBDL~o8p$F zZ4J*pbjRBVD0V$TKa@lyiBE4CvTmzXcbIdo-*$(A$?uH!zkXe08&OCxm)MBn6J*A= zljp80r?$UsI|Ucxs-z)g2WLa533CJaX9-}e(E{0ga{Lk8-}jraAC%MQg3qlk#JcgP zGaEXhbhy1XA_tY8ZZYA$B3-UmXb!aMJ+IlSB0vhQ4I_cfxd*q+v>w_2)VNIMe;f-b zMooBk&68tttVfh1(0YoAg@rdDfii(ipZnX32fvvD#X%TJNi66qC;|j}FpYYh0qai& z#*U#NZgED~+E9A6ran0g123gi#c?nxGQl-`@aBUS1(i#x`-%`*)M9+0w(_IG`ucRL zZJQ@B$Ay;h5u`$~W@}lyih(kh1Y;7;OtOHOE~R8( zk#M4%tbm{9$LaD(38k)t^CalU*jVxnPtJ2UQO&}m{A$J5u3V&D%KRRF<*5?Q%q79h zlLXR%pp>Wm0h9i(O3pA8 z^&-L_A0LNgT1qdG;5zU;NWJC~5y&N%W-IZZGSp5I#ulZOb10INAsC+j9L$G%EcPnB zgv2n%O%w4lhW1qT8HC9B;e$YosN*}gjk4hgn9#`s3_(c!SxQk1Y~3%3!a_SB@Ln%j!%G-SEBxaGR8{NL-2-n@VMcfIl&fJA?fBz~RlyNzu2!>vV# z#LeGK08ErQCxYD(#JOZObbj*+&}pGzzy;-8xDro=7KFYOae(kIzEDV5vq~)K;6hI;p)SzdzIamTkZzSoxtmJG*m)pO@`|9{agrTc)lb z3i2rD37-12N-8Qhd{aRh)I46(reb~~oRKFy7UFrGxH4&$f+GRQk7@#1t zm(gEG`1im{5!~39`3avDzbjQz~$eGHJZ$k_+c z7SgG>J>l&qQEw#{g<_8=m_N`Mw%qTT<#(DJ_S0va4v zp_{;BMtFc9FWXJ439f8RH^|ln1|B~n?pD10dDMGz)2YVl0OEshzrX+u$ofCp-*u)q zwv-+-t55LD~IusgwZxD9Qbw}>DV}F)P<)L zY?>qYJt)90$I!}K>Nd{PBCw}WD}R5}K#rq&Rtu*EW4TZt?L4nNW0p5LsdkF6hD zS4e!azta(eC*)*Rxs|pHVg`I7U-m#!Vz-sQt#M_dMO3ujow=O_S1VFI37OtcJUJ<@ zHdQ!Ipbhp;cA`ql9?hZx7k#^!<1opGh%2Tr;4!y+fB;;pS_j;iL`Bt8yOIp*(q))2cp zc2+*7?Zvkr{C7)kUa4I9y#Kv;s_Ri_RTMsp&ZASQBiOz7$G^LJ}M^sAR{M+tg( zexLH>8G)ajsXx#k*$W@S+Bj}#GPe-4@gHBr#QZGj`Pn;jfLyVkd#84Hnodltt@sW% za?y!p#s_b19JC(1adYUnoh27NOX|SR79OOV;F_Jvm}=a2Y#5ox+*V`D;D0%XP4+%8I_n&%EA#(Q0ARy%F0>H<~h~zv`C| z75S_Xw{nvqjeCpk>9qtB7{aM=vK@#MA5>lz+E@aOxb}u;`-%W08`^Wtok$%1O9X5k z&_0z^wByzm8^GUh29=nuq!#Wn<(oA2>E&Cyfcx560Xk!}BXqf3r2iDP-RExCh4tfy z54h%haJ$0A(xv>yoxjDcn}{hUyvE<+IXN3`Z0aG#*YZ&BmGASN7teshID-VDXBLT zsL4xBk-uZaZ2Kf!V&GATI>$~@;TKY&>8VS!=R9|9YWk#ZEmrPWHhuXuc$b@`i{N77 zaecx8`epi+tR{PrwI9`Yg~GG)?bvr3_{=VyiQs#8^TN+F&4}Rd2OZ>Bsu#`p(eR@h~!}o+#>LRkvlqw(&+n2-_9Kg~9$= zTUV+j*UXbY?R^E5JSydm9$)rm`DxNp8+AkH)E&nEnt#C$D;V3&lS3~TAT`J2mpHD3 zoq?%wL=86UR)J#fb23=o!`MHdJd&Ay%YBaE=z>f)>J(K;{KO9Z=jGHgSI5{kFB8dM zXYdYBGfWq$R{(YKgCscG0HwS5!Wkr}!t;LTQ{Iu)I1cB%Pg_$N2j_8S!=N6?Ex3@w z35Ks*BE9iNoDV+zQ;gtuzA}PAfg-EsT#qpCTq_^J_m?=`XPcXv^6byWT>f*s^O8ry zF>$%k;uv{uYXdoD49Y8Ww5YShiy&I}M7*;6}KOUew=i&>gaX;7k%U7>=RDYQW+o{KInUto>r;lJ0)``i|!T7F+q%m|mkQ&P8D z2Bk=Woj}C}THE10%RoJ66Q|A-ejdud|6X+*ns@q7yJPM1VW0UhpGqegac$vX`Q|QO z1`d1iVmq&?4Alp!Jgc@%Hyi*>jH*LG*WZry1VJ`(np=+@Q5e}>l{m<+UXWB&Y?k?H zbL`)93rK~$L$ys$>Ha~EN`Sc*px%Sx1yr0?*IFy=VrDB3`Ig&H$x}P<|!Y@Maa`X zLL(S%I2&M6L|&rI`&VCk8W$_(`injIo>;MWdS5suN|t4OOQ>oq=$4!rRF01yOV~~Z zJ{&B)4t5D?m3cu0QKezQ;@keRsf+XoGw9z#!(xAoy?udzkWiUC)0?ZPU-V$}!Uj{M zUya9YB~c8_?T?bw%0Cw{C?|WVzcJtIHETTDm=39N+zkBT3W(jGo5KS)Ou{wv|E#LCjSbI=O4te zv^oI(p2BU%%D{2Zp3`I1{vUd9|7_$Re(x`i`bKjgMNK3T=>Ow7-2iQ&UB1;Vp7@lk z+|&kdEGn;e;RK8VA670FgX)R zd=n&cROgoW=JVT{VTgAZBxPuS@&Dc)|NII}gJq|X@bK~SK>h}1Lt7NpJPkRy@v0sl z0^C9#?J9=Qn_s}Sy!;ZQCP=b5^YNv;DGBtiIN5y)l4j&2wnKK;b37?p^=hZAJa=9h z(gmssH=NgbHPghFAh7h~(*L|RFTA7(46h9V(#LNyzLynuhb$vIo1(b-ujYoACO%kB zin6?Iy)T4*{4a(^}Ea;o-SggWfqb!c81s&%5I!g?*vQO0!LMRMzZejp6$3oFja z)4(qud&qL1lF8Oe3N;;F_fiq&l$Tm*^QBSUr2aD!I(NRx>QWj}UM%Q3y>(jS!Ks|3 zvoJ@vxc8{8FfG)nI9`KNrcMgTTTPq@f92_=j%?X>*;F+nYQDSvBTv@D|JpA9e(gVR z1qWre>T&u82~0aQehXh3U5k2(gk`TVe6)$y;}(>1_Lt9}?L#eFx&GJsV9*4HgOu+; ztr=z5`3}KzeiV$*(=F9HABxU>eq~33=_X3K|MMYTRtWYa+OKNL|3SI>O^Uc-zip#oS9nsoWwyq92S#2`t<|A_PQ^vwi_&ILfM>g)|( z?>b`Ev)R1XGxO5OdeU;^FBk_}qNb0e?vzdM6RYjClOpCOz^7%mBjBCPCB<*f7?*f| zoPtv4G&mflrl!h_oI})H{mZ-U3Rov!<<@EMJ1@F{96t6ryc6;diuh560rG<6k zY{x>$w_60G1~>y)FWA4QZWaAs%j< zTMEu26FCaz<^iq|Yi2hzu4jl_YCLMlvtQYvSZYZOG_uqPC>8bwJ)9%xciz8ogVh*= z1KjHFv}yowh#-_A`3Dak*m0_6E6~@Y`S|&zVo}K_DY1zUHYT4D1}a8prr@2tCH_#5 z*yC@FDhSJGB|IC~kFq#M#%4Sc_1LEmQnb8a_-an@K^ObGoja?1cYehx7{WO5ORKQ$ zHPj`x7m<;lJXBsqk>*e7pDBLvVe_ZO{*Bn|TVwX5;iZCSe5r1g`u>X}O zA@?4ADZW$vSIN=vaa_LM`#O&TQiR;tqrQ}MU6{ph1{qKXdaw2HEAh|UPKu5PIMd^% zllwn~gZR${6AjpiEKmlm08^CSea$fIeaGM+#mV*X*c@@e->rFB2HYN!KTw!Cf@&%93W1w#5f?*o@L|vxOs{v9KEvq3q+o<;wxdFmCiEu% z4*X9L+n+!SOIS4>IRx*k0R}<0y#eVik0BL?qYixjo(sU|JI|`7uFe3Cu?506WT9Ds zgBYqX;zyB8|5Rp)n!YCZul-j|kBBrpMAD9G5fm_m;MO=j$9VTpfC_PZY7aZ^c^tu& z!ej(QbMnRSe3C~o(*ubqWrPeR7N0pzR*?yszyH__|NVQ+Nqn@RX4CwNC7?C=Oqx%v zH?_41RoXs!tt4tm!DQTRUQe)9XSeg#L0sjzBZ9A0(NMc2muJlgP^*>>yZNZ2e|mqXke-)U38$v zo}-Bv(X|6p^;zQ^mq0S1RhE@1o&EW~}ZZS!%yUz)0WVU3F25s~3fOi7}dVlG8VQg&j)|Q47 z1oSZU?8zFuE|KBkji4u(E?BqGOkUWB;=`gBUH5;@vPY5_W(S3n4{-5ulT?z#$ z@N4xJfSZ!)l&xt=H-t@`hJ}(2tqnPHt~dNY;2! z?LZM zftJdRR|1@7jJy8`2LHS-|M5L`841T-ot<~aO$Ul?4q^Rv?UndHs_%n{ysUsunc3QO z$0w0%(H#N^xE6@WkKFyp;lH5O63Kt8GzN8p1VanVcSMvcYlqBqBym0SBA{8$p>!U5 zS+XRjmuH%7;#UjyK&!Jhie8fu%g%^#99!g=hf6!;zHu9?R0dNm#NlJUzo>2VtgtnP z_u7XdP?qJCdwy0u0nt6DCce*U|IA19p9kJH#_LGbHcoI50<#ue16x;(9 zhnN?tXr@+_6{-+rX^lNT2Y5qUOuyA z^1y4bv3S4CwH&3JcTbvXHK&6b)*0{Ld9~3n;!}^LqBkr=(a$@H((UsNJZ+lqR zW2l}N$Z!Txwek&q7@H5W$~9uXvRkiZ0=kZiZ+7@190{PvMtI-{uWhl(^nvP?tQs6v zCY>n?@1eA&I9i5Bbo!7~3E=sKC-2(lV2r{U*l}dUe~0REi1kRNQQc9BPXRc}U}MPu zFwzIZV9W7wt|j@VqI;$P$?bbAS3#PcgRPhaQv57VN`TH*0Ccviowr3>e<6rw;B8P) zN$u1S!Mf=4aYQ@BvNEALbzv~wYiIwu_Vu^d(#sP{x!49YOGCzdU8HZC#c*@3R~%G( zm~kr0xsDOJI82&t2**t?UVV^d>ty!g+Nv6Z^yk=XKcJ zgfm|gJ68p68?{4L)J96|!1BOc)?SLIZE04!xU^`S%0l{3q$YS5xTJZmgeBO?h-wc@ z!E-7f8@Lw+h9ZFtDY8{Em9me|)lBeYs>90rKHg5y)vs1}WiID=@~rK-Cb6@1#n=st zgJ%1TAF<5knf;Y_-I1%CDzgqXDQzCV@{wWQTSw8;7vZXBc(ff`4`Q~?H)jfSb_iPx zJi*C0tl$ZPNQh!0(J#CBDMl2pP9iBLPY9b_`7ElZ$2rmDLtEn6uQ>@PRv`UKj6z4A zH~W>X`hCCIWA(F6$%^80l?4_R{ca)@9j%kn{;-65Z}FocZ?5r%qASMUag{#-VcP`e zF>&vFLk{SwHgn$p9O;+cloOJ;u*#-y=3Qb$hq}O`fB8~wZ=;%WVdM0VUy|rgxPF#~ zxfDmaK-ET6>&f$8ZqH&FARN+39|Dy3)E+j3QN1U|J+4+iB7FgbMvJJ=r`M_Mf2x+X zYc*1PO@nrqMKBFXglo4d-AYI&j}mwXV4hkTK2%o^p&p=lClGUjK$-~mxI+WR`43=$ zcu@yT4AWy4zA2}w7|-5eyLZD-i$NUGPqkqUyvD0x#uU~&opN85ip{gUV_;xtWJTf= z@4pVOHQ#FJ^19ld$w5N8FV4M@InvC**ycJ20%G(pLPHr3fo}lk!qH z;_hU0kmB-vniDlyX*8C_lDxNR%?k~^X@Jmt@oW=}nEy^9AWA0oSOhXgWO5RC``~n! z5RCgUo%Z*jOvg3*j$oy=MkH|w%0ycERN)_wmv z9~>gb&o6!Onq`DFIrO^VD*X2Y{~sB_2lDAWpsUf#TzzC~d;PthCI{JC4C~+juJ{!q z+*n4D;=oM808N$YeeSEvFYR-{mnNb*}U$^8zO>4Qajh z8JQK(9f(zpqyFc6`TndLw8kwW=uTQMg1ERA!6c?s7g-s{<0aL;7 z0#w0~L^wbMnUu;HLZhaz`6T~WSCr(Lh+De4L8tcjH>!wEf7_lEs^Wm!vJ?QKG!0iv zZ2B#Vnn3uB=U|2BfUNm0mC)ZG>jmZ%+4pj1+fxs&MJUj(p>w%qfMP#Qeuwao&?J)v zg6_>9(n^_0Tu{J|0shEnJ6ruJd3r)*#?M4p;E>*IQ+A^AcQ5xhzx$) zu{e=~BQA@yFL*JiNJP1SFPt=(ojtj8p=g*JGzdwk!q(Eu%k@moo$QBsoQxjny}znL z0c?+f)%xeO_3eRzS+Oq%LseDA=`}eX-#BwB`)m@z3hFzb8tmK(ynEj@HtXoB%D)cx z|D;s!SV_Xw&A|8Taqvy>mLWd!b!&C%vtRLF6*?)?@?u%`qxEmJ_rJ@Y=VwsfT=Mjj zY!Z`YN!*?@sCaLpq;@@wB-Es#-U$_SHsDZtYy2ANedv0xXeKWAP!{y-LDdRVFLi9h>NTJ zt5HSbfLbaarkhzEkJbAwgIS_c$hnhGDJ*ptl)WS;zaS zds2X)C5>aLtE8y-TES#i5oN>#P~+a*)Q{d*l@fboQ`_$aQX;qBh)zhN{bL8SsV-aSO z>u4B~Co38VaVgR9TXNr?z2}P3H#kx@Gp>+ujEgd;)yiU_y5eUe=~D%j#oV?gY%zc& zSvi`TIHhk7o83PX^P}frhuRrha=eSAexE!f*wV4vrL*Uf{ePEnXgCA+5W&)ReW}Q|Dd;wd_?%m z&X=8o8sc;G_>b9~zLu+oyk-x1qsvaF2ABUAMbMZ~2WaRpA9@wWo5@%}?X;8{_($3#;*D zW-g;Xq@M978A;N-SdAxtNJhV|_o?>6>F@T!%OSzn#Pml#H$bCF@U=1h6#X}f)9QV_ z(85zc9KA4$@c-sGi%R1(O^)FoDSp*{C^J?M<#{WHh0U)fPqU*@jhlmIjth`E;;Cso z^T+JtJwGS*@<3XF6pJEhw^3@~L80)yxvclL?afDG`Si5zNL7;;+fF^dUG79mgeGak zRs;9<5eb*NEW^l`&CBZ3A*?6{!c%SW3-tq1gOuFSc&nVwj%^XGapsxcCtDIra$K~y z;dlORUB)b%xD;Pv{;ejH=##<;PR&lBGqo@g*VM-@@Q!`c&R%I^8;ffqodnstCKneM-!w`~Pho#$Cya34>Qhas9;}}FfDDDJG`&2jLJ?ppXb!nl7v2BgW z^Qwz0_;>ad1fPlCiEr?08gg5zTg{(+Tk}=|g;EED=j$4^X7^A1N->KKTV}%GVEsNvN1}!AVlGtm+q%f zUB~gW|8Wuh%a+oYw4EG!WxE;NK87l(^L(Hq&ft2nBS-M=D8PLd4EEl7Mod3?;r!d; zP49G~X46Dp>O*{K=0pNx0x*58#6YfQ7HEZhPP=O{R-SjoVp1r3vh&*9b`P4)v+aFU zu-Y#Ng*E01s+e6*#|F;=&8U+mkp~6Zo@n&zziv}?e4zgr_%rs#re&(U2SbUsF#>jr z>*iIz%wtUS;WK_mKMM$mA+mnx?M8z&!h_<(&plWulm(Nn@qRX7fbj)B?%+*XSlVSg z&@46uALQc_ckW0VqEQtQVGlju@T@kyf2x`NXp28)BBe@aPVJ@^CFP0TV9E7$vz)^_ zrP_vz(7sa|SsAMAYS3o625*oA-vH4i9hFlI48gZ<(P5rve&*GW($3K@0OiFZB83?e zf6U02I8g`arsHd{Dxe1_zk?~;j!%j=zBK)xyYJ9UE|eaGGatGD62WN1rj8ysJ-xViX^#|4g7vUNL;B!~cw9*1juZ~AL1IM(EdjD!51qQS&!Z5aM9w7 zh}*rp`;k>kxA7xcuz>0WTGy}l0uNJe{dT1{4r+rE5}egNiRv(5%bOjT9H{OKxgmVRChH)LJLFnhJ3c%*xBNIeu~ZI$T5* zuYhB9O}Xc=c?8qNsbiG5hj%}g8Q+wWp8>zGFNO?`3dO4zLK;#Ct-nNcz1%UaoYC6O zATTGv7zU!KMGt_sVjSp`!{k+6fm&Et_*5Uc@g_GABa8xv9eg&%JCZn9$=^oRza0?i z2~aPYwgC&G(8%d5O>ct&SdNNbeA99}%8rwGq4w6_o{#_KJM4MfNv-DE5f)enro@Z> z&MAUtmF`H=>jI;uHBBY%WyP}|h#jGjy55$*NJ2-pSvTLEN*!sbS$4!bAjd(%$2+}} zJL~JP<&^{RPZd`K9CRK?;CSyaKS>*dszD0Tmi%HKquR*-Sgy~XKRbZg!(Vq5Fi%+j zzuZ-I!zO?3s`lcoOfAmXMt?k9G#?6ZNNE=<4|}5N}pE{6(PP?m)g(O!mBcoCPGc;Kqgadfra0p>*!eYIYDB0 z;jxAYdgXlLWe%uzt=?+qe6ek2_Ve07TGH2;FK-O9%LtCF}yRhaWuQ-+v7#vgL*dckhjxMDX}{E0)m3Q(9H^b z#dt@Z_HOM(WGYN0v|6H`X~$Wj9vqIs>JUuV%N~YnZ44!8myy!pK>*g1lX6bap!&OVKZ*vgW~*7yx@7p7ZTW;nH%%HpLh5P-c2j^F3?&INQJx? z*Bu{RaCdEpSKbQU|1m|UpxZx))mU5K)wRMtwK*p36v2Z9S0+9Kg^&{{JeFJ#f#l+R zG$yR4Mj>ZJ3LgtaffPT`zb(NispgS3W>=2|OT_o1{l%L7 zK7w#TJjYcXAolzM8N8`qbrQREC5=aIK4}@k;3V?xu)cDDa^W3j>ynL&{S~(KQo2QH z<7d|~$7d5YKlS#c575l^j4qoe|7fs3VNeM0XB}SkOb057RelL$ zm&Xdm;^YIA?ccWs2M15@vJUcGWRa$qKu%DHpFd>2e)alYxh;f4sIg{Zxr8QJ4%Rg> zF-e2GA5wrsXAV0LF;FpCW@LdpVA<+LrNhEYFjv4MhRpIN;#7UfcCz(_F-s{sx;UGK zaGj)cH%wwHOxdp!p$^atZ~A)H{(?ruSB83Vw^M*#%^%*W7+#e~G&;Y(opa#QD0)(G zV6bg(pv{D_b3h52k8S5yQHP1nfBzE7QXQt)^d?8yGQsHw0T)fZurFObdq0}bb^`2e1O63o!rKQx398yJ4(*@ zP0}88+#0eqjt~DaB+j)sm;d&o<(lq>{f5q#CshX5oc#USAWHmkO^hwawFGgo1cgxK z-UJ!;*vA}Pcrx?}aFmt^P#TIa$P;0A2;l75=|L}UoEegWN{O;v{tDG_t(JEc5;NmS*O$bp69x@XN1BGSa)enuB-6~pyT6TcWNpU zLNf4k%Kc4d=9$?spQ3kJJsM$0eUHp9EKk-ydMI)%s#O2|+#8ORXHVi1+ti8+ zoU@nY9w1Mw>#h(;z|?B{8Uxm-RSY#ZFjvoY>I7P|93X*6v; z04d8_%wlTF6q5JB;2EDsZnP-N8&3k%)oJme{Cq zD9K_30L&Wc>|RB=;qfjhY1D&`Cu^g$;-ZVy#@%K;y&3zi>ut^F*|t6&mun{Nl3a4K zYK+V(eSd=WI^n}ji7HenuTW^Z^y(`v4XJ8k1hi15PNIm#w4#MAKW1HRe9n(jc34!t zuJIxoxeNcwfWX!5cLrp*StkEIEN0q=h?4IN-AvrdhDvpJcX#B|CzOkXdZl(vPzX+0 z;&PlpkrJ5dmjQ!8He95Maip*s5bguq^r=$xjBsJ!PniU<(=Z2k*`h>-JDrijq?W?; z;lmGL=H^Sok;mZW#ghx-rOam`=V}Uh)+%@Ly{I>!DrCdQE2NqRDqJ0z)UD=Wp7T&0xAV0GP~URb{NloXf1wEuUBFbY)x#XGh<1`_<~=#T4xm?sCM_}Yo- z_YLdQtc_U<-H3R1Z&!XKf2psn9c5y*$wW)JH}oaluf#A}lv-k|3G4h&z)s(qGH>#M z#yR*XlL6qVGl$a@o6Ji*YotE|qr1eJ{E<$YoI+g_H_G_!J z6jSE`Z1w?=TDmZ}zH@|T6eLWD(CwSgkn!I^`l=e9Q4Kr?+Jo*UG#W{m3 z_@CNUABn-7^XAWw0g+-O#$`R1IpO}H^KeYWh)w}q|4$_*&2ObwQcxeO5tZwL#^^tD8 zO;0JYLUC8%00gk>b5WN(xr3h#?Bg~5B0NGHjE0>Q{twcr^Wwp5qs>yci_^X))Tab z9)ShU0`}SKN=s=mVgps1(n>k8pz?S-VnJwQZrJP0nvJig*p9&nCVJMi_{ECLNQuQm z;W+fWgO3KgGCRo<+sVpbzTqZ5f01{TR@oj`41cx@-vL!qnzUcF;VQAhBys0pGx$Kb zOOCGC5|AK;j#dXgqsKgiap{W3&p0^I%1CCca+7Jh%;^#I6-+ZX__R-aX1h^kXO_mE zsz23t#cE2?KfQXjEqx(1>S%np>(7sju8WtgPVmb;R_c#@lTujNUrx%(2U4*0&>%J* z4&exA;u#X#_jR8rGiVLJ_q8*9-lxrjD?fIP@|}e)Px&@pzx$7!mo05kL5AgqJz`l`&0owCIb{Ic3D zZd`=8J%!oq#}&x2qv-FIzF|gUC}VQNe(F~S;#CzO7w(Ba2WK>K%=x-u;-=bWSG={m zN)BpmEAq7!afEQM(lqsKD%awzF-I-VGaQ~6)K|4sQHP-bds2Lp9{NnsE7={pv8*L} z7HYb9uP;T9Nd;;HBBDt!7}c?!o}MFcwq7O?_SkhgafhXu9df~ESH-Yq52*6zNsb?j zs<6!H*>Di=$P@?6B2lL zq!W4Z>?$daXWxiMg;Qe(@TWz(M34Ul4{x}BHkPSigY`tmo zZMNR`xw#Q8vVqFZ&Mr~)KDELK-A(=ILHWC&uK9!z(Ik|3eFlUmL<&I3^GJ>ntb@EB8#~+vct6fa;4zB(p?HT5QQ~t!* z+Eb?H=J~ExFSwNn6oVj>0YjRThs|iH)eg@(Nb8T8>;eOb>cf?;+R-{c{b#<-kdv-kWO*}WgC=GqD))eH(1HsN7foKFmqh(xvkH={mp&AjiRWNT zXg&lj_V-j>W3_Q@ewS{zDY`%uRy^GDj*XZjsjSDMN;Nr?E$(1uKy7DFIBrV3^;h_Z)q##KP2Tfv z1MlS2R43_P`IcIyWS$#Ob};Jd3Cc(xoqM`Bv^zrP_g%u?!YEZ0CIy5(3QF$xwP@)q-ig&5jb9;a9nu8ddie40mHM$K*2``$##eXVk?OXo&IL-77| za3AOuPk7!4+vEIV7~2nEK?%AVH~hX#phb2F+_=x{TNat$5O*Uzz++$1&;LhzlUBRB zCQN!0gdg0DQzhig7pJr18>I28%=r>&7}y>fN~YPM;c#|#M7`2#clo4l+v1={`F8p? z-9d+9f>SMzU!|kfSsq9&G$6t?ODI9h|q=UtM=M2Touc-{7|v? z$!e|7bH9z_!uF`Sfl_aN+b@IN6NOF87Lcfe8-9t`}^+qHRhU97dSH5ER_9b9s9HmTnjzxR`%WPI@lJ9JbzW~H29>PO?Y3F z?mDNiWEOW7@)Icw)#kE3oW36SEzyJSU-9mmZ+DmHBz_(%G#c%e{=ftY4(ITbKE`bi z-nPaHivqWz!(@hk{nAT8Zt4xAYcZ)0wja)xJpW#_A$v=8J61*hn@W&*nH%$Z3SnpA zw{-)qtPXQ?bkg<~;1BV7-W{ZAJgp#xTR&`$7L*I$c>;cOKE_u5AgjU%7kM z)o;PZKb2Hh8M4aVk~a4*`v4>1(|oq% zzU1n0U-{lgUY@lQx`Ur|ZRKxieMGFqL45Ci_sqwh%3p#zol%wHH#dMwG3Wustzdra z-j5!Sg`RSvvV&fOb)atc?f_S6C$>u=b6kR zUIISeT^F;`kM#g*q7?tmW_DknEKl zKdf=Mj^+XPdyk3o>T1ADupQ^O{dY7A$0=viUoawq5nEcI6dkIa7k|{qb8&=Onx(;f=5LnqBS|#<~yX{97*s^ zX*uD!*W|$H$oi=qhdZkcDH9f&T%MD;$T%`!88~raC?bQ0^^3%(u!2Ht9CwQzMRX z1ohyZbkA)z*S+l&HPGCL?vDUNFrfcvlZ_-{^f`qS%@PaE!N>*0(xMgo5p_09~P>5ucRY)Qvdy`~^ zP-M?!%ig~8y4CaaJm34i$9H_kd%PXibKlo}jsO2Vf9ni~cc)22($f!cQO@di$B}1} zWONC#HyXp|AK^f*!hHmb4+%M5=C+x)Uje#|ZFDX0KAX*PT$es%Ws&>T3jX!jImf%RaIk_C^n`- zZbV8f%(&vS+yzL)GIat*V0QNF%1Vh&gq1BlPhP(jXTfH>InB+Iqi7c~MBg1Q?;^aT zuz|C_H}&&-P=_V+)M{66?=*QM20KW$NCV*#%<6EOnUZm>+TC*(CoW2#by4PPOA=nN zbd~8}FzF8euGhK8lkgR4x9CM#jK0X%_{PHXi*_^zE{E(>OZEZm-f*e zKc_raVf-oIzUC`2hUZi#yB@B&ep@BYr{nJx*xAC{GhgYIzjKuSIhA#li>$Wmg6L}M zn!Z7J;(Q_kAG~zkEmZCAbESL3SU?udrZrl0a#cs|kq&0lb<3o>aW8z;M(NSNDm)7v ze#Xu?9|rnFJxGY< z5AE_Mc9SI6%jeMi8qig2J;v2MO@u#+%OAPoY=0kT=*FaiL*)up!o4F4N2z4)xUH>C zi#d9>hp^4Do1P%*ejYTc@9_mIb?2xx_E2YD#|a!m&WO-HLb7ZrcE4#_YverKe6z`}yd^4S-3s@bG9-6s+ws_p)>&%x2 zSz-EU(Sg!jAsmw%N3|>$svTYoS*htHhVO`)K`ySBd$aKREkH z)l@Fz7YwYf$?YaAx(G94E~-)0gDe$y4WMu=208;$vNy@5vxxXWXi*^8~F#Du1b$?~Je zdO1#b*uJ+w*KFUBl0W@IrO^+$NYQ%e-Zt}Z6Ar%Mp*p8}X3Q>;vg^HcVY=8WM)EaR zKXr6z>x((Lx!PAbly8MrhZd=Jj(kg`|4i*OB$=JFfHUR$g+rt4BBn;~NoG5nBS%cL=gC_`mF_R0WU05I<*AA(tRFgUW~wS|aI2tdMpEFt-xhN!*DHlr zt;h)9Q_63dSy6oC?TeoLcS$E`yHsA5)7ud_abWh!YxA?s;nLXk-PWm?U6xXP}#RGUTOvY;C;k z$U9;>)D=L=It4#t4$~0kjvolb+A~P;1Ofo>huo)FAkRzmq-7t9U?U<9c?wEz0hv?& z)j+Wmq$EcWdkYen<=J8sd_>tuv~Zeo#sNZ=3tiyYZCnprz;i?r3W1qDz1JG-l1hqO zCo3{`!T?YYb?5WkxWzPxKnnO|o99dsSg^aF6?D4j;Cx;je)C&+2C~vgotAClY+w~= zus*5P>pt5#MFct)7`k8sI+pKxfb*1xhtvEog+r(ymVcF;e!^2@Y$Hx3EiH2j)Dz6@ z6A=~Fn<^9j`x3PGlODi;wib7o5a)52!19cM z1S%PcFT4uXLh0`q|Qe$9lGj zFpJY(Ki|xCwH;u zzS!-*7X9wF8vXq$M@u0F57<`%F=!B0LI0Gn$8>?%_JG6w{sGm%&v9h#F?~Lre{Z5H zt*F2YwU0SjRzEu=GNrT`i0k(ZvOoJJi3Bw@7pC7_|HuQKE`2-JqIu*nBkI zm`Pheci~&SydDncXg>#M@!ZsV7unv;PP6HZP65Un?VG0Yt+v#+U0Hlx0%ulnryK7T zujzxdWD1ShVQ!$`cav;a>^Zo@-V*#BDHaBSUgDnA-K;#`ytZQ1*9n(zp|3_UJ#6UM zSkhGjh*D`6NyXIr^hk3Jl}@iH4waaTK>}-yYoK_7Y>lIj zjPHYq;Skx?7e}eYM97CtkmX-j>v~!ahh2&#d71R9$w2#=6N@!)c zJ+G3y?&Q^JdP(v*vV6r&jgJxidaoEa*IlTde&fa>b=PdYmqh5CVmEeltsR#U?%?H^|z=7nooZLmr@iw+Dtg1#u^Y!lpl^J~>Y*WLx@Z6S>! z1lnUC_>tZ28Jp~$?8Cac0+a5y$$zT796n7xi2?SO&gIj|R*Y#lDD{cklkKRF^@rdt z{pkJ4?COdSgj2`ejGDP)mQ}A>MRCZdWq5U_{L|?z(XSc(49k|u^Y*NtBY?_)4qDrx zgvuI(5&LfJSEUH2m9|@%+H4=p(U(=Ee`0X0N}m|E&F)sw&5^?1>k4-t-$UYl0em=yRT3+vef9e7akt;Hy zrw5|jjg#7*-d^koM{9pTta8zj+Q zE>CC1{YmPyx0zqD(joG(BZ%|VH`_KsK|GQ+-6w1saXjkelV1Vo$m66RRw*bL80-u_ ze8$2|NosdEt!$`gSJzzo( zP3rpT?MGa5MDgFY7NT>ktkdm5#oLys>>u--RMb-@#x|lu1eQJHevfH}!@Ry<(J6Cy zBPa2K4Yy8mSVk)GR0tu{8PQ>JZ;;a%Vu+>o{)&U0L7eNzdvkwkflKF=dQhO)WVv_f zp=?_U_Hnff5~4I5D1e!G6@DN(ZYvx_RD>rm?3nI1`IqhHm&I0JU<0OOG#jRk*80_Np(%4SQzqN}XTk}+8D3Sx^c0-ldg)Oy@ z#7JXJUJOjz##7kZaW_7`vEvU0hRBz#9oX5n3{so?>kmb02!6z8R#a3BQymhaAxAmR z_cu!9*PZb`gl3WOQVjcFf@_fubu&3F#%+F$NuJmWJs}!aCkUzf-e@$#B@l|C6k=Ja4V!fQFQ>RyD>SRLu7%k|~7<^rXK9_x}w~Bfd}_FN>FXF)s)t&R|x8zMYXa zbNrruDEf8HVMyD%0yNxJfLCm_*TzYZkN6M34m?yXM)2hG*SY34k zgQVCe2=n)~yW141yrn8`Ks+D<Nk-oXIJx+m{4Gn);HK-MS#EY)qd0i<*p z?BvH8_{0TLp*~22suF~-MdTTs8?5L9J>rf%TJWbY^N{;sxwy$*+@9xgYW`aTgE+=Q zeixmWZV8ODv%=J>M)fz^CZOV7TF$hGMBTRt@AjQt8+bQ}RcPi+8jSt`wd@=)gY7S< zla^jzCGC=SfuRArJUlg-wV)c7C;5&u_6&-nb`bc?LR#PRQR81~3d?W^FpUD5!a>N~XbKWKUBionN56Db%k<3; zEPU%P86zn|7_m9ya5p!K*RVNWUL{OaU2UD)80pqXyVI<7H>rKv^Y-fNFZz7hnh&}wHDVwvV5qeM#+FrOIi&Y?-C1B! zRafm@B5R(5Fo;3xls8U zWpw6lCcryylCM1NbryAX`u;dp{c;A&JG92O%U1A{!JCwm4$h0N)^93!``1!k9{6Ad%v6R72|TBySN^2$E5{WlwQv8-7w&Nk?5b3 zepPQMB~elzU8;D-j%{L_pKXD<{ zP6N45$O(Z)QmKIuD)p&1wzNNnvq?NO&fT3`a!5t6g4Zv*%3g6Vyg&Y_MOGj7s?EvH zwGO$8*qsB$Bc4+H!H*Zw96M8DDXh-{At*C9Oy)rl0*UIbYJB|2J&yaW%_${)7qNy&x+H*MBU(XW+C-P`F*U&J9VmEv#!BdFkBE<&U0-ls<23 zRhU~Ce}v9%Xq3izoy3{fJ7k}daH)f9xx@(X~dsFqmJ zR681L4L0lqTi(~=WU@Dm7;<@L0=hI!9=E`Xn}yS)zGP>S#o92^V$JRQK2q-Uc`~Jk zZ~WyMFza#lV>8l9`6Z)N6=r99$&izeDyl&P9#65yu{L}w*ARQ<$J@K_hsfk7ZnXCh zl<%(fDH3{;yIOHwr5O&6S1-M1_&HLeVvt91D(G;UjC1#->{*I5)(M3W{&}gOd!v(J zjCp0EKcOPB?0soR@7f2MOeJg{gV^t?MjK^f8P-j)Xsl0sZHtbZq-}cIpowpdiung) z@*b;$b?V)%>s<<9_>0OAUvJINGCuSP6!2rC_jXK(RCkk*4A0(#o` z4@LH{K~^@`^VWD%E;NFOi3YJxkSduovmiacNC)V1eq%K45bOdw4gN3R84{K)ef2yD zhC@{8KZwBN27aBd0|Qj)Y){FtfRH3eC#vDZ0g~_@AF8FC z9Lt_{=fg0?p0T~N(>Ng^A!VCuNbC??0IODegT(6q^vedk=HPwl$qnD2l>= z1WqiDmIHpUNH$Ik!Nu;{x+lm{f{(Yg$Zil3n_J=V^gD@*i~pA6YGmt*{*vQL2YQRd zBPuEti%{}tY#xU_xSDz_#3rINS5Wc8buU>OaFLH(jSGK7fkjL$YDkKrqN4iR)x{HN zxkLsM;LvJ_u%TW#If#CQ@I)C*d0<=pA7_afN~Igq;a5;0_+Xd)02?V4JJkm<8h&pG z&IS5Q-|h^J3HI4jnuYgb->0RRVO}a5529W^1qK$QdkS3A1V0nV$6Ty|Ec6#`xO^hj zqTWEB3245qWg||I_0w7iU|B_k^hlDV@XI+w`Y`D})sd#It9uX9s8*vP>G>v%-N}Ve z>W>Cr{klJ%b8SIno}Es`_)moQiuEA2z(m< zockwcT-8@eMYQdB=3xQ*?d(T215Smk!wE>KyIg(gtr7(BQTR4!zvnok!Td~u7QE@YK3@RY{+tc^li$b1ubP;uYC`Q)9u)Dzn zGL8G%%H6957y2vh`#cjcsven-pOP_(9;?;V5L2oo3Q7uY`!sT?kaKTaXH51|=l_)S zFqBlRC%f?5{do1Ix2a`ev9NnhX!q;UJ)8dC2|r!548P}##1>&;f>vAm<1cOBBTLq_ zV6+Q`F50@{oG79L1q(7r)v`Ccx&EyTh1k}uoB$X0S~r7-X~=$Gvug9@vlZ=x`zy<0 zUacqa070_(u%lreGupWEV0%vx;J(r2oFIv5^Z8$P1r&nAg*?z$j}fh1(f$0CFBC!d z1_MZH&hLMe|Fyp#MmJR)C>pF=Z1_rN&1T)~>#_{0JnT-^E$Tp<|24*bt_)P4Wi;6# zqGJ8ne`!8%-ThPZiKJy)43u7;`p_`G@262^ScY~Jez!BxR&Ml!)0O{S3jwwbyVZKI ze%X>df4KZaCv&E2f-40#O*vx1yh8$>(_R1sKb|HrfHnLcmY$P(dKkcc~lmD0Y@y~BX3J(|`I)=&tXx*H~0jqg33Q{9+v~2=| zMegGS1_sI-yB$-$Gy1bD9lIrEsMw?jTD?Z zKb(s+@KhXB65=%cSPBj0;FtTsCDMd@l`R*Vu!nL|pD{0|OA-S^74AyB5X7w$GX#m> zFYX%&G(PCo!FKV>PYJe3Z9szi{8H_}G;TH7N+n=3E{)9`PLG-=fDrz^T`^@b%u$+R z|FK(xotzP!_}e9JxG-4p&)phU#e+9xZSU*53%f4@5&@Y)kY7|p2e=HJJrN`9t3$H# zYEM7=;-z+)L$rmgW_BY7su3}iOG`>NP8-?tUhW49P^(kL zk{5BuWeuUjEeVWsT4sao-}wk5A2z<|FcjIaMXKUTq&>zRR}*;>h_E3!=3%E%e&>=FeBSim98`L z-@##!koPlbbg!}8FkJc;0ua5084{f;WJr;X7P0Lq1ZyPR!0&@1Kz9v{mGziEyMFm; z!lTIjoJ`CNnAIQm^CFG+xrO?fmz)#nDpP~yEAg!cy(FF?ASBz>!dRIPKoc4^;L&Qs z=+7ZO^RBO7wJj}q$l17JhjCEH@qby;hYcHFIp}oYRkRz?sfJ+O|~VM9_{3aNG?Qz$RvH@9vgLS3Z9? zME;|mmj&zm(6yiq1r z)@9B;7K0iqD==#>GP=)#N~4Nor?aQs5CkZQYa4vFUx(RQv8`X-ND;WLoDsns0t+J| zLBYBGA-z$XtSdHUC(~Byd3kw%QuXD<&Wm^~L4zljp65B(*=jkzA2qQloXu!)7wp|xAWAsfkcX3ajEj2O&*HK0!^sJpu{;G@(wY%Ug-1OwPSR(!b6j26*2`d|GJY@yI=>)W0i#!7kRb-`i2#2=4BjX9Pad0K zXJPUMA!#~Oud7Mb8AaEHnpL~^!W6s-0;Yidtc0{VX0f8@Yzp#REuWO@?Iq7K2f8RH z+ReYX80E0}V1}7*65Z8V%@-4bz9Ti|1o0H)najG<=2k)5oaVgGA8HJ>Ow`iDC}e9S#Oy;&K0YsJ#U8qm9;0aXz+~dmk5>c^ zElug8Dq4{m-xwd!?!-Ab~jy{Du0A2o|spio>MUOD3a+<%$t$v zD_FCvDNRNEMlyY&O`p4YH;#+UaWX2-4mqw>3Ae|do7~k`V$8MxNzs%O(OH63ZQ5e# zQCT4X?O5MgAfQ+t+Ze;tmu=C|z;bhkduGsy*-9$Lxb5@$n@AWN(?F^xQ zx-N7Dqe`E8A-fXZt9R<2rL@1Oke4gN7uFCjUoH-eW1SbKe+va9ZOY5lOY{V+pa)?? zL3jF-(~VO*-;C|^q8>I;v6Ki}aeC?#B8gc(9)vrtPjmUlruMP`+urH|+b8$hRCt$w zQ&(YpTZY1`5eH;7Pek&9hi*cz-2cnc{BWUru{BfujTm(KVyD{QqW~(#<5PbTB<9uk z;e`^T!ogmH5yraLzn{a)pL@DF|9~?80D%o zGtr~SYH+IRf%`E@fD|zexs-vz7-5{bg+Y>qvMYju6RqA@qZeBr&<3X|U5%PGhcIjlv#ULQUFD9j9D*qQWwX(LhMO8RbkJN>5xpBu8 z5xQt=D<+)Xv9?b46&O@mSd8Lk#ol-}B?!PNUtL)l=fEQ7xxyY0)L-O+nU$ga|AweGpFjqT{UH_bvW2Ii*UHF5kkcM80}I}2-KWINBGl{~V=fsVS&sCRgX_0}iu+htUVav$1t8cBSTjk6SmiNEYVL4YE@ZGCLp-|HPg2f5^IG8gg1<{WCN=y zEmxeyAEbtd4cuIIUNlU)d}U1wjgF0sT#eyfDB<gxKdv0V#ZGjPW^>w=afavL{=7}+p66I_4Xc(IO&e-x-y>Qy1;+7lE{PMOAB4dO(x=_ zC|uGI5$m@Ky@hYAn&f0oHl$R4eZ#zPzi<4+RJ0UV7?O%*#EDea-(Nm*XMb8lkA&&y zymh=Uc5o}*x(n^d?^^`ArN3{HFh-^AU`x%?50 z1DQqgem{{!2qqkbEdYtqcDY`i~n;!n(mj<(NVgB)@ zfr~^-Lk#&_fBz6bRw| zX+Eru!8u)O-{Si=!0W5~z1KdE|9qK4sQY+qtKU-&OcnAD=PH3%q45T7_x=g^n2;0) zHPFHO4|aMaDefP~TFhy+I4oM`zFoMDlje&%u7zZ~I{EP@re$hq7>6XL~sIP;c&H*L65W|g}J-zT8zLT0E$Rl zY#@4lqIZ(45bd55&MC1q1CLD;SKy=N9fKfy_C4nkcrbQZKPNirPd`6QJFnQ^ z+eW~B&)YEE$6=%v2bkcvs&de=RXw*_8kg~O1{K{E*R^|nknvuX^>YZ68xuRb z4qG#ckS&5p^ajeEI}P;T8u=Y)^~8pfu2{qYxYg!qVC+heS&apXcxzK3O?hoR9Nl}A+RYGRxAsh7 z3%LuOy=6-`jSZ*8L zA!csR21J{Ub}(r4E5HFct}CCV-Yg`NJgfIVA~0XVs)YWTkq*5_%pPe^;%J$WNw9a4B~|=G`OP6(`m?Zy?xgz#Q|LJ{1Qcy;lpuk~owayM8WJ>>>i^;~cR2 zRJeLwPpF{p1ZC>UlnL(7Ej`R&Y)yJ0;7MVD2@rrh#ZNJp9xV3@#!rS6CKjK&xwjhZ zruPIlCDAT1ZAP+mu+~1MUzZm@C9$|NdV9vT}b*-w@ zd%uY8!)+GE@27IFsUuWrE+_};Sr=U#BTZ=2p9JwvoR`^>2W5&70Y6!3;Sr=Ql zh1TjR4?mpkHsCBwPx|gX_St1g2pi>HwIMu|zfiuqUGu{I{tRc|WYL~1Cd=gF6|98& z(=VAYde7=YjP)dxy?X5rdkR@({GvUn#O-id%>6oRY)T5U7n}@UjCi>F-bd`>V4ghKdXySt3UNxe{8RjN!9=85te@kwIYmFqSDI&dGZ9 zXxld-LkXXl(i<5`3+RYZI5n4#mmCV7WBZJ&IC`h}Bk@bOm<9i_K8Fe*xiGBEBl+A1 zH76v;M#-k(7i>gOddK4-B4aRJ+nf?UNvG6+2%r3P-O`&{E!Y*+82vy=IkL?L$*Dvu9TlW|H>H)0Qrd%QR80J8gm4_(8{Q zz_+2}iACuRP&pK_3Zt*s*$q(nrcOi^){D;A6kN={R~{LuKF;DYDnWA!8P9WTqfE04 zJJw1;lsmFNmK`NL1cFDP(fqGjB?BUv>N4T0qO>P5$= zxkx?@lJ5nV(~hrIn^@dPOQch)B;uqPnvvn2pT za}&Oi(wJuoVBK{OP7A2{4D^ZHQtbnbU^rUfYiD9OK_s{*0P_il`e)A*xQ3_QMV zY3nS9y$e|%eAma${uR*hFN1Vr6VI+_y=FRbLc(S290^fFQT51s_qDB=&$?`VWg2Op z(7lfH<2q3*_<3V|?VN zw{(*PtPMoZt3gMIM6#Xy?v;A5ayeavp>M|p`^h(WKrPHcYc2Us!0HVID5i>y_rFrtpxI-sEZ=$bftk zL0e6*qXAVYZSvhS{l}gXXpLU4md7|C03gROQgG{mpCVH|P2O`8w@onRh~|?7KHJW( zIZ5G9=w;yH{&Pb(SQ;SM6e3GU4H3?X2>K;|Xs-NEl3BwmMODMXat}%{t-DUJ9o@tO zR`41uV@I*D^B|1&HAGNozn6m&OFTI{yEQN{<}=MPxls`s9V8wE8tdX&wQ|Wr-l5pA z^phydUneOz#2)(UuGh**DT?+x|GH`vCqpteHD8&4_Y{%H~FV`(>T+!%S{?23>??h2Z(o!wV@Z1@w_y3I>K zTmehcu8D%f5Xf)g*C#ON#zv&JjJEr*bXAze8O~OO4|Uvzd`BA25m= z&4J>#S%Q2hw3d{#ToG|^pm?RMj;D))(1)VL`N4{rZpuEDa>Os(v1bS)821WWJUI0c zACOU1bj$7_ScbSi%JiQ6Tu9fGxbxn27BTjwLn#p3OOfB4VvgixKwO4pRk|%2C*g@r zA_z%nx+hD(t4o#S-?uQO?VoM&V+*f4*v4_-(OkyG!gSXs=(}ID8slxPsjiemOVX2Q z_et;Lml0HgECh02yMjVy*C8UI1yy|hyInQU6@v}6s4h%D#*4BaQA?2SJ>=6$X%DyH zDZ6~L$uz~ZVF4Eda`Oi!73$(*2`r)?gGN<9DzwL@`Iqyn`Yhlom>+F+G5E<6k~}7n zz&GuS&;J5AnAZ4V0hVYA-kQ9oG)0C9odliUArw=I~Ra{I+o)p&ny3K_j zMOL-+{CI)k$CUao{i8`Y#97MN;0>{T)+7D-BX6Cw%VSRB`r0$tG}DnLmyv$DTczE) zagB6gj=3x`Us%9-DYeSdsERxX(HvX51ukIh)ze+f``<%{H5CPFxjyc=6(Lb3`Rk*d zgOuh%8B|#gm*-Id=VS>);$%y$4CThP&73rT z!#E(Fd7MFSXt?yod^Tlp0d#)dL)i_hs};F3Qy?4MTh&SUb+T`Ehi6C6#J%f%1MT1X z^U9IITS6#n;-1`_59T0qUQz2jC(H`~!zt_aN&&Ws`ph z;=s3Y%QwG2D}?O5Z8x7H0Qq=OAV1|Uz_WjVF-(vsJyYUEfg6ptTsi;`;>8&6=0FU` zUsOO32;MEeDWRXary4Pq)&I0v|NdFSDacy9N2pKdCBlT_ zHf?Qbp(T^`z3y`eRq5s53<+3#kh}oEFg#q}Y1Ci9QcP9EbL*gMih;j(pXA5oy$s!> z6*iA*QtLqv^>>stB=$=892go3EiRfy^f9E_K_s=vav6^C#CKj3W$2o)2yGF>ZV>=! z{O^o}!VXZUSp6ZEOm@!Yp{7Bq3i4v-<=g*g&WhQsw%05uZr;}b2Zpo=9FGK!SayB|-t z!sQDOBj$sUwO?e6OL~!?&(r@XfY2L7-mW4G?!}f}<>9Ksfz*{E;7SzEABR1?_DTHd zlT211XAN6E++L85paD$oIVA|aE)zy6450|qle zrh~$}Hurhkl#MF}aGQ=GK$!&+f--kvfAPe_ZQL1PyCOhr5KPSBZ(zJpc3K@@3p1+V zzqPod7XTygzvH_@GwcQ7@P2SlwR(qv#A%cPkpV#`6sYmCfe;mfE1xL%?P-|OG5F0t z47R^@CKTEBkX7-UQLt*AXg%RQve2I)Ckr?3pIsAD=KtM9jmrZtU?VA4_s7rv{w?y= zzNf1x%2B0Y@2A@5rBz65A3oJ!iet|r-?lXJVMAD zd8ARv|48I@zER+lfM1t62qAt~o+krb2C) zx$l1BUb1Pme9?l$e~cBw`p;iCP++k{=J`kY{5fFp9}_))Pm_{Pw!&4)P`x@CCiUm& zUd&9PzlKHq^@SFlNSgB3cIAK0ZToZX*I!?g9*0A~pVVEF{?G6G*8#v6G<4T z(SU=T)&;%2{hdi+BWToQOjKX1%FO09bI7dEP-TysIcIp>=!RjDPg&$zuH{$B$95HQ zH0;1Mzz+}-PUo?^s^L9$ZL#|Bhno+nPSMHbq>jB_{#j&qvInA2H()5)msRDrMj!Hn z(UC)HNmdMub2U4h??Gnr;I`VP!+^cU+fGG+G`3~<%OEI#0%1810Kr-SbcR9N1$P1} z)_R+ZbXl@1af&Z3;Q0!rPc6;tdK|6Xo-Ydm3Tb;zw&%O7qK{z`;jGRzrDA4d)XjyQ zMwVl&rtlof41HLHBtNEZ<~`>}9wB?@c)nTG+h)o}vfxSR4Ui}|euG#w?8F55v>=fl zOTSxEOI7U9+WzcWxs0OhAdp%eiC8qfQYO6c z_TxDt0F^_SbGC^pM{3K%#c>BOhgTTatJX%wlQ4aEq+R< zykj*jckEQ5VE*B@Cc7b*o4w8DpYc*%&JCYCyVe`fLHsy!cW1fnHB;r3pfjaQjo4&Eo)xdj+P)#k7Y6UrfK~P96Hli+xz|n`qup|lcF{Li0JgH;UaEH&j zv9|f9nf&^Y$8b0AN+DR!w*r*=^qod;f9U@zTzmWfniSe<2P zX89Dli^fw?C%T_h9@+JP)J4yQ7VfqceYc+aTAyyN8FuZHk{N}W_|g(?4RQ+Zuh%+M_1hZBlb-SJShk9DN|!n$87 zPHYLVgb^XW+RwcQn!g~j(L&{q|!uogF-c$5)}wmvY}?T<3Ro;~r5MwWq3 zr9oG~LzV$^k!kWRj%0wTcmY1KJkjpZ{$BUHzBkKb;s%^ELn$HrEHBFk{Ang>UtZK$ z3KQwEjmx$i8B;e9-uTkSzSG;cY6CuCA-=;r-odPVcs}k|dXCwZb?oL@ZhDPh;0)i+ z4CW-^a~SgWshfY$;llKs8mN|mXF{%pdhV$c8#1Sq&K;$it;NPIYujXwoE;O>-!7ib zbKz^KU`TwDpZv)D=&b~|rN%*c3{C~$5tu}aTo}T4Xy$rqT!kLlZmHy0y~X6NFuLBv znzJ%4Z&SAQIO;h)U|gv}rjaqTBs1pe=oA0VL&5z+Xj`P$}f&%P_p6I@RZ;gKFkCLl0r zI9WYh??ek{2=k4}H+(a08sCgI@Hm;kZ%+&bv8;1@_IPU6xTnBE@!0W8V7gshpD$a$ z=MzVl|E#CHQ9{=Rj&RRCSS`co6?57m%l{Ei4I6~7&k%_7?3 zVKXsbnQlo`#9@afoO4^|Ir0gf@_E!c z^xaq0K0_+_Lwdu2+$m}J|^_j)<9OB2Ggc^ulxp{HWXhL=WspYm=>VVYC}E6o(7Hs_C{T=Zp3P+ z(-3dSH;0p)(LeEl7|M@qT;Eu8AO826EHV>v9k*-!LK7wE+)WwB-X0}RgDV9J$M;-g^h_DaEpjKU`PQ*(6FcqPn+q>^>4e5++9k`3}45qd@%{?#|O7VyZ7WwGgOJ!Px0)0)v>%hS32xN&~c7{JK`7?`4GNY z1_7Vw(BU+nj}Q9T+!OXEI6WIrpVI9$cg{WL8!3@TADNPPmL6RwaMP7#CXl90zSt!C zC!fMLN7c@fFMly@6&^NU+f)cm=VWZqmDIa*Yh&L`%MAS_XxaRC!{XhBcXt;&*UveR zpR5|d>r-B3xOJTCzV(p%dTb?jvoK%HmFMT~Hwa;E!=n6!-Xb)_ym6;%$)Dhz+qy%*%_GQMkoh>QK2g+^l_lbH2Ysj3r8S4Z>q^TW&px8!oun z6v6y@!()4nF_l2h8c1nIvZDl#qxD^wr#zwsQqPNMIm2E~F3Xm6e$~C+L?|M12wQf` zISP;PF<*7*2^mE*wNo-Swb?n+Qy0`4@Xj=$+Ib(01tSjdJ0x{YxmaTL+N2#qC1cu5 z+4Oh~G4*2g9m;B>r381r=!IFxYjnd&=19$yrZrY#K{HM*rT4X&YV(UR6eY=~g&BW} zlcveRt(J&av??IFToOi-=21NVK8S?)NQGtT)JrBKO@Hk8gS9Wf#p<+R*I}d6Ob}_msXxbc>k;!2f9=yq zjay{T<1HRaCb|8J`>)f}@dJvxw;tkdgchGjB#+;=qdP@PY;<{ben3F}XV%1r>UEQ7 zy_ZH|&xw!4)xH*6|B$67U*jxAqXB1!#)`8Y@6!FH2^aVM-F2LKash`~rBANI-bb&4 zOvFxQQ>O`Uc<`0VTh}8a+zJMGn?E7B!Xy4@p%1ofJj~vlfyI}u;`CN`WPCeWGb-lt zBW#nfmCM;tn@_N96sj?e4E~8@{Zn1a(>!HpMPUvg_ZemmWDL=}V`!mA_kIe@F=ikOPO?s$qvT2;gmd&&D)&#i!1B}3aJ)GdsYyOlLmc2BrS)CiN z$kfkhbbGPFs92+9%K3K}uX<0;K-5D7XNx;GFq&5mvpxDQFQmZAtx(w%Vs~ zUR<4OnN{R%)@{+pv0(We>cYYy(T(LMWAxU&_J10C>#(ZYtzQ@x-CYu*bV#Wn(kMzd zEI^QMP(Zp_}4;eG3Pz+dyHR= z(JsFhp;^x!(LD8TEe`CgEus##XW=aJ_WT7#SF+#pL!wOKnV zIV7YziDUT@YR1=@`Tk$8y<76Ts%*S`{TjCQdn6zV$6O{D@{4K+lxgHP{?TicqzL~@ zL^$y{(2cV5JsN!}3WPd)rQCm+KTC_ z!8YU+erc79;c5Up8a=r(A!a@Eeuwj=qxK@{f?~~@>$sHbEeU@71L19$JhNcn#XD`Q z0-V%Rlzdg9dBVsBYhn?ut=QTJ?bil6&w`9x2HA#c#BBS>XLg~>BjBudbtr%(qRQa8 z7_RP zjKyhre@J8`reaiJndFk9YW&we!fhClTr`5;K5J0YbCE($n1=Jla7SjTt>{89Z`)eNJ&fCnM#ttfTU^GfHa9u9 zXkS>%Xmm=MguEAZou)S&Hetw`q0F21?0_zCKZo|0m!L6?8A_C%l&s?LY+Ga0xPy|D zKn!Am;by%fXem{cIrhDJex5;=mrg*qTd&lg<59yK(Qdcd)r+_99Z#e=4S|&6(R)L< z$3`P#>yKa0|Sq<64dvumxoch zsw<^)Nzg15G|PsiyjBZ9NxpMALs)rrucBFn6)8byRfcm4wkZ~3vCXOieZh}19KNAH zR?GzM3@<-kBI`KF9zqb`CnU(rJsZtZ7+cFVxaCq)(mfyhPo-=o zD!s{4n9HZ~6RA+%)L$r{KA-m|>^nX{HjRycc6S$B8p7SGGc1z2IJ(pPGz^Qfu;UsZoi;tV^ST)|Z=-hjqH#ip=kK zeN5El%#)y1>cDhTHVpT`)8nvYxhv-@wQ~I$64{+|0A8^N7v(Q73DHL&KT2KZUh5Q& zijy0aloq+0i95zzG$xc3m?b2&jwaX0Sf{T)Ui1dwE0HX4Vae5xM_KtVXu_i3@p1p~ zXBdR~Q-v$->nU=g$)1a6>Aj{}hueZ|;3Kg<1X>=kZ-I1B{V-n(rWt^S@k3LM!RlsA znSb@5?z3II-6uw`ybspbdSm=i*E2NgqJ3SO?oF4B;kNW5z(?<6~2ISL8xS9L*3gK#b*t*^HO zLZiOhIf8wk09U*chvoEOgX1a5<%(ofWD<3{t4w-c$r@Eh~3a}53GCVh^3^dFws-E{s z&(toxV6-N=yu~;49X=cN@`EIlHSkzN*$B6~*xTwZ)C*`I~<& z`h2DwrXNqEZ*w{tvW_hFweax%S_t^#7lckp$kegQM$V4x)Z}uAa#6!Iz_B7?!M%yC zE--~L=?llr{juedG&w{?8k2JOtVaNmxH^BTBu!&U!`n?3NHTf_D4za7 z9stXwf&1y0y`BQkRh`eiop@0v3-Pki!zwZFk9}|L#o;L5!5HxiTZw?RhyN|?8HiMi1_z3Nfgq|AU&Gk4 zR5ecM+tTU^w^`!E&4SD9GmiXVUgCs1VXA#t_1lF{F5p7$g#y;BM+VQnUsF~*9WamH zl_f<}ih@$x>2!+XUpOu|_^jFdBI(8)kW zuW`^xblEILFvTCy1H!Z__}&%yPn}rzM?7 zNRwD~vhnd%oXf&$TG}jroHSpKwW;kx_i0P^z5|H9`E-~tAF%l51F`<%%>0Y_hf-yF zoNWRhNJqyR#oPsSGl8k3HeNOB?nU^<>jH`#TQ(JOH@`Y< z#dqO8u4xzP!W*)|>=p?PEo*c87` zS=?aqPsotZRmZD(OtqfzZuF~*TYwuI2Vr6OWq-ZqF!wnEG#KGpis z)Jk+t>5a0~pR-yrBv{pI8LZIzUm4kUxp2R*FBGWB5cz|*vg}12HWbN`U2Hg%$IF|r zbB{14kC~+jlakLtO_~@-#~XCozM2z zXP@dH0Wg?0{Y*jIhDXbplymUAe$icUpmTETmKXOd(qZw4>0x21Om8XK^h;grk~l+; zU&ihSm<*zNC|!{QUGMSorH>T~rqlW`L_9s@1N9v+HbhRxQh72trwDv5nlCYvZOpOc z`7+N0n{2vYLvOZwPL@I@V348Nvo)>(-%~jX;YuV6pr^M>!dS%oJK@BL>pqLZroB;A zL(gBDc0bLBDrd-u)~W@DCOq^7zD#j9xje&jZ3a%VPtm4AWv{eUMdb!n*h6L&a@BD= zDkZRQ0NW#XU>ZlahI`@NiA56uior5}1dSYj>rQY74z*;Wzz^K$-lNb9QTxxlH^sQ7 z!)XwUOaW)RyK>E{2(0^oer1cQwm&djP&Xz7BXD`3P0HM4F-c953}yL{He{LWPT=y!Uh(w8XtA*u~zwiM3yhaapT_+zGfgMD^o@ zetvLusMXm|s`gfgEhAfu?#0}kjlzG_n9trshbqL&Q-OKgNZ}%E$Ma zFT4UzrIO`6nV5B1<~FV_o!JsRI^`4?yfk^W;+h`&Ql>XF)pkM$5b3a4PIv9Aeh}VF z)z~T2>AeaZFAB2pJgx7%^?bUdAL4PJGYT{^rR%i|G=ef1yQ!(X`d6_DX>-4D928^; zJo|e68sTf+Zz=q5l>v_yy~=Ek=2hcUCbcFa>BzI9GPwd%VO~+}DP32uHRd)T|BW+` zTI(lWLh|fd#J)Z^?Ir~JE1OTwhiKYp1>Nb-_WBfwxrE3 z4v+hO7*nkftkvC$v&-SNmsxB~E!NjtFic(#ygyGW7hnbMu=<`Qe06 z?`4rXyH7)PJ^--owlrtoL1g&yOuc)5H};&#=8&h|vZQ!_Ol^f#1Vu4@;NM|fLFOz7 zuU$+{a8TVZ%F1%v8kD)KM(p(5k&{0~mA(BT?&(M}`VWbcDovNwO&>L4O*@UVcfe$i zKB>7gxZ-8i*tzW;yO6r(fzyX+Azqv=NopMaFfBcCM#9k1X| zvD3}kB-8J|8Ax$|iFdfU&e*>40@VGNqb*dlBVMUnyW%BBHx4yy^&kBn^n?rV95*>x zti95|+1*0*Tj4NLaJKsD*Za?1-n5?cyv}HaT)gKlO_1w1<+a;Jb8|O?Iwh;c#WjGV z(hAP%XPArbtU8L+xHom^+Wjh4jt1SEe-^)tC9e{?bki<5J_H9(2E(){`bdet{miBB z+d{)}#jts9`5BzcR6a}srFI@4h5m8zsTeh`Hp`bRQ{YR%-JWgGK7>Pw=UnX zQSTCYX55@R>*Ux)L|sm7>PGp;u+LvFvl~cUQc4%i1TrF?)`xGsxgHaxqmw9gL2;CE z?s-2nOs7bJJK>WU+sc3`KNX>*RtB%JH?GAmFKV?b0}>K#F>fEX`}C98{sK$-Ua31C1L1iN9Zu2M}ME zK>Q1O%U~iIPLh`V|H@82y(KZcN85`HxI^Ym+0xEfS5#sk_q3obpN*EHxJD<4^S^6^C{gbq>VljZ?SH>u@@gQ#+K)*O!TewUvMAI$kPE;g zg`(jR0#*C{m?%lQwNYN%1EB|shn+q3{tz&|-sHXi;|fTV$QskiSV?20^M0q~_@5|b zq3G1gku1^c43yv4gKiY#h?&aW9mo4=;)jV291xdp%`inaI);enCNH_Ag$)I zO_z{Q8iC%l7uFdMU)zxxYJ4+Fu4MVDhC%V!cAUGG`+}c`224BkNvd5n4mj5_L8B{y zZ#Mh>Ldie77lmQ%(7ylXynKZJwi0w4hJ>^N(wl%-Eko%_O?2r-hv{`u)T2ztk_`l;$fj;N~q8CFPHbd%6`2hYx=VdiOqR4=GJ`s{+hg_ zl&4t`3HkT?&><0&cv z!%oaopMkvN9xXKq9d&%R_;T%%M9;;V(xNCv??V6UW~|qRkKZYn@42MDa7VO zepukA@i3Z)oR9*-1fxdS$kQeL-gMNGoL7S9~49R_nuu+jPzJ5OpC%Ipk}33 zu7PuiuV}$>u1SzR?c5iHD<924ax?<-V^R34B9p)MyXUKE*LE6Yw>f%ul<|CPG7|oA zS(t@jkzajj-Z`#_Mty&`qGzk=YwWo9ExFk(Djwa4*+(rZjDG+m=70he8P6+_4iSqc zZVt4!;x30?`-wxu!}tIljR5!jRh%O%3`EodN?HN60C(Sz`uC;BE{NVSq)Dd;gn8aH zU4$e`N?1BRF3fWj5onZcP2XKNr!=I!UJ}hZ+AHiE>8XPYInoRm)u5H4p_M~l)zm+b z96=2!Uu5ccugnMhIk%J0zi^$6&dUju$&=C=e__4F@gMuvUwha<7Cd1Y*9$@s%z@MI zV4f>)APZOaohA@nPMBOZ6fp8XUd8fTg!}%s1~+iyD&ZbA5YYRes(*JGyT1Kood-3O zR-@KR#cire8Ff<7E%Iqo^Tn0vo#6ATqvRxE#MB%x7}g`Qy4V5&26dXL-va3O$@$I) zF|!`rYM5QSoAc-FzBKwTU?cGLxNO;ywqCNK!$GH`*hx6)As>QGB=lXcwm zHkU>vRw~Os{t|f=*j6Tdn;JXtI)0|AqI#W&E041&^jbZuk6_uoHkgL-ky$-mTncw1 z$i{&S*I_G1keZj!l4F-aL|w&Ph96*H%M)5^I^pL)YexguQ%6f`;TzNY840*vq1jnS z@HaSB2h)wK@dL&BP%0Tl$+<6ARCQ42s0Gvq<4iti1uEYEu^P<_vO66+#^ zByT01EwLOOSoh@A7t zZ<(u)sAK7O3*%9f8GS_3l1Mol+Jc*IM;wQQdRoAi`h;9)E-MCYZs^KWdh+u2#gi?F zYFAA)V*Zx9;b}d{-F+O2*ofwDQ`etqaOBy)nO&|N|I+|FhfBsrkmT=sXuNbaMXqMa zRAt~*DStiL{p|%~jSiD@09Jp+4giyeX?Q-C@-gQXwzEWbz=c;mDDw!tsqd6NXsocA zC{DPo-SmRH&lMWa=-Y1V#dpG2F}3Uvy)#JINQAJk@gqQya_uJTmI||gNe3GGWRkp| znh5o|I#LQrpW9khi}o{w`O{=|9GYY71$Mqef7=~bmC)K~HdcKY02{xq%Qv{|Yy89p@y%+s3|2RJPD5yrA2QTjSI_GW| z0Y9#v)Ihukw&ga+P-@22`Z!9T8Z57lGVlJ}}`ErVpVMo|dXHuak zfG8zMO%mQ+dzDk_Vi2^~O(Io%Wgq&uAf{8Ck4;h5a(3c|-UzC_@BK_wi#d zV|dBqqLCc>%X~x)JE=i++g75VWXBrp>1^m{eoQuih(Hd!S<04SLgX=S_(M15k0{(s@`5>z3y}=XY%oUn?L;x z8)lPtv^kna?M?2ecA)L}8$CMGpot`x=*L%63JYd`Lh>z^stNq0{QZ=wv;5yFu$>t} zNW>jxVKsOlKbgX&sW4BY2C&V?O8A;6Djx1;Fx6=veiayrlBjIs55X{_0vwo+KYsC; zu#A;;&Vx*g9 zOB^*778QDB2q{XWHBRBUps6*g9OzV1BbKIbHn@;H-TXR;&htX$ToLJ>?aGlHPh+{l z+{|HZ?&Bgo=h2=TZA^DV;vS#wR@><#HSR(3Avi5RC1ErP9s?zBxwqsQPRzKzGlDB}M7OyKyc(7Q=eLta#Q zcPU)>?oXZ*f1hq55l&&v-J-34MPUe~ar4aBr`k6fm~m{J7dJFl?!-L~{5E?vPT=D=kL7XJKR8{<*@dmhv;>{9PbT(#)6*gvKSoMXD}W+ZYsr~dP-3W7bZsSnjcM;I)s97$reXmI^ zX z6!ex>_3%dQ=%}N>pNkyUjAVs_qKx<0LFcL0UEmB0%!wrkt?{@Z0u!rYY|SokCMA{^N@~&% z2ClwpU{*5b_7P?B{&7XnW-K=yf9&#(xJyd#Z;FY2>nNV}{ ztHO_e)IZ^V*7pEUo+hLZaff>`rZtzKiu#U^h4Qmxx%@gKf*3o$gjn0;UzM{30X#aT z1+m6&bSE+u5BMuc<~dA=aCorK!|hccjIN1m$}A72YYO(yfkvDSW74!$E{-AjcLDwF z?vXa8bKVqYT59rQl07HmDvI)x0v@sYKOn9VXkqf(*Vu-&d}vZ{!%=s?k@29H!?F71 zwG@zQi+WI-445_zruG+Sdj3GX^n&XlO2 z3@V>{v&*_~f=BRUb5j+Ygn_i6abx;GQ`mxhqPoh8Z|z7hqjse1{4^{0?|n!hrlKgi z0r#K=0XsPwdJktJMhiZ+RfE*8k?Xddkd?0_Zy7ZNs8~Mm3PsA$0`;nh%olvlfyyFC zPrRWpnBkVNS2Z)vJIr!-lO6&D6dF zr61l(m|J)(%g4sY#+DLTWt^M$=Qv)Fj^fRdwqsUn6wI>L6F8zc_6eb7v7m$(q=rTh zOl){ad;;EBIyx@XlnwFG7yfkjq8Z**Hp&WM?8etM3rjWe3)JH}*@l7Hf}fvgE|rzz znJSp}Ty(CJ;7uL?(UzZjfbwR1))QAi zdU@A&)bq}pvGw4z>~s<6IIvmVWysUEg+pwphOAv)^9palo~TYj@@epXio=f5G-u8W zO;HZXvzf5-%N5 zZLT-dARf(fVcJ8X)aO-KLsK%NDGPFPnIyFg$BGF5(c}loGJ~P$N|qc`$aRE}YC>QO zcR|b68=lvOVjYcjf$AZXv>l%VpuNx*LYaQ5A=doLHD*lg{)7UZc+H+xXb5Btwu>D# zF7m!W%T}E4q=qx;_s0K>nBkVX^oI~k5fgvBp{iI$vA)eN=}e}&?!{(_sp9L3O1#%2 z;>^#%QS^!Lfxbn{n{L&GLjjtf??jz3}alq4%=)Je$G2e0g!uP>i%bh=28UMjd& z$VM_J+*O}-jx!kr_M~dfl^8}b-JTqe2O8!rJ+*Okv-SD=gD`Q$}AFMKEmk1 zmzBm%Hnk}Of*IvhoC=pj9+fx96Y$upZC;&w)Wuk&TO|D(Dx=GpTq_3gZHYwwrq3f! zU)SxCHw>#JkJeTG@JHx}O(C5Or{$gIi$8+@=)i2GG6uu%l~#Hu|k(XH9oykZJ{jv#)KTm1*A(ZX0YX*($LOF zFHBMpa>?h`4@6;ngaTJ`Mbk3A4km^^c#~h*%+!@cy$Vg|FTsA{Cb8R2dvs@xlvNLH z`e4J-@ib&Qq3m1u17o5%{TE#T(9xa4>VNV1lXa1Xc0Hyuzh)>TiM-_D>z+H zT`^4MM6cfEeWfqE<6#nwW#(nAI?<26D&u?rSJ!x+GHGdY%@e-6|EzQT{+ru3qCOm9 z=+#(6&V)9}>^k?kK3uC%{n6>-_iWwIzkMLiomn9*L6#>j0Gwv#j-lz6RpY;u)GNB| zYpS$XYu*gk zeA%nnpi=8qEE(JU+l*DiYs}f>;HebRk}$iZkXB{Kcz6s0L!bL;&;;*ONNVMegNp0# z1znIVt9lVBNmTz`Lit~$OH8T8>F_n0G7%dqJcPc&!$AhRErpv1)~Nr!52%QK>i%9xMr!lOkF33 zBi!AaqgtgKEGEP`YBiFcH-kNCdP|pe_PIhD-X4KH??lqj3G>GW9NYVE#Ql{u*ijdm zo*_MS?E6fE^6uCCO0CmN2g9!#JF&iiXiBXff1@4X8r;*9dZUFkArkSOKo_ZI$i{ff zwO1L_TW~^Ho|a!E;MaSzJ=an7vzoeOQd|Bh-bz(vplnsBHSX4|lUTR{84z_;Rm^o1 z**Y__KR~O9Ywm#u1-WTKVIYa1ZP4 zB=h6=^Jrvy;-KEwOi7WDA=;8A>>qofg+X!BcSS=Y%qBRRHk;Kn^JmCFgQhq z`9ifO*>*N*kf?S(j%&!;`@&6}+sT9+@%_^0`w=17O3dy4AwN$pHB^4kJ~Ls&;*o8N z39VZ7Erx#+r%-3o1A~f{LlZ)f;(I|~y#|hbPU!7e1Q2dH9${)sj0$#S) zC+}9%jQJ#wS19tn60&inx;`00?HUU7RE;e9QZfz7CsA7*@!f3*{7!PSbD4z5CaZK( zG%VG#P`u3V(CsoQJB1oH)MpsA0=N3Z|SZaDWA?%27JbCxxer98sjAywP?>f$#dybz&? zXO!%wx&nqaotN-2mQlVC?hAl7dYd}Oh4Qj?l~BaouJNMmunww~kK`{j`g}a-4|UHc zs~*Q1Yt74mve`M2i{)j7$bEuIb=nU((@B%q6~{3)y_rYU?iInVQPTL@xcwo&)js|2 zvyGuTA(ba%v(X1g<0lbHzropYYzEHv*y>Bg#_es7ncAPxmfs7Wy+>GMf2^KDz3iL0MXF+SAYG*2(yskAsUf#WHz8vfZ(&FR&Mryu6_< z<>`wB=cI%xI6v?{ZVQt z_t|@6*&;;ai?py+*eW_7{g0#Z@8-Qm}IZ4lH~MxB!9oPqBvQ_Kfy7|>nOhAc=Oj(5_yMo zNdsv|u7Ket2oJOAq41o+67k}A;hKmVQ4vLtDMwjOV?u`#027SN^h?w8(o{Ku?PM9W zJ4l%ovXE6+T{0lgy*&5LhFEVBHJG_C$_}ja zcE7;pT(tc0hq!5gGlP5;(}!KxGYgCH-ymJ)NgU*=60vkFi+vCi#x&e;>zVD@9-knIiG4Kl7D1~kYALyr zhOr>%Hs|_LrOk4M@Ua>ifJ27zpJ7=Kfapuwrqk=X*F832$VkeC1X*o`@PsU;h)B~3f~85 z1qJsY*!t1XfXrIDpSK)wl+SqA;7Zb?#(lnDJUGK|p~XxWFP`LkIeXH=cU&zU2Y+D!Brx7w<>l#L_1NPhmS$5JYjTj++WMt+#QSG6zWSIX$KHN4!%ijC#p zE=^22*BI|}yx1`uJ1*6Iy{@oY$YJFF+|iTWA5=8ATaN2$MJOm9@{X48H;ce zqylucQNB*IwFqkL3f@E$1x))Km|#`b#O2g$$E%<91XN{kArM83aStX z!@m_hwE+E}XChxnX$+l$s-4E@1SsfJNg$14JTugf*WH&kX(@of=J0`#t_moc!ra}$ zjGT7aaFVfj>i}M$Hy$asr}3N$+^Z@WVqKbY8>i;=Hs7*nuOIvS1EmvGQ6{AT})%3ol6npo#n=w%+%J76g2OPpfUE< z(4Uxg`FT>O6aV(|u+uZFLu2_x;^CJ^+7*VPmMNYc=s4*{Rh(k`V|TE&h^kcYMzuez zQfZWFY3>nzVR9}1HK)my@HT4pur(fyqr7X-aY?bqXDS#6r4+}0x40f?ShR>a#&Iu} zp=7SKYyHNBp=o>8KyG%D=5#)?abb* z-tLp;771AZL6l_CT}0Ej4}WrTdv0F3X%&5qE@Z)p1&?hVw&2IxQTk7#?;55Zg=vy+ z2|jtz^*e?0Z0~g?^HWB{*sGmXs9=dtLVqmpq46;yD<8h5bXU(+Bq+d?)AA*9LdOfE z7?R;4VB?2MHRHJ&mxGkd?6LCTtLp;||Ew{SS>f1FOcdU@ican%i}smp{90}z*2c3V zS`k(5!RuE*z(;PK<^0r+s$AEkvoF~X5n;|Y?^F_1q>{-Fvht-4bF97RC{lS@~5{ESlQ zqr*2S*Z%&oAFnaQpKnU>pmWvv>k>6F-e2PAw_WXg6e_shw5)A z$iNv2yYJJ}CQtTKq^l30W7<1Vv}pNzVU+7intR$Z?wmcP2(rmrhzR3tdM z16j1;L=>sU?A#$oMyV*a-CC=eb!AFI>o;ykmnDktr@CP?CJn-Y+6nGtCuZPf{m*vq ze<;!7C{(xmkdH;B_znM?tU3_3Y8}C&$+<|(U!ZIJ+j?VVkya|SuxNULDW9uNN(&xF z+S{(h#p-#%sLr<-Zf1#%NBq5IPe2lQfzRe6h3Y*=QYK0;4g*o;-gz1OIRP7$i}BiA zb1f=0zZe==R0#~?1GBs=VW#_d6r3ymUDk?t#>(aYsO9mKp(w@jHuKVBRaeG@Av?^% zkH3@~8pC}rHz7WUiSCpD*V3T4At(s0rGH%xT8Wju@|%kD&(_<+hf7}BKgJ8Ai$OKC zg;x75m{~pI9FybeXmW6qxEJaF1+Ri$B>oA&5UXdDRp@{J%)iAX|9%nw2a{Hd>(40J z16ZB^{@?%Mg(wFs|MLeNqjV)_QuiIM{QXY+?Z&`|D=6AqR0_-CA9wA) se61908hd{js>A;CxBd6ma3TKSNKPLj?4=(RqrpD~In}!rvL?a*2TQ!zI{*Lx literal 0 HcmV?d00001 diff --git a/doc/mesh-mesh-collision-call.pdf b/doc/mesh-mesh-collision-call.pdf deleted file mode 100644 index 3c338d04dfb8a5244d8e26a9c43aa238a9e59eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24297 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

>2TZjkNB}k(f#o%W0GaINd1|qS5v<1tZm8{@ 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 aWtEGoDa7?Ix^S`|2!QqYv9yW|*8c(ixuC58 diff --git a/doc/python/doxygen_xml_parser.py b/doc/python/doxygen_xml_parser.py index 3c5b0e3..d9590b7 100755 --- a/doc/python/doxygen_xml_parser.py +++ b/doc/python/doxygen_xml_parser.py @@ -1,4 +1,5 @@ #!/usr/bin/python3 +# ruff: noqa: E501 from __future__ import print_function from lxml import etree @@ -87,16 +88,18 @@ def _templateParamToDict(param): - type = param.find("type") + type_ = param.find("type") declname = param.find("declname") defname = param.find("defname") # FIXME type may contain references in two ways: # - the real param type # - the name of the template argument is recognized as the name of a type... if defname is None and declname is None: - typetext = type.text - for c in type.iter(): - if c == type: + typetext = type_.text + if typetext is None: + typetext = "" + for c in type_.iter(): + if c == type_: continue if c.text is not None: typetext += c.text @@ -111,15 +114,13 @@ def _templateParamToDict(param): assert len(s) == 2 return {"type": s[0].strip(), "name": s[1].strip()} else: - return {"type": type.text, "name": ""} + return {"type": type_.text, "name": ""} else: assert defname.text == declname.text - return {"type": type.text, "name": defname.text} + return {"type": type_.text, "name": defname.text} def makeHeaderGuard(filename): - import os - return filename.upper().replace(".", "_").replace("/", "_").replace("-", "_") @@ -324,8 +325,6 @@ def getdeclname(i, declname): return ", ".join([arg.format(n) for n in argnames]) def include(self): - import os.path - loc = self.xml.find("location") # The location is based on $CMAKE_SOURCE_DIR. Remove first directory. return loc.attrib["file"].split("/", 1)[1] @@ -430,19 +429,23 @@ def __init__(self, *args): self.typedef[memberdef.find("name").text.strip()] = True elif memberdef.attrib["kind"] == "enum": + if memberdef.find("name").text is None: + ref_name = self._className() + "::" + "anonymous_enum" + else: + ref_name = self._className() + "::" + memberdef.find("name").text ref = Reference( index=self.index, id=memberdef.attrib["id"], - name=self._className() + "::" + memberdef.find("name").text, + name=ref_name, ) self.index.registerReference(ref) for value in memberdef.iterchildren("enumvalue"): - ref = Reference( + value_ref = Reference( index=self.index, id=value.attrib["id"], - name=self._className() + "::" + memberdef.find("name").text, + name=ref.name, ) - self.index.registerReference(ref) + self.index.registerReference(value_ref) elif memberdef.attrib["kind"] == "function": self._memberfunc(memberdef) @@ -699,8 +702,6 @@ def parseCompound(self): def write(self): # Header - from os.path import abspath, dirname - from time import asctime self.output.open("doxygen_xml_parser_for_cmake.hh") # self.output.out ("// Generated on {}".format (asctime())) @@ -815,7 +816,7 @@ def __init__(self, output_dir, warn, error, errorPrefix=""): self._created_files = dict() def open(self, name): - assert self._out == None, "You did not close the previous file" + assert self._out is None, "You did not close the previous file" import os fullname = os.path.join(self.output_dir, name) @@ -889,4 +890,4 @@ def err(self, *args): index.parseCompound() index.write() index.output.writeFooterAndCloseFiles() - assert index.output._out == None + assert index.output._out is None diff --git a/doc/python/xml_docstring.py b/doc/python/xml_docstring.py index 0b1fd09..d36618b 100644 --- a/doc/python/xml_docstring.py +++ b/doc/python/xml_docstring.py @@ -59,14 +59,14 @@ def _newline(self, n=1): def _clean(self): s = 0 - for l in self.lines: - if len(l.strip()) == 0: + for line in self.lines: + if len(line.strip()) == 0: s += 1 else: break e = len(self.lines) - for l in reversed(self.lines): - if len(l.strip()) == 0: + for line in reversed(self.lines): + if len(line.strip()) == 0: e -= 1 else: break @@ -80,7 +80,7 @@ def getDocString(self, brief, detailled, output): if brief is not None: self._newline() self.visit(detailled) - from sys import stdout, stderr, version_info + from sys import version_info self.writeErrors(output) self._clean() diff --git a/doc/shape-mesh-collision-call.pdf b/doc/shape-mesh-collision-call.pdf deleted file mode 100644 index 39c820ffe10b1f5688447fc674c9e78cb3bae5a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26183 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%0AD)7P#DW!}@khhgy#-`oXe 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 DB}xYk diff --git a/doc/shape-shape-collision-call.pdf b/doc/shape-shape-collision-call.pdf deleted file mode 100644 index 9bfcedaf07d5f3293cbb847efa22e8da13747957..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22270 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 d$N4`DJ0D*=Z(n@xf collisionGeometry() const { return cgeom; } - /// @brief get geometry from the object instance + /// @brief get shared pointer to collision geometry of the object instance const shared_ptr& collisionGeometry() { return cgeom; } + /// @brief get raw pointer to collision geometry of the object instance + const CollisionGeometry* collisionGeometryPtr() const { return cgeom.get(); } + + /// @brief get raw pointer to collision geometry of the object instance + CollisionGeometry* collisionGeometryPtr() { return cgeom.get(); } + /// @brief Associate a new CollisionGeometry /// /// @param[in] collision_geometry The new CollisionGeometry diff --git a/include/hpp/fcl/internal/tools.h b/include/hpp/fcl/internal/tools.h index 4e1a342..52a2eca 100644 --- a/include/hpp/fcl/internal/tools.h +++ b/include/hpp/fcl/internal/tools.h @@ -35,11 +35,10 @@ /** \author Joseph Mirabel */ -#ifndef HPP_FCL_MATH_TOOLS_H -#define HPP_FCL_MATH_TOOLS_H +#ifndef HPP_FCL_INTERNAL_TOOLS_H +#define HPP_FCL_INTERNAL_TOOLS_H -#include -#include +#include #include #include @@ -110,6 +109,7 @@ void eigen(const Eigen::MatrixBase& m, int j, iq, ip, i; Scalar tresh, theta, tau, t, sm, s, h, g, c; int nrot; + HPP_FCL_UNUSED_VARIABLE(nrot); Scalar b[3]; Scalar z[3]; Scalar v[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; diff --git a/include/hpp/fcl/narrowphase/narrowphase.h b/include/hpp/fcl/narrowphase/narrowphase.h index 9abdcd9..519f3ae 100644 --- a/include/hpp/fcl/narrowphase/narrowphase.h +++ b/include/hpp/fcl/narrowphase/narrowphase.h @@ -235,6 +235,7 @@ struct HPP_FCL_DLLAPI GJKSolver { } break; case details::GJK::Valid: + case details::GJK::EarlyStopped: case details::GJK::Failed: col = false; @@ -250,7 +251,7 @@ struct HPP_FCL_DLLAPI GJKSolver { break; default: assert(false && "should not reach type part."); - return true; + throw std::logic_error("GJKSolver: should not reach this part."); } return col; } diff --git a/include/hpp/fcl/timings.h b/include/hpp/fcl/timings.h index 0a67e70..961a267 100644 --- a/include/hpp/fcl/timings.h +++ b/include/hpp/fcl/timings.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2021 INRIA +// Copyright (c) 2021-2023 INRIA // #ifndef HPP_FCL_TIMINGS_FWD_H @@ -30,7 +30,18 @@ struct CPUTimes { /// Importantly, this class will only have an effect for C++11 and more. /// struct HPP_FCL_DLLAPI Timer { - Timer() : m_is_stopped(true) { start(); } +#ifdef HPP_FCL_WITH_CXX11_SUPPORT + typedef std::chrono::steady_clock clock_type; + typedef clock_type::duration duration_type; +#endif + + /// \brief Default constructor for the timer + /// + /// \param[in] start_on_construction if true, the timer will be run just after + /// the object is created + Timer(const bool start_on_construction = true) : m_is_stopped(true) { + if (start_on_construction) Timer::start(); + } CPUTimes elapsed() const { if (m_is_stopped) return m_times; @@ -48,6 +59,10 @@ struct HPP_FCL_DLLAPI Timer { return current; } +#ifdef HPP_FCL_WITH_CXX11_SUPPORT + duration_type duration() const { return (m_end - m_start); } +#endif + void start() { if (m_is_stopped) { m_is_stopped = false; @@ -75,7 +90,10 @@ struct HPP_FCL_DLLAPI Timer { void resume() { #ifdef HPP_FCL_WITH_CXX11_SUPPORT - if (m_is_stopped) m_start = std::chrono::steady_clock::now(); + if (m_is_stopped) { + m_start = std::chrono::steady_clock::now(); + m_is_stopped = false; + } #endif } diff --git a/package.xml b/package.xml index 34a7fce..7377667 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ hpp-fcl - 2.3.0 + 2.4.0 An extension of the Flexible Collision Library. diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 88f9589..12f7e85 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,7 +1,7 @@ # # Software License Agreement (BSD License) # -# Copyright (c) 2019-2022 CNRS-LAAS INRIA +# Copyright (c) 2019-2023 CNRS-LAAS INRIA # Author: Joseph Mirabel # All rights reserved. # @@ -32,6 +32,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +include(${PROJECT_SOURCE_DIR}/cmake/python-helpers.cmake) include(${PROJECT_SOURCE_DIR}/cmake/stubs.cmake) ADD_CUSTOM_TARGET(python) @@ -113,7 +114,7 @@ IF(HPP_FCL_HAS_OCTOMAP) LIST(APPEND ${LIBRARY_NAME}_SOURCES octree.cc) ENDIF(HPP_FCL_HAS_OCTOMAP) -ADD_LIBRARY(${LIBRARY_NAME} SHARED ${${LIBRARY_NAME}_SOURCES} ${${LIBRARY_NAME}_HEADERS}) +ADD_LIBRARY(${LIBRARY_NAME} MODULE ${${LIBRARY_NAME}_SOURCES} ${${LIBRARY_NAME}_HEADERS}) TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} SYSTEM PRIVATE ${PYTHON_INCLUDE_DIRS}) TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/src" "${CMAKE_CURRENT_BINARY_DIR}") IF(WIN32) @@ -141,16 +142,16 @@ SET_TARGET_PROPERTIES(${LIBRARY_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python/${LIBRARY_NAME}" ) -IF(UNIX AND NOT APPLE) - SET_TARGET_PROPERTIES(${LIBRARY_NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/../../..") -ENDIF() - IF(IS_ABSOLUTE ${PYTHON_SITELIB}) SET(ABSOLUTE_PYTHON_SITELIB ${PYTHON_SITELIB}) ELSE() SET(ABSOLUTE_PYTHON_SITELIB ${CMAKE_INSTALL_PREFIX}/${PYTHON_SITELIB}) ENDIF() SET(${LIBRARY_NAME}_INSTALL_DIR ${ABSOLUTE_PYTHON_SITELIB}/${LIBRARY_NAME}) +IF(UNIX) + GET_RELATIVE_RPATH(${${LIBRARY_NAME}_INSTALL_DIR} ${LIBRARY_NAME}_INSTALL_RPATH) + SET_TARGET_PROPERTIES(${LIBRARY_NAME} PROPERTIES INSTALL_RPATH "${${LIBRARY_NAME}_INSTALL_RPATH}") +ENDIF() INSTALL(TARGETS ${LIBRARY_NAME} DESTINATION ${${LIBRARY_NAME}_INSTALL_DIR}) @@ -158,7 +159,7 @@ INSTALL(TARGETS ${LIBRARY_NAME} # --- GENERATE STUBS IF(GENERATE_PYTHON_STUBS) LOAD_STUBGEN() - GENERATE_STUBS(${CMAKE_CURRENT_BINARY_DIR} ${LIBRARY_NAME} ${ABSOLUTE_PYTHON_SITELIB}) + GENERATE_STUBS(${CMAKE_CURRENT_BINARY_DIR} ${LIBRARY_NAME} ${ABSOLUTE_PYTHON_SITELIB} ${LIBRARY_NAME} ${PROJECT_NAME}) ENDIF(GENERATE_PYTHON_STUBS) # --- INSTALL SCRIPTS diff --git a/python/broadphase/broadphase_callbacks.hh b/python/broadphase/broadphase_callbacks.hh index 8f492e4..b6ed07d 100644 --- a/python/broadphase/broadphase_callbacks.hh +++ b/python/broadphase/broadphase_callbacks.hh @@ -100,8 +100,8 @@ struct DistanceCallBackBaseWrapper : DistanceCallBackBase, .def("distance", bp::pure_virtual( static_cast & dist)>(&Self::distance)), + CollisionObject* o1, CollisionObject* o2, + Eigen::Matrix& dist)>(&Self::distance)), doxygen::member_func_doc(&Base::distance)) .def("__call__", &Base::operator(), doxygen::member_func_doc(&Base::operator())); diff --git a/python/broadphase/broadphase_collision_manager.hh b/python/broadphase/broadphase_collision_manager.hh index 045c2d5..796dbd4 100644 --- a/python/broadphase/broadphase_collision_manager.hh +++ b/python/broadphase/broadphase_collision_manager.hh @@ -49,6 +49,7 @@ #endif #include +#include namespace hpp { namespace fcl { @@ -216,6 +217,9 @@ struct BroadPhaseCollisionManagerWrapper static void exposeDerived() { std::string class_name = boost::typeindex::type_id().pretty_name(); boost::algorithm::replace_all(class_name, "hpp::fcl::", ""); +#if defined(WIN32) + boost::algorithm::replace_all(class_name, "class ", ""); +#endif bp::class_ >( class_name.c_str(), bp::no_init) diff --git a/python/hppfcl/__init__.py b/python/hppfcl/__init__.py index 34f1199..b937843 100644 --- a/python/hppfcl/__init__.py +++ b/python/hppfcl/__init__.py @@ -31,5 +31,6 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +# ruff: noqa: F401, F403 from .hppfcl import * -from .hppfcl import __version__, __raw_version__ +from .hppfcl import __raw_version__, __version__ diff --git a/python/hppfcl/viewer.py b/python/hppfcl/viewer.py index 29cdb2a..15bf53c 100644 --- a/python/hppfcl/viewer.py +++ b/python/hppfcl/viewer.py @@ -3,9 +3,13 @@ # Copyright (c) 2019 CNRS # Author: Joseph Mirabel -import hppfcl, numpy as np +import warnings + +import numpy as np from gepetto import Color +import hppfcl + def applyConfiguration(gui, name, tf): gui.applyConfiguration( @@ -43,10 +47,7 @@ def displayShape(gui, name, geom, color=(0.9, 0.9, 0.9, 1.0)): gui.setLightingMode(name, "OFF") return True else: - msg = "Unsupported geometry type for %s (%s)" % ( - geometry_object.name, - type(geom), - ) + msg = "Unsupported geometry type for %s (%s)" % (name, type(geom)) warnings.warn(msg, category=UserWarning, stacklevel=2) return False diff --git a/python/math.cc b/python/math.cc index fdd7dd0..9e92cb1 100644 --- a/python/math.cc +++ b/python/math.cc @@ -53,12 +53,12 @@ struct TriangleWrapper { static Triangle::index_type getitem(const Triangle& t, int i) { if (i >= 3 || i <= -3) PyErr_SetString(PyExc_IndexError, "Index out of range"); - return t[(i % 3)]; + return t[static_cast(i % 3)]; } static void setitem(Triangle& t, int i, Triangle::index_type v) { if (i >= 3 || i <= -3) PyErr_SetString(PyExc_IndexError, "Index out of range"); - t[(i % 3)] = v; + t[static_cast(i % 3)] = v; } }; diff --git a/python/pickle.hh b/python/pickle.hh index e2a2be6..3e640cf 100644 --- a/python/pickle.hh +++ b/python/pickle.hh @@ -24,7 +24,7 @@ struct PickleObject : boost::python::pickle_suite { static boost::python::tuple getstate(const T& obj) { std::stringstream ss; boost::archive::text_oarchive oa(ss); - oa& obj; + oa & obj; return boost::python::make_tuple(boost::python::str(ss.str())); } diff --git a/src/BV/AABB.cpp b/src/BV/AABB.cpp index 3fdc703..665efc0 100644 --- a/src/BV/AABB.cpp +++ b/src/BV/AABB.cpp @@ -55,7 +55,7 @@ bool AABB::overlap(const AABB& other, const CollisionRequest& request, sqrDistLowerBound = (min_ - other.max_ - Vec3f::Constant(request.security_margin)) .array() - .max(0) + .max(FCL_REAL(0)) .matrix() .squaredNorm(); if (sqrDistLowerBound > break_distance_squared) return false; @@ -63,7 +63,7 @@ bool AABB::overlap(const AABB& other, const CollisionRequest& request, sqrDistLowerBound = (other.min_ - max_ - Vec3f::Constant(request.security_margin)) .array() - .max(0) + .max(FCL_REAL(0)) .matrix() .squaredNorm(); if (sqrDistLowerBound > break_distance_squared) return false; diff --git a/src/BV/OBB.cpp b/src/BV/OBB.cpp index 63e7a7b..10d5089 100644 --- a/src/BV/OBB.cpp +++ b/src/BV/OBB.cpp @@ -292,7 +292,7 @@ inline FCL_REAL obbDisjoint_check_A_axis(const Vec3f& T, const Vec3f& a, // |T| - |B| * b - a Vec3f AABB_corner(T.cwiseAbs() - a); AABB_corner.noalias() -= Bf * b; - return AABB_corner.array().max(0).matrix().squaredNorm(); + return AABB_corner.array().max(FCL_REAL(0)).matrix().squaredNorm(); } inline FCL_REAL obbDisjoint_check_B_axis(const Matrix3f& B, const Vec3f& T, @@ -356,10 +356,12 @@ bool obbDisjointAndLowerBoundDistance(const Matrix3f& B, const Vec3f& T, request.break_distance * request.break_distance; Matrix3f Bf(B.cwiseAbs()); - const Vec3f a( - (a_ + Vec3f::Constant(request.security_margin / 2)).array().max(0)); - const Vec3f b( - (b_ + Vec3f::Constant(request.security_margin / 2)).array().max(0)); + const Vec3f a((a_ + Vec3f::Constant(request.security_margin / 2)) + .array() + .max(FCL_REAL(0))); + const Vec3f b((b_ + Vec3f::Constant(request.security_margin / 2)) + .array() + .max(FCL_REAL(0))); // Corner of b axis aligned bounding box the closest to the origin squaredLowerBoundDistance = internal::obbDisjoint_check_A_axis(T, a, b, Bf); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cc5fa64..e1fe526 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -173,7 +173,10 @@ add_library(${LIBRARY_NAME} ${${LIBRARY_NAME}_SOURCES} ) -set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN") +if(UNIX) + get_relative_rpath(${CMAKE_INSTALL_FULL_LIBDIR} ${PROJECT_NAME}_INSTALL_RPATH) + set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "${${PROJECT_NAME}_INSTALL_RPATH}") +endif() # IDE sources and headers sorting ADD_SOURCE_GROUP(${LIBRARY_NAME}_SOURCES) diff --git a/src/broadphase/broadphase_SaP.cpp b/src/broadphase/broadphase_SaP.cpp index 22bdab6..e956012 100644 --- a/src/broadphase/broadphase_SaP.cpp +++ b/src/broadphase/broadphase_SaP.cpp @@ -767,7 +767,7 @@ void SaPCollisionManager::distance(BroadPhaseCollisionManager* other_manager_, } //============================================================================== -bool SaPCollisionManager::empty() const { return AABB_arr.size(); } +bool SaPCollisionManager::empty() const { return AABB_arr.empty(); } //============================================================================== size_t SaPCollisionManager::size() const { return AABB_arr.size(); } diff --git a/src/broadphase/broadphase_dynamic_AABB_tree.cpp b/src/broadphase/broadphase_dynamic_AABB_tree.cpp index 908a849..ab36051 100644 --- a/src/broadphase/broadphase_dynamic_AABB_tree.cpp +++ b/src/broadphase/broadphase_dynamic_AABB_tree.cpp @@ -574,7 +574,7 @@ void DynamicAABBTreeCollisionManager::collide( case GEOM_OCTREE: { if (!octree_as_geometry_collide) { const OcTree* octree = - static_cast(obj->collisionGeometry().get()); + static_cast(obj->collisionGeometryPtr()); detail::dynamic_AABB_tree::collisionRecurse( dtree.getRoot(), octree, octree->getRoot(), octree->getRootBV(), obj->getTransform(), callback); @@ -600,7 +600,7 @@ void DynamicAABBTreeCollisionManager::distance( case GEOM_OCTREE: { if (!octree_as_geometry_distance) { const OcTree* octree = - static_cast(obj->collisionGeometry().get()); + static_cast(obj->collisionGeometryPtr()); detail::dynamic_AABB_tree::distanceRecurse( dtree.getRoot(), octree, octree->getRoot(), octree->getRootBV(), obj->getTransform(), callback, min_dist); diff --git a/src/broadphase/broadphase_dynamic_AABB_tree_array.cpp b/src/broadphase/broadphase_dynamic_AABB_tree_array.cpp index 279bd1d..2bc5cab 100644 --- a/src/broadphase/broadphase_dynamic_AABB_tree_array.cpp +++ b/src/broadphase/broadphase_dynamic_AABB_tree_array.cpp @@ -622,7 +622,7 @@ void DynamicAABBTreeArrayCollisionManager::collide( case GEOM_OCTREE: { if (!octree_as_geometry_collide) { const OcTree* octree = - static_cast(obj->collisionGeometry().get()); + static_cast(obj->collisionGeometryPtr()); detail::dynamic_AABB_tree_array::collisionRecurse( dtree.getNodes(), dtree.getRoot(), octree, octree->getRoot(), octree->getRootBV(), obj->getTransform(), callback); @@ -648,7 +648,7 @@ void DynamicAABBTreeArrayCollisionManager::distance( case GEOM_OCTREE: { if (!octree_as_geometry_distance) { const OcTree* octree = - static_cast(obj->collisionGeometry().get()); + static_cast(obj->collisionGeometryPtr()); detail::dynamic_AABB_tree_array::distanceRecurse( dtree.getNodes(), dtree.getRoot(), octree, octree->getRoot(), octree->getRootBV(), obj->getTransform(), callback, min_dist); diff --git a/src/collision.cpp b/src/collision.cpp index 3da3e4a..244aee4 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -62,8 +62,8 @@ void CollisionResult::swapObjects() { std::size_t collide(const CollisionObject* o1, const CollisionObject* o2, const CollisionRequest& request, CollisionResult& result) { - return collide(o1->collisionGeometry().get(), o1->getTransform(), - o2->collisionGeometry().get(), o2->getTransform(), request, + return collide(o1->collisionGeometryPtr(), o1->getTransform(), + o2->collisionGeometryPtr(), o2->getTransform(), request, result); } diff --git a/src/distance.cpp b/src/distance.cpp index c9625c6..1199cdc 100644 --- a/src/distance.cpp +++ b/src/distance.cpp @@ -52,8 +52,8 @@ DistanceFunctionMatrix& getDistanceFunctionLookTable() { FCL_REAL distance(const CollisionObject* o1, const CollisionObject* o2, const DistanceRequest& request, DistanceResult& result) { - return distance(o1->collisionGeometry().get(), o1->getTransform(), - o2->collisionGeometry().get(), o2->getTransform(), request, + return distance(o1->collisionGeometryPtr(), o1->getTransform(), + o2->collisionGeometryPtr(), o2->getTransform(), request, result); } diff --git a/src/shape/convex.cpp b/src/shape/convex.cpp index 3841f00..a2a05a9 100644 --- a/src/shape/convex.cpp +++ b/src/shape/convex.cpp @@ -72,7 +72,7 @@ ConvexBase* ConvexBase::convexHull(const Vec3f* pts, unsigned int num_points, // Initialize the vertices int nvertex = (qh.vertexCount()); - Vec3f* vertices = new Vec3f[nvertex]; + Vec3f* vertices = new Vec3f[size_t(nvertex)]; QhullVertexList vertexList(qh.vertexList()); int i_vertex = 0; for (QhullVertexList::const_iterator v = vertexList.begin(); @@ -93,7 +93,7 @@ ConvexBase* ConvexBase::convexHull(const Vec3f* pts, unsigned int num_points, convex->initialize(true, vertices, static_cast(nvertex)); // Build the neighbors - convex->neighbors = new Neighbors[nvertex]; + convex->neighbors = new Neighbors[size_t(nvertex)]; std::vector > nneighbors(static_cast(nvertex)); if (keepTriangles) { convex_tri->num_polygons = static_cast(qh.facetCount()); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a812543..d708a4e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,5 @@ # Find Boost.UnitTestFramework -FIND_PACKAGE(Boost REQUIRED COMPONENTS unit_test_framework filesystem chrono timer) +FIND_PACKAGE(Boost REQUIRED COMPONENTS unit_test_framework filesystem) config_files(fcl_resources/config.h) @@ -8,9 +8,7 @@ macro(add_fcl_test test_name source) target_link_libraries(${test_name} PUBLIC hpp-fcl - Boost::chrono Boost::filesystem - Boost::timer utility ) IF(NOT WIN32) @@ -74,7 +72,6 @@ add_executable(test-benchmark benchmark.cpp) target_link_libraries(test-benchmark PUBLIC utility - Boost::chrono Boost::filesystem ${PROJECT_NAME} ) diff --git a/test/collision.cpp b/test/collision.cpp index 1ad2498..1a2afa3 100644 --- a/test/collision.cpp +++ b/test/collision.cpp @@ -35,9 +35,6 @@ /** \author Joseph Mirabel */ -#define BOOST_CHRONO_VERSION 2 -#include -#include #include #define BOOST_TEST_MODULE FCL_COLLISION @@ -57,6 +54,8 @@ #include "../src/collision_node.h" #include +#include + #include "utility.h" #include "fcl_resources/config.h" @@ -243,9 +242,6 @@ typedef boost::mpl::vector, KDOP<18>, KDOP<16>, kIOS, OBBRSS> std::vector splitMethods = boost::assign::list_of( SPLIT_METHOD_MEAN)(SPLIT_METHOD_MEDIAN)(SPLIT_METHOD_BV_CENTER); -typedef boost::chrono::high_resolution_clock clock_type; -typedef clock_type::duration duration_type; - #define BV_STR_SPECIALIZATION(bv) \ template <> \ const char* str() { \ @@ -345,7 +341,7 @@ struct mesh_mesh_run_test { loadPolyhedronFromResource(TEST_RESOURCES_DIR "/rob.obj", Vec3f::Ones(), model2); - clock_type::time_point start, end; + Timer timer(false); const Transform3f tf2; const std::size_t N = transforms.size(); @@ -356,8 +352,8 @@ struct mesh_mesh_run_test { ++indent; for (std::size_t i = 0; i < transforms.size(); ++i) { - start = clock_type::now(); const Transform3f& tf1 = transforms[i]; + timer.start(); CollisionResult local_result; MeshCollisionTraversalNode node(request); @@ -369,7 +365,7 @@ struct mesh_mesh_run_test { collide(&node, request, local_result); - end = clock_type::now(); + timer.stop(); BENCHMARK(str()); BENCHMARK(1); @@ -384,7 +380,7 @@ struct mesh_mesh_run_test { } BENCHMARK(local_result.numContacts()); BENCHMARK(local_result.distance_lower_bound); - BENCHMARK((end - start).count()); + BENCHMARK(timer.duration().count()); BENCHMARK_NEXT(); if (local_result.numContacts() > 0) { @@ -400,9 +396,9 @@ struct mesh_mesh_run_test { ++indent; for (std::size_t i = 0; i < transforms.size(); ++i) { - start = clock_type::now(); const Transform3f tf1 = transforms[i]; + timer.start(); CollisionResult local_result; MeshCollisionTraversalNode node( request); @@ -421,7 +417,7 @@ struct mesh_mesh_run_test { delete model1_tmp; delete model2_tmp; - end = clock_type::now(); + timer.stop(); BENCHMARK(str()); BENCHMARK(2); BENCHMARK(splitMethod); @@ -435,7 +431,7 @@ struct mesh_mesh_run_test { } BENCHMARK(local_result.numContacts()); BENCHMARK(local_result.distance_lower_bound); - BENCHMARK((end - start).count()); + BENCHMARK(timer.duration().count()); BENCHMARK_NEXT(); if (local_result.numContacts() > 0) { @@ -452,7 +448,7 @@ struct mesh_mesh_run_test { ++indent; for (std::size_t i = 0; i < transforms.size(); ++i) { - start = clock_type::now(); + timer.start(); const Transform3f tf1 = transforms[i]; CollisionResult local_result; @@ -465,7 +461,7 @@ struct mesh_mesh_run_test { collide(&node, request, local_result, NULL, false); - end = clock_type::now(); + timer.stop(); BENCHMARK(str()); BENCHMARK(0); BENCHMARK(splitMethod); @@ -479,7 +475,7 @@ struct mesh_mesh_run_test { } BENCHMARK(local_result.numContacts()); BENCHMARK(local_result.distance_lower_bound); - BENCHMARK((end - start).count()); + BENCHMARK(timer.duration().count()); BENCHMARK_NEXT(); if (local_result.numContacts() > 0) { diff --git a/test/obb.cpp b/test/obb.cpp index f9e6c34..cad2c8c 100644 --- a/test/obb.cpp +++ b/test/obb.cpp @@ -2,6 +2,7 @@ * Software License Agreement (BSD License) * * Copyright (c) 2014-2016, CNRS-LAAS + * Copyright (c) 2023, Inria * Author: Florent Lamiraux * All rights reserved. * @@ -37,9 +38,7 @@ #include #include -#define BOOST_CHRONO_VERSION 2 -#include -#include +#include #include @@ -49,69 +48,6 @@ using namespace hpp::fcl; -int getNumCPUs() { - int NumCPUs = 0; - int MaxID = -1; - std::ifstream f("/proc/cpuinfo"); - if (!f.is_open()) { - std::cerr << "failed to open /proc/cpuinfo\n"; - return -1; - } - const std::string Key = "processor"; - std::string ln; - while (std::getline(f, ln)) { - if (ln.empty()) continue; - size_t SplitIdx = ln.find(':'); - std::string value; - if (SplitIdx != std::string::npos) value = ln.substr(SplitIdx + 1); - if (ln.size() >= Key.size() && ln.compare(0, Key.size(), Key) == 0) { - NumCPUs++; - if (!value.empty()) { - int CurID = (int)strtol(value.c_str(), NULL, 10); - MaxID = std::max(CurID, MaxID); - } - } - } - if (f.bad()) { - std::cerr << "Failure reading /proc/cpuinfo\n"; - return -1; - } - if (!f.eof()) { - std::cerr << "Failed to read to end of /proc/cpuinfo\n"; - return -1; - } - f.close(); - - if ((MaxID + 1) != NumCPUs) { - fprintf(stderr, - "CPU ID assignments in /proc/cpuinfo seem messed up." - " This is usually caused by a bad BIOS.\n"); - } - return NumCPUs; -} - -bool checkCpuScalingEnabled() { - int num_cpus = getNumCPUs(); - - // We don't have a valid CPU count, so don't even bother. - if (num_cpus <= 0) return false; - // On Linux, the CPUfreq subsystem exposes CPU information as files on the - // local file system. If reading the exported files fails, then we may not be - // running on Linux, so we silently ignore all the read errors. - std::string res; - for (int cpu = 0; cpu < num_cpus; ++cpu) { - std::ostringstream oss; - oss << "/sys/devices/system/cpu/cpu" << cpu << "/cpufreq/scaling_governor"; - std::string governor_file = oss.str(); - std::ifstream f(governor_file.c_str()); - if (!f.is_open()) return false; - f >> res; - if (!f.good()) return false; - if (res != "performance") return true; - } - return false; -} - void randomOBBs(Vec3f& a, Vec3f& b, FCL_REAL extentNorm) { // Extent norm is between 0 and extentNorm on each axis // a = (Vec3f::Ones()+Vec3f::Random()) * extentNorm / (2*sqrt(3)); @@ -150,7 +86,7 @@ void randomTransform(Matrix3f& B, Vec3f& T, const Vec3f& a, const Vec3f& b, #define PRODUCT(M33, v3) (M33 * v3) #endif -typedef boost::chrono::high_resolution_clock clock_type; +typedef std::chrono::high_resolution_clock clock_type; typedef clock_type::duration duration_type; const char* sep = ",\t"; @@ -193,7 +129,7 @@ inline FCL_REAL _computeDistanceForCase1(const Vec3f& T, const Vec3f& a, AABB_corner.noalias() = T.cwiseAbs() - Bf * b - a; #endif // */ - return AABB_corner.array().max(0).matrix().squaredNorm(); + return AABB_corner.array().max(FCL_REAL(0)).matrix().squaredNorm(); } inline FCL_REAL _computeDistanceForCase2(const Matrix3f& B, const Vec3f& T, @@ -202,7 +138,7 @@ inline FCL_REAL _computeDistanceForCase2(const Matrix3f& B, const Vec3f& T, /* Vec3f AABB_corner(PRODUCT(B.transpose(), T).cwiseAbs() - b); AABB_corner.noalias() -= PRODUCT(Bf.transpose(), a); - return AABB_corner.array().max(0).matrix().squaredNorm (); + return AABB_corner.array().max(FCL_REAL(0)).matrix().squaredNorm (); /*/ #if MANUAL_PRODUCT FCL_REAL s, t = 0; @@ -215,7 +151,7 @@ inline FCL_REAL _computeDistanceForCase2(const Matrix3f& B, const Vec3f& T, return t; #else Vec3f AABB_corner((B.transpose() * T).cwiseAbs() - Bf.transpose() * a - b); - return AABB_corner.array().max(0).matrix().squaredNorm(); + return AABB_corner.array().max(FCL_REAL(0)).matrix().squaredNorm(); #endif // */ } @@ -1204,25 +1140,26 @@ struct BenchmarkResult { bool failure; static std::ostream& headers(std::ostream& os) { - duration_type dummy(1); - std::string unit = " (" + - boost::chrono::duration_units_default<>().get_unit( - boost::chrono::duration_style::symbol, dummy) + - ")"; - os << boost::chrono::symbol_format << "separating axis" << sep - << "distance lower bound" << sep << "distance" << sep << "failure" << sep - << "Runtime Loop" << unit << sep << "Manual Loop Unrolling 1" << unit - << sep << "Manual Loop Unrolling 2" << unit << sep - << "Template Unrolling" << unit << sep << "Partial Template Unrolling" - << unit << sep << "Original (LowerBound)" << unit << sep - << "Original (NoLowerBound)" << unit; + const std::string unit = " (us)"; + os << "separating axis" << sep << "distance lower bound" << sep + << "distance" << sep << "failure" << sep << "Runtime Loop" << unit << sep + << "Manual Loop Unrolling 1" << unit << sep << "Manual Loop Unrolling 2" + << unit << sep << "Template Unrolling" << unit << sep + << "Partial Template Unrolling" << unit << sep << "Original (LowerBound)" + << unit << sep << "Original (NoLowerBound)" << unit; return os; } std::ostream& print(std::ostream& os) const { os << ifId << sep << std::sqrt(squaredLowerBoundDistance) << sep << distance << sep << failure; - for (int i = 0; i < NB_METHODS; ++i) os << sep << duration[i].count(); + for (int i = 0; i < NB_METHODS; ++i) + os << sep + << static_cast( + std::chrono::duration_cast( + duration[i]) + .count()) * + 1e-3; return os; } }; @@ -1378,15 +1315,13 @@ int main(int argc, char** argv) { output = &std::cout; } - bool cpuScalingEnabled = checkCpuScalingEnabled(); - if (cpuScalingEnabled) - std::cerr << "CPU scaling is enabled." - "\n\tThe benchmark real time measurements may be noisy and " - "will incur extra overhead." - "\n\tUse the following commands to turn on and off." - "\n\t\tsudo cpufreq-set --governor performance" - "\n\t\tsudo cpufreq-set --governor powersave" - "\n"; + std::cout << "The benchmark real time measurements may be noisy and " + "will incur extra overhead." + "\nUse the following commands to turn ON:" + "\n\tsudo cpufreq-set --governor performance" + "\nor OFF:" + "\n\tsudo cpufreq-set --governor powersave" + "\n"; std::size_t nbFailure = obb_overlap_and_lower_bound_distance(output); if (nbFailure > INT_MAX) return INT_MAX; diff --git a/test/python_unit/api.py b/test/python_unit/api.py index 10c92c9..d982dc6 100644 --- a/test/python_unit/api.py +++ b/test/python_unit/api.py @@ -2,7 +2,6 @@ from test_case import TestCase import hppfcl -hppfcl.switchToNumpyArray() import numpy as np diff --git a/test/python_unit/collision.py b/test/python_unit/collision.py index fd69df3..d14ece9 100644 --- a/test/python_unit/collision.py +++ b/test/python_unit/collision.py @@ -2,7 +2,6 @@ from test_case import TestCase import hppfcl -hppfcl.switchToNumpyArray() import numpy as np diff --git a/test/python_unit/geometric_shapes.py b/test/python_unit/geometric_shapes.py index c6b6b6e..b64850e 100644 --- a/test/python_unit/geometric_shapes.py +++ b/test/python_unit/geometric_shapes.py @@ -167,11 +167,11 @@ def test_convex(self): ] ) faces.append(hppfcl.Triangle(0, 1, 2)) - convex = hppfcl.Convex(verts, faces) + hppfcl.Convex(verts, faces) verts.append(np.array([0, 0, 1])) try: - convexHull = hppfcl.Convex.convexHull(verts, False, None) + hppfcl.Convex.convexHull(verts, False, None) qhullAvailable = True except Exception as e: self.assertEqual( @@ -180,11 +180,11 @@ def test_convex(self): qhullAvailable = False if qhullAvailable: - convexHull = hppfcl.Convex.convexHull(verts, False, "") - convexHull = hppfcl.Convex.convexHull(verts, True, "") + hppfcl.Convex.convexHull(verts, False, "") + hppfcl.Convex.convexHull(verts, True, "") try: - convexHull = hppfcl.Convex.convexHull(verts[:3], False, None) + hppfcl.Convex.convexHull(verts[:3], False, None) except Exception as e: self.assertIn( str(e), "You shouldn't use this function with less than 4 points." diff --git a/test/scripts/collision-bench.py b/test/scripts/collision-bench.py index 885995a..62d6e87 100644 --- a/test/scripts/collision-bench.py +++ b/test/scripts/collision-bench.py @@ -1,6 +1,7 @@ +import csv +import sys + import matplotlib.pyplot as plt -import csv, sys, numpy as np -from math import sqrt filename = sys.argv[1] @@ -43,8 +44,8 @@ idx_reorder = sorted(list(range(len(xvals))), key=lambda i: xvals[i]) -def reorder(l): - return [l[i] for i in idx_reorder] +def reorder(v): + return [v[i] for i in idx_reorder] xvals_s = reorder(xvals) diff --git a/test/scripts/collision.py b/test/scripts/collision.py index 1604b96..73fc979 100644 --- a/test/scripts/collision.py +++ b/test/scripts/collision.py @@ -1,6 +1,5 @@ # Load "env.obj" and "rob.obj" in gepetto-gui -import numpy as np import os from gepetto.corbaserver import Client diff --git a/test/scripts/distance_lower_bound.py b/test/scripts/distance_lower_bound.py index 1e4b29b..61e7b3a 100644 --- a/test/scripts/distance_lower_bound.py +++ b/test/scripts/distance_lower_bound.py @@ -1,6 +1,5 @@ # Load "env.obj" and "rob.obj" in gepetto-gui -import numpy as np import os from gepetto.corbaserver import Client diff --git a/test/scripts/geometric_shapes.py b/test/scripts/geometric_shapes.py index 02dccad..1e893bb 100644 --- a/test/scripts/geometric_shapes.py +++ b/test/scripts/geometric_shapes.py @@ -1,6 +1,6 @@ # Datas for compare_convex_box -from gepetto.corbaserver import Client from gepetto import Quaternion +from gepetto.corbaserver import Client def translate(tr, t, d): @@ -10,7 +10,7 @@ def translate(tr, t, d): cl = Client() try: cl.gui.getWindowID("fcl") -except: +except Exception: cl.gui.createWindow("fcl") cl.gui.addBox("fcl/b0", 2, 2, 2, [1, 0, 0, 0.5]) diff --git a/test/scripts/gjk.py b/test/scripts/gjk.py index bdad975..6eee6d8 100644 --- a/test/scripts/gjk.py +++ b/test/scripts/gjk.py @@ -1,6 +1,5 @@ # This script displays two triangles and two spheres in gepetto-gui # It is useful to debug distance computation between two triangles. -import numpy as np from gepetto.corbaserver import Client Red = [1, 0, 0, 0.5] diff --git a/test/scripts/obb.py b/test/scripts/obb.py index 7b59e61..1418e0e 100644 --- a/test/scripts/obb.py +++ b/test/scripts/obb.py @@ -1,7 +1,10 @@ -import matplotlib.pyplot as plt -import csv, sys, numpy as np +import csv +import sys from math import sqrt +import matplotlib.pyplot as plt +import numpy as np + filename = sys.argv[1] with open(filename, "r") as file: @@ -67,9 +70,25 @@ plt.figure(iplot) plt.title("Time, with std dev, versus separating axis") for k, n in enumerate(fieldnames[4:]): - # plt.errorbar ([ np.linspace(0, 11, 12) + shift for shift in np.linspace (-0.2, 0.2, ) ], means[k], stddevs[k], label=n) + # plt.errorbar( + # [ + # np.linspace(0, 11, 12) + shift + # for shift in np.linspace( + # -0.2, + # 0.2, + # ) + # ], + # means[k], + # stddevs[k], + # label=n, + # ) plt.errorbar(np.linspace(0, 11, 12), means[k], stddevs[k], label=n) - # plt.errorbar (np.linspace(0, 11, 12), means[k], [ [ 0 ] * len(stddevs[k]), stddevs[k] ], label=n) + # plt.errorbar( + # np.linspace(0, 11, 12), + # means[k], + # [[0] * len(stddevs[k]), stddevs[k]], + # label=n, + # ) plt.xlim([-0.5, 11.5]) plt.ylabel("Time (ns)") plt.xlabel("Separating axis") @@ -120,23 +139,23 @@ zip(fieldnames[4:], _means, _stddev, _mins, _maxs), key=lambda x: x[1] ) plt.errorbar( - [f for f, m, s, l, u in _sorted], - [m for f, m, s, l, u in _sorted], - [s for f, m, s, l, u in _sorted], + [f for f, _, _, _, _ in _sorted], + [m for _, m, _, _, _ in _sorted], + [s for _, _, s, _, _ in _sorted], fmt="go", linestyle="", label="mean and std deviation", ) plt.plot( - [f for f, m, s, l, u in _sorted], - [l for f, m, s, l, u in _sorted], + [f for f, _, _, _, _ in _sorted], + [v for _, _, _, v, _ in _sorted], "b+", ls="", label="min", ) plt.plot( - [f for f, m, s, l, u in _sorted], - [u for f, m, s, l, u in _sorted], + [f for f, _, _, _, _ in _sorted], + [u for _, _, _, _, u in _sorted], "r+", ls="", label="max", diff --git a/test/scripts/octree.py b/test/scripts/octree.py index a5c865f..c0ef6e4 100644 --- a/test/scripts/octree.py +++ b/test/scripts/octree.py @@ -1,6 +1,5 @@ # Load "env.obj" and "rob.obj" in gepetto-gui -import numpy as np import csv import os from gepetto.corbaserver import Client diff --git a/test/serialization.cpp b/test/serialization.cpp index c5eaffb..14bbf33 100644 --- a/test/serialization.cpp +++ b/test/serialization.cpp @@ -70,7 +70,7 @@ using namespace hpp::fcl; template void saveToBinary(const T& object, boost::asio::streambuf& buffer) { boost::archive::binary_oarchive oa(buffer); - oa& object; + oa & object; } template @@ -89,9 +89,11 @@ enum SerializationMode { TXT = 1, XML = 2, BIN = 4, STREAM = 8 }; template void test_serialization(const T& value, T& other_value, const int mode = TXT | XML | BIN | STREAM) { - const std::string tmp_dir(boost::archive::tmpdir()); - const std::string txt_filename = tmp_dir + "file.txt"; - const std::string bin_filename = tmp_dir + "file.bin"; + const boost::filesystem::path tmp_path(boost::archive::tmpdir()); + const boost::filesystem::path txt_path("file.txt"); + const boost::filesystem::path bin_path("file.bin"); + const boost::filesystem::path txt_filename(tmp_path / txt_path); + const boost::filesystem::path bin_filename(tmp_path / bin_path); // TXT if (mode & 0x1) {