diff --git a/.docker/Dockerfile b/.docker/Dockerfile index edc945b0e..9b07c8197 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,12 +1,10 @@ ARG from=diegoferigo/gym-ignition:pypi-master FROM ${from} -# Source /etc/bash.bashrc before each command -SHELL ["/bin/bash", "-i", "-c"] - # Install the PyPI package in a virtualenv ARG pip_options="" RUN virtualenv -p $(which python3) ${VIRTUAL_ENV} &&\ + python -m pip install --upgrade pip &&\ pip install ${pip_options} gym-ignition &&\ rm -r $HOME/.cache/pip @@ -15,5 +13,7 @@ WORKDIR /github ARG branch="master" RUN git clone -b ${branch} https://github.com/robotology/gym-ignition /github -CMD ["bash"] +# Reset the entrypoint ENTRYPOINT [""] + +CMD ["bash"] diff --git a/.docker/base.Dockerfile b/.docker/base.Dockerfile index 75fc24448..5fca6ba22 100644 --- a/.docker/base.Dockerfile +++ b/.docker/base.Dockerfile @@ -59,21 +59,4 @@ RUN add-apt-repository ppa:git-core/ppa &&\ ENV VIRTUAL_ENV=/venv ENV PATH=$VIRTUAL_ENV/bin:$PATH -# Install iDynTree -RUN apt-get update &&\ - apt-get install -y --no-install-recommends \ - python3-numpy libxml2-dev coinor-libipopt-dev libeigen3-dev &&\ - rm -rf /var/lib/apt/lists/* &&\ - git clone --depth 1 -b devel https://github.com/robotology/idyntree /tmp/idyntree &&\ - mkdir -p /tmp/idyntree/build && cd /tmp/idyntree/build &&\ - cmake .. \ - -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS:BOOL=OFF \ - -DIDYNTREE_USES_PYTHON=True \ - -DIDYNTREE_USES_IPOPT:BOOL=ON \ - &&\ - cmake --build . --target install &&\ - rm -r /tmp/idyntree - CMD ["bash"] diff --git a/.docker/cicd-devel.Dockerfile b/.docker/cicd-devel.Dockerfile index 88a272300..eb32c7cfa 100644 --- a/.docker/cicd-devel.Dockerfile +++ b/.docker/cicd-devel.Dockerfile @@ -1,76 +1,24 @@ ARG from=diegoferigo/gym-ignition:base FROM ${from} -ARG IGNITION_DEFAULT_CHANNEL="stable" -RUN echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-${IGNITION_DEFAULT_CHANNEL} `lsb_release -cs` main" > \ - /etc/apt/sources.list.d/gazebo-${IGNITION_DEFAULT_CHANNEL}.list &&\ - wget http://packages.osrfoundation.org/gazebo.key -O - | apt-key add - &&\ - apt-get update &&\ - apt-get install -y --no-install-recommends \ - cmake \ - freeglut3-dev \ - libavcodec-dev \ - libavdevice-dev \ - libavformat-dev \ - libavutil-dev \ - libbenchmark-dev \ - libcurl4-openssl-dev \ - libfreeimage-dev \ - libgflags-dev \ - libglew-dev \ - libgts-dev \ - libjsoncpp-dev \ - libogre-1.9-dev \ - libogre-2.1-dev \ - libprotobuf-dev \ - libprotoc-dev \ - libqt5core5a \ - libsqlite3-dev \ - libswscale-dev \ - libtinyxml-dev \ - libtinyxml2-dev \ - libwebsockets-dev \ - libyaml-dev \ - libzip-dev \ - libzmq3-dev \ - pkg-config \ - protobuf-compiler \ - python \ - qml-module-qt-labs-folderlistmodel \ - qml-module-qt-labs-settings \ - qml-module-qtqml-models2 \ - qml-module-qtquick-controls \ - qml-module-qtquick-controls2 \ - qml-module-qtquick-dialogs \ - qml-module-qtquick-layouts \ - qml-module-qtquick2 \ - qtbase5-dev \ - qtdeclarative5-dev \ - qtquickcontrols2-5-dev \ - ruby \ - ruby-dev \ - ruby-ronn \ - swig \ - uuid-dev \ - # Additional - libdart-collision-ode-dev \ - libdart-dev \ - libdart-external-ikfast-dev \ - libdart-external-odelcpsolver-dev \ - libdart-utils-urdf-dev \ - &&\ - rm -rf /var/lib/apt/lists/* - RUN pip3 install vcstool colcon-common-extensions &&\ rm -r $HOME/.cache/pip ARG CMAKE_BUILD_TYPE="Release" -ARG ignition_codename="citadel" +ARG ignition_codename="fortress" +ARG IGNITION_DEFAULT_CHANNEL="stable" -RUN mkdir -p /workspace/src &&\ +RUN echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-${IGNITION_DEFAULT_CHANNEL} `lsb_release -cs` main" > \ + /etc/apt/sources.list.d/gazebo-${IGNITION_DEFAULT_CHANNEL}.list &&\ + wget http://packages.osrfoundation.org/gazebo.key -O - | apt-key add - &&\ + apt-get update &&\ + mkdir -p /workspace/src &&\ cd /workspace/src &&\ wget https://raw.githubusercontent.com/ignition-tooling/gazebodistro/master/collection-${ignition_codename}.yaml &&\ vcs import < collection-${ignition_codename}.yaml &&\ + apt -y install --no-install-recommends \ + $(sort -u $(find . -iname 'packages-'$(lsb_release -cs)'.apt' -o -iname 'packages.apt') | grep -v -E "dart|^libignition|^libsdformat" | tr '\n' ' ') &&\ + rm -rf /var/lib/apt/lists/* &&\ cd /workspace &&\ colcon graph &&\ colcon build \ @@ -80,8 +28,12 @@ RUN mkdir -p /workspace/src &&\ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ --merge-install \ &&\ + find build/ -type f -not -name 'CMakeCache.txt' -delete &&\ echo "source /workspace/install/setup.bash" >> /etc/bash.bashrc +# Source /etc/bash.bashrc before each command +SHELL ["/bin/bash", "-i", "-c"] + COPY entrypoint.sh /entrypoint.sh COPY setup_virtualenv.sh /setup_virtualenv.sh RUN chmod 755 /entrypoint.sh diff --git a/.docker/cicd-master.Dockerfile b/.docker/cicd-master.Dockerfile index f464f77b3..a7307c06d 100644 --- a/.docker/cicd-master.Dockerfile +++ b/.docker/cicd-master.Dockerfile @@ -2,7 +2,7 @@ ARG from=diegoferigo/gym-ignition:base FROM ${from} # Install ignition gazebo -ARG ignition_codename="citadel" +ARG ignition_codename="fortress" RUN echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" \ > /etc/apt/sources.list.d/gazebo-stable.list &&\ wget http://packages.osrfoundation.org/gazebo.key -O - | apt-key add - &&\ diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 8cd493a50..a630f09d9 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -40,7 +40,7 @@ services: build: args: from: diegoferigo/gym-ignition:base - ignition_codename: dome + ignition_codename: fortress CMAKE_BUILD_TYPE: Debug context: . dockerfile: cicd-devel.Dockerfile @@ -50,7 +50,7 @@ services: build: args: from: diegoferigo/gym-ignition:base - ignition_codename: dome + ignition_codename: fortress CMAKE_BUILD_TYPE: Release context: . dockerfile: cicd-devel.Dockerfile diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..6f458db64 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owners +* @diegoferigo diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ac1cdd1f5..36f273f8a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,8 +6,9 @@ labels: issue::type::enhancement -โš ๏ธ If you're not sure on the specifics of the feature or would like a broader discussion, -please consider posting a proposal to [Discussions][Discussions] instead. +โš ๏ธ If you're not sure on the specifics of the feature or would like a broader discussion, please consider posting a proposal to [Discussions][Discussions] instead. + +[Discussions]: https://github.com/robotology/gym-ignition/discussions ## Summary @@ -15,8 +16,7 @@ Describe what this new feature is about and the context you would find it useful ## Implementation suggestion -Provide a suggestion on how to implement this feature, which could help us -to speed up the implementation. +Provide a suggestion on how to implement this feature, which could help us to speed up the implementation. ## Additional context diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 38ead433a..68d3cd0dd 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -9,26 +9,26 @@ on: jobs: - build-and-test: - name: Build and Test +# ============= + build-colcon: +# ============= + name: 'colcon@${{ matrix.ignition }}' + if: | + (github.event_name == 'push' && github.ref != 'refs/heads/master') || + (github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'master') runs-on: ubuntu-latest - container: 'ubuntu:focal' strategy: fail-fast: false matrix: - type: - - User - - Developer ignition: - - dome - # - citadel - + # - dome + # - edifice + - fortress env: CCACHE_DIR: ${{ github.workspace }}/.ccache - steps: - - name: Inspect Environment + - name: '๐Ÿ”๏ธ Inspect Environment' run: | env | grep ^GITHUB echo "" @@ -36,80 +36,65 @@ jobs: echo "" env - - name: Install dependencies + - name: 'โฌ‡๏ธ๏ธ Install dependencies' run: | - apt-get update - apt-get install -y --no-install-recommends \ - wget \ + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + lz4 \ git \ + wget \ gpg-agent \ ninja-build \ build-essential \ - software-properties-common \ - python3 \ - python3-pip \ - python3-wheel - pip3 install pytest pytest-xvfb setuptools_scm - env: - DEBIAN_FRONTEND: noninteractive + software-properties-common - - uses: actions/checkout@master - - run: git fetch --prune --unshallow + - name: '๐Ÿ Initialize Python' + uses: actions/setup-python@v2 + with: + python-version: 3.8 - - name: Compilation cache + - name: '๐Ÿšš Compilation cache' uses: actions/cache@v2 with: path: ${{ env.CCACHE_DIR }} # We include the commit sha in the cache key, as new cache entries are # only created if there is no existing entry for the key yet. - key: ${{ runner.os }}-ccache-${{ matrix.ignition }}-${{ matrix.type }}-${{ github.sha }} + key: ${{ runner.os }}-ccache-${{ matrix.ignition }}-${{ github.sha }} # Restore any ccache cache entry, if none for the key above exists - restore-keys: | - ${{ runner.os }}-ccache-${{ matrix.ignition }}-${{ matrix.type }} - ${{ runner.os }}-ccache-${{ matrix.ignition }} - ${{ runner.os }}-ccache + restore-keys: ${{ runner.os }}-ccache-${{ matrix.ignition }} - - name: Enable ccache + - name: '๐Ÿšš Enable ccache' run: | - apt-get update - apt-get install -y ccache + sudo apt-get update + sudo apt-get install -y ccache echo "/usr/lib/ccache" >> $GITHUB_PATH ccache --set-config=max_size=5.0G ccache --set-config=sloppiness=file_macro,locale,time_macros ccache -p ccache -s - # ================ - # Install Ignition - # ================ - - - name: '[Stable Channel] Install Ignition ${{ matrix.ignition }}' - if: | - github.event_name == 'release' || - github.ref == 'refs/heads/master' || - (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') + - name: 'โš™๏ธ Add osrf ppa' run: | - sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" >\ + sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" >\ /etc/apt/sources.list.d/gazebo-stable.list' - wget http://packages.osrfoundation.org/gazebo.key -O - | apt-key add - - apt-get update - apt-get install -y --no-install-recommends ignition-${{ matrix.ignition }} + wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add - + sudo apt-get update - - name: '[Nightly Channel] Install Ignition ${{ matrix.ignition }}' - if: | - (github.event_name == 'push' && github.ref != 'refs/heads/master') || - (github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'master') + - name: 'โš™๏ธ Prepare colcon workspace' run: | - sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" >\ - /etc/apt/sources.list.d/gazebo-stable.list' - wget http://packages.osrfoundation.org/gazebo.key -O - | apt-key add - - apt-get update - pip3 install vcstool colcon-common-extensions - mkdir -p /workspace/src + pip install vcstool colcon-common-extensions + sudo mkdir -p /workspace/src /workspace/install + sudo chmod -R a+rw /workspace cd /workspace/src wget -O - ${TAGS_YAML} | vcs import - SYSTEM_VERSION=$(lsb_release -cs) - apt-get -y install $(sort -u $(find . -iname 'packages-'$SYSTEM_VERSION'.apt' -o -iname 'packages.apt') | tr '\n' ' ') + echo $(sort -u $(find . -iname 'packages-'$(lsb_release -cs)'.apt' -o -iname 'packages.apt') | grep -v -E "^libignition|^libsdformat" | tr '\n' ' ') \ + > /workspace/install/pkg.txt + xargs -a /workspace/install/pkg.txt sudo apt-get install -y --no-install-recommends + env: + TAGS_YAML: https://raw.githubusercontent.com/ignition-tooling/gazebodistro/master/collection-${{ matrix.ignition }}.yaml + + - name: '๐Ÿ—๏ธ Build colcon workspace' + run: | cd /workspace colcon graph colcon build \ @@ -118,144 +103,418 @@ jobs: -DBUILD_TESTING:BOOL=OFF \ -DCMAKE_BUILD_TYPE=Debug \ --merge-install - echo "source /workspace/install/setup.bash" >> /etc/bash.bashrc - env: - TAGS_YAML: https://raw.githubusercontent.com/ignition-tooling/gazebodistro/master/collection-${{ matrix.ignition }}.yaml - # ========================== - # Install other dependencies - # ========================== + - name: '๐Ÿ“ˆ Ccache stats' + run: ccache --show-stats + + - name: '๐Ÿ“ฆ๏ธ Compress the workspace' + run: tar -I lz4 -cf /tmp/workspace_install.tar.lz4 /workspace/install + - name: 'โฌ†๏ธ Upload the workspace' + uses: actions/upload-artifact@v2 + with: + path: /tmp/workspace_install.tar.lz4 + name: workspace-${{ matrix.ignition }} + retention-days: 1 + +# =============== + build-and-test: +# =============== + name: 'Build and Test [${{matrix.type}}|${{matrix.ignition}}|${{matrix.python}}]' + if: always() + needs: [ build-colcon ] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + type: + - User + - Developer + ignition: + # - dome + # - edifice + - fortress + python: + - 3.8 + - 3.9 + steps: + + - name: '๐Ÿ” Inspect Environment' + run: | + env | grep ^GITHUB + echo "" + cat ${GITHUB_EVENT_PATH} + echo "" + env - - name: Install iDynTree dependencies + - name: 'โฌ‡๏ธ Install build dependencies' + if: contains(matrix.os, 'ubuntu') run: | - apt-get update - apt-get install -y --no-install-recommends \ - libxml2-dev coinor-libipopt-dev libeigen3-dev libassimp-dev swig + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + lz4 \ + git \ + wget \ + cmake \ + gpg-agent \ + ninja-build \ + build-essential \ + software-properties-common + + - name: '๐Ÿ Initialize Python' + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: '๐Ÿ”€ Clone repository' + uses: actions/checkout@master + - name: '๐Ÿ”€ Download all refs' + run: git fetch --prune --unshallow + + # ================ + # Install Ignition + # ================ + + - name: 'โš™๏ธ Add osrf ppa' + if: contains(matrix.os, 'ubuntu') + run: | + sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" >\ + /etc/apt/sources.list.d/gazebo-stable.list' + wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add - + sudo apt-get update + - name: '[๐Ÿ”’๏ธ|stable] Install Ignition from ppa' + if: | + contains(matrix.os, 'ubuntu') && ( + github.event_name == 'release' || + github.ref == 'refs/heads/master' || + (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') + ) + run: sudo apt-get install -y --no-install-recommends ignition-${{ matrix.ignition }} + + - name: '[๐Ÿงช|nightly] Download pre-built colcon workspace' + uses: actions/download-artifact@v2 + if: | + contains(matrix.os, 'ubuntu') && ( + (github.event_name == 'push' && github.ref != 'refs/heads/master') || + (github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'master') + ) + with: + path: /tmp + name: workspace-${{ matrix.ignition }} + - name: '[๐Ÿงช|nightly] Setup colcon workspace' + if: | + contains(matrix.os, 'ubuntu') && ( + (github.event_name == 'push' && github.ref != 'refs/heads/master') || + (github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'master') + ) + run: | + sudo tar -I lz4 -xf /tmp/workspace_install.tar.lz4 -C / + xargs -a /workspace/install/pkg.txt sudo apt-get install -y --no-install-recommends + echo "source /workspace/install/setup.bash" | sudo tee -a /etc/bash.bashrc # ============= # Build project # ============= - # Note: In order to execute the setup.sh script, the file /etc/bash.bashrc must be sourced. - # To do that, we change the shell to a bash interactive session. - - # Developer installation - - name: Install iDynTree - if: matrix.type == 'Developer' + # This is required because ScenarIO needs to import the iDynTree targets + - name: 'โฌ‡๏ธ Install iDynTree dependencies' + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxml2-dev coinor-libipopt-dev libeigen3-dev libassimp-dev swig + - name: '๐Ÿค– Install iDynTree' run: | - pip3 install git+https://github.com/robotology/idyntree@devel + pip install idyntree IDYNTREE_PYTHON_PKG=$(python3 -c 'import idyntree, pathlib; print(pathlib.Path(idyntree.__file__).parent)') echo "CMAKE_PREFIX_PATH=$IDYNTREE_PYTHON_PKG" >> $GITHUB_ENV - - name: '[Developer] Build and Install C++' + + # Note: In order to execute the setup.sh script, the file /etc/bash.bashrc must be sourced. + # To do that, we change the shell to a bash interactive session with 'bash -i -e'. + + # Developer installation + - name: '[๐Ÿ‘ท|developer] Build and Install C++ ScenarIO' if: matrix.type == 'Developer' shell: bash -i -e {0} run: | env - mkdir build && cd build - cmake .. \ + cmake -S . -B build/ \ -GNinja \ -DCMAKE_BUILD_TYPE=Debug \ -DIGNITION_DISTRIBUTION=$(python3 -c "print('${{ matrix.ignition }}'.capitalize())") - cmake --build . --target install - - name: '[Developer] Setup Python Package' + sudo cmake --build build/ --target install + - name: '[๐Ÿ‘ท|developer] Install Python ScenarIO' if: matrix.type == 'Developer' - run: pip3 install -e . + run: pip install -e ./scenario # User installation - - name: '[User] Create wheel' - if: matrix.type == 'User' && matrix.ignition == 'dome' + - name: '[๐Ÿ‘ค|user] Create wheel (default ignition)' + if: matrix.type == 'User' && matrix.ignition == 'fortress' shell: bash -i -e {0} - run: pip3 wheel -v -w dist/ . - # Note: calling "pip wheel" with "--global-option" forces dependencies to be build from their sdist. - # Since it's very slow, we create the wheel from setup.py without isolation. - - name: '[User] Create wheel' - if: matrix.type == 'User' && matrix.ignition != 'dome' + run: pip wheel --use-feature=in-tree-build -v -w dist/ ./scenario + # Note: Calling "pip wheel" with "--global-option" forces all dependencies to be built from their sdist. + # Since it's very slow, we create the wheel from setup.py without isolation (requires system deps). + - name: '[๐Ÿ‘ค|user] Create wheel (custom ignition)' + if: matrix.type == 'User' && matrix.ignition != 'fortress' shell: bash -i -e {0} run: | - pip3 install \ - cmake_build_extension \ - git+https://github.com/robotology/idyntree@devel - python3 setup.py bdist_wheel \ + pip install wheel setuptools-scm cmake-build-extension + python3 ./scenario/setup.py bdist_wheel \ build_ext -DIGNITION_DISTRIBUTION=$(python3 -c "print('${{ matrix.ignition }}'.capitalize())") - - name: '[User] Install local wheel' + - name: '[๐Ÿ‘ค|user] Install local wheel' if: matrix.type == 'User' - run: | - cd dist - pip3 install -v *.whl + run: pip install -v dist/scenario-*.whl - - name: Inspect installed ScenarIO package + - name: '๐Ÿ”๏ธ Inspect installed ScenarIO package' + if: matrix.type == 'User' && contains(matrix.os, 'ubuntu') run: | - apt-get install -y --no-install-recommends tree + sudo apt-get install -y --no-install-recommends tree tree $(python3 -c "import scenario, pathlib; print(pathlib.Path(scenario.__file__).parent)") - - name: Ccache stats - run: ccache --show-stats + # ==================== + # Install gym-ignition + # ==================== + + - name: '๐Ÿ Install gym-ignition' + run: pip install wheel && pip install .[all] # ============ # Test project # ============ - - name: '[ScenarI/O] Python Tests' - run: | - cd tests - pytest -m "scenario" + - name: '[๐Ÿ|scenario] Python Tests' + shell: bash -i -e {0} + run: pytest -m "scenario" - - name: '[ScenarI/O] Python Tests with Valgrind' + - name: '[๐Ÿšจ|scenario] Python Tests with Valgrind' + shell: bash -i -e {0} if: failure() run: | - apt-get install -y --no-install-recommends valgrind - pip3 install colour-valgrind - cd tests - valgrind --log-file=/tmp/valgrind.log pytest -s -m "scenario" || colour-valgrind -t /tmp/valgrind.log + sudo apt-get install -y --no-install-recommends valgrind + pip install colour-valgrind + valgrind --log-file=/tmp/valgrind.log pytest -m "scenario" || colour-valgrind -t /tmp/valgrind.log - - name: '[gym_ignition] Python Tests' - run: | - cd tests - pytest -m "gym_ignition" + - name: '[๐Ÿ|gym-ignition] Python Tests' + shell: bash -i -e {0} + run: pytest -m "gym_ignition" - - name: '[gym_ignition] Python Tests with Valgrind' + - name: '[๐Ÿšจ|gym-ignition] Python Tests with Valgrind' + shell: bash -i -e {0} if: failure() run: | - pip3 install colour-valgrind - cd tests - valgrind --log-file=/tmp/valgrind.log pytest -s -m "gym_ignition" || colour-valgrind -t /tmp/valgrind.log + sudo apt-get install -y --no-install-recommends valgrind + pip install colour-valgrind + valgrind --log-file=/tmp/valgrind.log pytest -m "gym_ignition" || colour-valgrind -t /tmp/valgrind.log - # ====== - # Deploy - # ====== + # ============================ + # Upload artifacts (only User) + # ============================ - - name: Remove other wheels + - name: '๐Ÿ—‘๏ธ Remove external wheels' if: matrix.type == 'User' - run: find dist/ -type f -not -name 'gym_ignition-*' -delete + run: find dist/ -type f -not -name 'scenario-*' -delete -print - - name: Rename wheel - if: matrix.type == 'User' + # We have to trick PyPI that our wheels are manylinux2014 even if they are not. + # Unfortunately we cannot create self-contained wheels (neither the PEP600 perennial) + # due to the Ignition architecture. + - name: '๐Ÿ“ Rename scenario wheel' + if: matrix.type == 'User' && contains(matrix.os, 'ubuntu') run: | ls dist/ - find dist/ -type f -name "*.whl" -exec rename.ul linux manylinux1 {} + + find dist/ -type f -name "*.whl" -exec rename.ul linux manylinux2014 {} + ls dist/ - # When this workflow will test other OSs, the sdist has to be created and pushed only once - - name: Create sdist + - name: '๐Ÿ” Inspect dist folder' if: matrix.type == 'User' - # if: matrix.type == 'User' && startsWith(matrix.os, 'ubuntu') + run: ls -lah dist/ + + - name: 'โฌ†๏ธ Upload artifacts' + uses: actions/upload-artifact@v2 + if: matrix.type == 'User' && matrix.ignition == 'fortress' + with: + path: dist/* + name: dist + + # ======= + # Website + # ======= + + - name: 'โฌ‡๏ธ Install website dependencies' run: | - pip3 install cmake_build_extension setuptools_scm - python3 setup.py sdist + sudo apt-get update + sudo apt-get install -y doxygen texlive-font-utils - - uses: actions/upload-artifact@v2 - if: matrix.type == 'User' + - name: '๐Ÿ” Inspect metadata' + run: sphinx-multiversion --dump-metadata docs/sphinx/ build/ + + # This is necessary because otherwise the check for uncommitted apidoc + # only detects additions, not removals. + - name: '๐Ÿ—‘๏ธ Remove apidoc folder' + run: rm -r docs/sphinx/apidoc + + - name: '๐Ÿ—๏ธ Build sphinx website' + shell: bash -i -e {0} + run: | + [[ -d build ]] && sudo chown -R $(id -u):$(id -g) build/ + cmake -S . -B build/ -DBUILD_DOCS:BOOL=ON + cmake --build build/ --target sphinx + + - name: '๐Ÿ” Check new uncommitted apidoc' + run: test $(git status --porcelain | wc -l) -eq 0 + + - name: '๐Ÿ” git status' + if: ${{ failure() }} + run: | + git status + git diff + + - name: 'โฌ†๏ธ Upload website folder' + uses: actions/upload-artifact@v2 + if: matrix.type == 'User' && matrix.ignition == 'fortress' && contains(matrix.os, 'ubuntu') + with: + path: build/html + name: website + retention-days: 1 + +# =================== + website-deployment: +# =================== + name: 'Website Deployment' + needs: [ build-and-test ] + runs-on: ubuntu-latest + steps: + + - name: 'โฌ‡๏ธ Download website folder' + uses: actions/download-artifact@v2 + with: + path: build/html + name: website + + - name: '๐Ÿ” Inspect html folder' + run: ls -lah build/html + + - name: '๐Ÿš€ Deploy' + uses: JamesIves/github-pages-deploy-action@releases/v3 + if: | + github.event_name == 'push' && + github.repository == 'robotology/gym-ignition' && + (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/devel') + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: build/html + CLEAN: true + CLEAN_EXCLUDE: '[".gitignore", ".nojekyll"]' + +# ================= + python-packaging: +# ================= + name: 'Python packaging' + needs: [ build-and-test ] + runs-on: ubuntu-latest + steps: + + - name: '๐Ÿ”๏ธ Inspect Environment' + run: | + env | grep ^GITHUB + echo "" + cat ${GITHUB_EVENT_PATH} + echo "" + env + + # Any Python3 version is ok in this job + - name: '๐Ÿ Initialize Python' + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: '๐Ÿ”€ Clone repository' + uses: actions/checkout@master + - name: '๐Ÿ”€ Download all refs' + run: git fetch --prune --unshallow + + # =============== + # Download wheels + # =============== + + - name: 'โฌ‡๏ธ Download scenario wheels' + uses: actions/download-artifact@v2 + with: + path: dist + name: dist + + - name: '๐Ÿ” Inspect dist folder' + run: ls -lah dist/ + + # =============== + # Create packages + # =============== + + # We use build to create sdist. Also pipx would work. + # https://github.com/pypa/build + # Compared to calling setup.py, the advantage of these tools is that + # they automatically handle the build dependencies. + - name: '๐Ÿ๏ธ๏ธ Install dependencies' + run: pip install build + + - name: '[๐Ÿ“ฆ|scenario]๏ธ Create sdist' + run: python -m build --sdist scenario/ -o dist/ + + - name: '[๐Ÿ“ฆ|gym-ignition]๏ธ Create sdist' + run: python -m build --sdist . + + - name: '[๐Ÿ“ฆ|gym-ignition]๏ธ Create wheel' + run: python -m build --wheel . + + # ================ + # Upload artifacts + # ================ + + - name: '๐Ÿ—‘๏ธ Remove external packages' + run: find dist/ -type f -not \( -name '*scenario-*' -o -name '*gym_ignition-*' \) -delete -print + + - name: '๐Ÿ” Check packages' + run: pipx run twine check dist/* + + - name: '๐Ÿ” Inspect dist folder' + run: ls -lah dist/ + + - name: 'โฌ†๏ธ Upload artifacts' + uses: actions/upload-artifact@v2 with: - name: packages path: dist/* + name: dist + +# ============ + upload_pypi: +# ============ + name: Publish to PyPI + needs: + - build-and-test + - python-packaging + runs-on: ubuntu-20.04 + # Devel branch produces pre-releases. + # Tagged versions produce stable releases. + + steps: + + - name: 'โฌ‡๏ธ Download Python packages' + uses: actions/download-artifact@v2 + with: + path: dist + name: dist + + - name: '๐Ÿ” Inspect dist folder' + run: ls -lah dist/ # Validate the last tag accordingly to PEP440 # From https://stackoverflow.com/a/37972030/12150968 - - name: Check PEP440 compliance + - name: '๐Ÿ“Œ Check PEP440 compliance' if: github.event_name == 'release' run: | - apt-get update - apt-get install -y source-highlight + sudo apt-get update + sudo apt-get install -y source-highlight last_tag_with_v="$(git describe --abbrev=0 --tags)" last_tag=${last_tag_with_v#v} rel_regexp='^(\d+!)?(\d+)(\.\d+)+([\.\-\_])?((a(lpha)?|b(eta)?|c|r(c|ev)?|pre(view)?)\d*)?(\.?(post|dev)\d*)?$' @@ -266,12 +525,11 @@ jobs: match=$(check-regexp ${rel_regexp} ${last_tag} | grep matches | cut -d ' ' -f 5) test $match -eq 1 && true - # The package is deployed only if there's an upstream release / prerelease, or it is upstream's devel. - - name: Publish package to PyPI + - name: 'โฌ†๏ธ Publish packages to PyPI' if: | - github.repository == 'robotology/gym-ignition' && matrix.type == 'User' && - ((github.event_name == 'release' && github.event.action == 'published' && matrix.ignition == 'dome') || - (github.event_name == 'push' && matrix.ignition == 'dome' && github.ref == 'refs/heads/devel')) + github.repository == 'robotology/gym-ignition' && + ((github.event_name == 'release' && github.event.action == 'published') || + (github.event_name == 'push' && github.ref == 'refs/heads/devel')) uses: pypa/gh-action-pypi-publish@master with: user: __token__ diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 000000000..acdae9fe6 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,74 @@ +name: Code Style + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + + clang-format: + + name: clang-format + runs-on: ubuntu-latest + + steps: + + - name: "๐Ÿ”€ Checkout repository" + uses: actions/checkout@v2 + + - name: "โฌ‡๏ธ๏ธ Install dependencies" + run: sudo apt-get install -y --no-install-recommends colordiff + + - name: "๐Ÿ“ Format C++" + uses: diegoferigo/gh-action-clang-format@main + id: format + with: + style: file + pattern: | + *.h + *.cpp + + - name: "๐Ÿ”๏ธ Inspect style diff" + if: failure() + run: printf "${{ steps.format.outputs.diff }}" | colordiff + + black: + + name: black + runs-on: ubuntu-latest + + steps: + + - name: "๐Ÿ”€ Checkout repository" + uses: actions/checkout@v2 + + - name: '๐Ÿ Initialize Python' + uses: actions/setup-python@v2 + with: + python-version: "3.8" + + - name: "๐Ÿ“ Black Code Formatter" + uses: psf/black@stable + with: + options: --check --diff --color + + isort: + + name: isort + runs-on: ubuntu-latest + + steps: + + - name: "๐Ÿ”€ Checkout repository" + uses: actions/checkout@v2 + + - name: '๐Ÿ Initialize Python' + uses: actions/setup-python@v2 + with: + python-version: "3.8" + + - name: "๐Ÿ“ isort" + uses: isort/isort-action@master + with: + configuration: --check --diff --color diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml deleted file mode 100644 index 339c65bac..000000000 --- a/.github/workflows/website.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Website - -on: - push: - tags-ignore: - - '**' - pull_request: - branches: - - master - - devel - -jobs: - website: - name: Build and Deploy - runs-on: ubuntu-20.04 - - steps: - - - uses: actions/checkout@master - with: - persist-credentials: false - - - run: git fetch --prune --unshallow - - - name: Setup Environment - run: | - echo "CC=gcc" >> $GITHUB_ENV - echo "CXX=g++" >> $GITHUB_ENV - echo "PYTHON_VERSION=3.8" >> $GITHUB_ENV - env - - - name: Inspect Environment - run: | - env | grep ^GITHUB - cat ${GITHUB_EVENT_PATH} - - - name: Setup docker image [master] - if: | - (github.event_name == 'push' && github.ref == 'refs/heads/master') || - (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') - run: | - docker run -d -i --name ci -v $(pwd):/github -w /github \ - -v /home/runner/work/_temp/:/home/runner/work/_temp/:rw \ - -e PYTHON_VERSION=$PYTHON_VERSION -e CC=$CC -e CXX=$CXX \ - diegoferigo/gym-ignition:ci-master bash - - - name: Setup docker image [devel] - if: | - (github.event_name == 'push' && github.ref != 'refs/heads/master') || - (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'devel') - run: | - docker run -d -i --name ci -v $(pwd):/github -w /github \ - -v /home/runner/work/_temp/:/home/runner/work/_temp/:rw \ - -e PYTHON_VERSION=$PYTHON_VERSION -e CC=$CC -e CXX=$CXX \ - diegoferigo/gym-ignition:ci-devel bash - - - name: Wait entrypoint - run: | - sleep 30 - docker logs ci - - - name: Install packages - shell: docker exec -i ci bash -i -e {0} - run: | - apt-get update - apt-get install -y doxygen texlive-font-utils - - - name: Install website dependecies - shell: docker exec -i ci bash -i -e {0} - run: pip3 install -e .[website] - - - name: Inspect metadata - shell: docker exec -i ci bash -i -e {0} - run: sphinx-multiversion --dump-metadata docs/sphinx/ build/ - - # This is necessary because otherwise the check for uncommitted apidoc - # only detects additions, not removals. - - name: Remove apidoc folder - shell: docker exec -i ci bash -i -e {0} - run: rm -r docs/sphinx/apidoc - - - name: Build sphinx website - shell: docker exec -i ci bash -i -e {0} - run: | - mkdir build && cd build - cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DBUILD_DOCS:BOOL=ON - cmake --build . --target sphinx - - - name: Check new uncommitted apidoc - run: test $(git status --porcelain | wc -l) -eq 0 - - - name: git status - if: ${{ failure() }} - run: | - git status - git diff - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@releases/v3 - if: | - github.event_name == 'push' && - github.repository == 'robotology/gym-ignition' && - (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/devel') - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: build/html - CLEAN: true - CLEAN_EXCLUDE: '[".gitignore", ".nojekyll"]' diff --git a/.gitignore b/.gitignore index 1e3b0d6c0..803310da2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,5 @@ __pycache__ *.egg-info *.cache .idea* -dist/* +**/dist/* .egg* diff --git a/CMakeLists.txt b/CMakeLists.txt index 24ff38ee5..e429cc1d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,220 +2,20 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. +# This file is here only to allow build systems to find the +# real CMake project that is stored in the scenario/ folder. cmake_minimum_required(VERSION 3.16) -project(Scenario VERSION 1.2.2) +project(scenario) +add_subdirectory(scenario) -# Add custom functions / macros -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +# The uninstall target resource must be included in the top-level CMakeLists +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/scenario/cmake) +include(AddUninstallTarget) + +# ============= +# DOCUMENTATION +# ============= if(BUILD_DOCS) add_subdirectory(docs) endif() - -# C++ standard -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Include useful features -include(GNUInstallDirs) - -# Build type -if(NOT CMAKE_CONFIGURATION_TYPES) - if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING - "Choose the type of build, recommended options are: Debug or Release" FORCE) - endif() - set(SCENARIO_BUILD_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo") - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${SCENARIO_BUILD_TYPES}) -endif() - -# This new build mode configures the CMake project to be compatible with the pipeline to -# create the PyPI linux wheel -include(AddNewBuildMode) -add_new_build_mode(NAME "PyPI" TEMPLATE "Release") - -# Expose shared or static compilation -set(SCENARIO_BUILD_SHARED_LIBRARY TRUE - CACHE BOOL "Compile libraries as shared libraries") - -if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "PyPI") - # Apply the user choice - set(BUILD_SHARED_LIBS ${SCENARIO_BUILD_SHARED_LIBRARY}) -else() - # Check that is Linux - if(NOT (UNIX AND NOT APPLE)) - message(FATAL_ERROR "PyPI packages can be only created for Linux at the moment") - endif() - - if(SCENARIO_BUILD_SHARED_LIBRARY) - message(WARNING "Enabling static compilation, required by the PyPI build mode") - endif() - - # Force static compilation - set(BUILD_SHARED_LIBS FALSE) -endif() - -# Use -fPIC even if statically compiled -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -# Tweak linker flags in Linux -if(UNIX AND NOT APPLE) - if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - get_filename_component(LINKER_BIN ${CMAKE_LINKER} NAME) - if("${LINKER_BIN}" STREQUAL "ld") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--unresolved-symbols=report-all") - endif() - endif() -endif() - -# Control where binaries and libraries are placed in the build folder. -# This simplifies tests running in Windows. -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") - -# Get include-what-you-use information when compiling -option(SCENARIO_USE_IWYU "Get the output of include-what-you-use" OFF) -mark_as_advanced(SCENARIO_USE_IWYU) -if(SCENARIO_USE_IWYU) - find_program(IWYU_PATH NAMES include-what-you-use iwyu) - if(IWYU_PATH) - set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_PATH}) - endif() -endif() - -# Settings for RPATH -if(NOT MSVC) - option(ENABLE_RPATH "Enable RPATH installation" TRUE) - mark_as_advanced(ENABLE_RPATH) -endif() - -# Dependencies -add_subdirectory(deps) - -if(${CMAKE_VERSION} VERSION_GREATER 3.15) - cmake_policy(SET CMP0094 NEW) -endif() - -# Find virtualenv's before system's interpreters -set(Python3_FIND_VIRTUALENV "FIRST" CACHE STRING - "Configure the detection of virtual environments") -set(Python3_FIND_VIRTUALENV_TYPES "FIRST" "ONLY" "STANDARD") -mark_as_advanced(Python3_FIND_VIRTUALENV) -set_property(CACHE Python3_FIND_VIRTUALENV PROPERTY STRINGS ${Python3_FIND_VIRTUALENV_TYPES}) - -# Find Python3 -find_package(Python3 COMPONENTS Interpreter Development REQUIRED) -message(STATUS "Using Python: ${Python3_EXECUTABLE}") - -# Select the appropriate install prefix used throughout the project. -set(SCENARIO_INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR}) -set(SCENARIO_INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR}) -set(SCENARIO_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) - -if(NOT CMAKE_BUILD_TYPE STREQUAL "PyPI") - # Add the libraries installed in the Python site-package folder - set(EXTRA_RPATH_DIRS - "${Python3_SITELIB}" - "${Python3_SITELIB}/scenario/bindings") -else() - # Add the libraries installed in the Python site-package folder - # (that in this case is CMAKE_INSTALL_PREFIX) - set(EXTRA_RPATH_DIRS - "${CMAKE_INSTALL_PREFIX}" - "${CMAKE_INSTALL_PREFIX}/scenario/bindings") -endif() - -# Configure RPATH -include(AddInstallRPATHSupport) -add_install_rpath_support( - BIN_DIRS - "${CMAKE_INSTALL_PREFIX}/${GYMPP_INSTALL_BINDIR}" - "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_BINDIR}" - LIB_DIRS - "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_LIBDIR}" - "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_LIBDIR}/scenario/plugins" - "${EXTRA_RPATH_DIRS}" - INSTALL_NAME_DIR - "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_LIBDIR}" - DEPENDS ENABLE_RPATH - USE_LINK_PATH) - -# Find a supported Ignition distribution -if(NOT IGNITION_DISTRIBUTION) - - include(FindIgnitionDistribution) - set(SUPPORTED_IGNITION_DISTRIBUTIONS "Dome" "Citadel") - - foreach(distribution IN LISTS SUPPORTED_IGNITION_DISTRIBUTIONS) - - find_ignition_distribution( - CODENAME ${distribution} - PACKAGES - ignition-gazebo - REQUIRED FALSE) - - if(${${distribution}_FOUND}) - message(STATUS "Found Ignition ${distribution}") - - # Select Ignition distribution - set(IGNITION_DISTRIBUTION "${distribution}" CACHE - STRING "The Ignition distribution found in the system") - set_property(CACHE IGNITION_DISTRIBUTION PROPERTY - STRINGS ${SUPPORTED_IGNITION_DISTRIBUTIONS}) - - break() - endif() - - endforeach() - -endif() - -if(NOT IGNITION_DISTRIBUTION OR "${IGNITION_DISTRIBUTION}" STREQUAL "") - set(USE_IGNITION FALSE) -else() - set(USE_IGNITION TRUE) -endif() - -option(SCENARIO_USE_IGNITION - "Build C++ code depending on Ignition" - ${USE_IGNITION}) - -# Fail if Ignition is enabled but no compatible distribution was found -if(SCENARIO_USE_IGNITION AND "${IGNITION_DISTRIBUTION}" STREQUAL "") - message(FATAL_ERROR "Failed to find a compatible Ignition Gazebo distribution") -endif() - -# Alias the targets -if(SCENARIO_USE_IGNITION) - include(ImportTargets${IGNITION_DISTRIBUTION}) -endif() - -# Helper for exporting targets -include(InstallBasicPackageFiles) - -# ========= -# SCENARI/O -# ========= - -add_subdirectory(cpp/scenario) - -# ======== -# BINDINGS -# ======== - -# Require to find Ignition libraries when packaging for PyPI -if(CMAKE_BUILD_TYPE STREQUAL "PyPI" AND NOT USE_IGNITION) - message(FATAL_ERROR "Found no Ignition distribution for PyPI package") -endif() - -find_package(SWIG 4.0 QUIET) -option(SCENARIO_ENABLE_BINDINGS "Enable SWIG bindings" ${SWIG_FOUND}) - -if(SCENARIO_ENABLE_BINDINGS) - add_subdirectory(bindings) -endif() - -# Add unistall target -include(AddUninstallTarget) diff --git a/README.md b/README.md index d45365f74..fa6d94cfc 100644 --- a/README.md +++ b/README.md @@ -2,36 +2,10 @@

gym-ignition

-

-Description -โ€ข -Setup -โ€ข -Citation -โ€ข -Website -

-
-


- - - - - -
General - - C++ Standard - - - Size - - - Size - -
CI/CD CICD @@ -45,7 +19,6 @@
gym-ignition @@ -66,62 +39,61 @@
-


+|||| +|:---:|:---:|:---:| +| ![][pendulum] | ![][panda] | ![][icub] | + +[icub]: https://user-images.githubusercontent.com/469199/99262746-9e021a80-281e-11eb-9df1-d70134b0801a.png +[panda]: https://user-images.githubusercontent.com/469199/99263111-0cdf7380-281f-11eb-9cfe-338b2aae0503.png +[pendulum]: https://user-images.githubusercontent.com/469199/99262383-321fb200-281e-11eb-89cc-cc31f590daa3.png + ## Description **gym-ignition** is a framework to create **reproducible robotics environments** for reinforcement learning research. -The project consists of the following components: +It is based on the [ScenarIO](scenario/) project which provides the low-level APIs to interface with the Ignition Gazebo simulator. +By default, RL environments share a lot of boilerplate code, e.g. for initializing the simulator or structuring the classes +to expose the `gym.Env` interface. +Gym-ignition provides the [`Task`](python/gym_ignition/base/task.py) and [`Runtime`](python/gym_ignition/base/runtime.py) +abstractions that help you focusing on the development of the decision-making logic rather than engineering. +It includes [randomizers](python/gym_ignition/randomizers) to simplify the implementation of domain randomization +of models, physics, and tasks. +Gym-ignition also provides powerful dynamics algorithms compatible with both fixed-base and floating-based robots by +exploiting [robotology/idyntree](https://github.com/robotology/idyntree/) and exposing +[high-level functionalities](python/gym_ignition/rbd/idyntree). -- [**`ScenarI/O`**](cpp/scenario/core): - *Scene Interfaces for Robot Input / Output* is a C++ abstraction layer to interact with simulated and real robots. -- [**`Gazebo ScenarI/O`**](cpp/scenario/gazebo): - Implementation of the ScenarI/O interfaces to interact with the [Ignition Gazebo](https://ignitionrobotics.org) simulator. - We provide Python bindings with functionalities comparable to popular alternatives like - [pybullet](https://github.com/bulletphysics/bullet3) and [mujoco-py](https://github.com/openai/mujoco-py). -- [**`gym_ignition`**](python/gym_ignition): - A Python package with the tooling to create OpenAI Gym environments for robot learning. - It provides abstractions like `Task` and `Runtime` to help developing environments that can be executed transparently - on all the ScenarI/O implementations (different simulators, real robots, ...). - The package also contains resources for inverse kinematics and multi-body dynamics supporting floating-based robots - based on the [iDynTree](https://github.com/robotology/idyntree) library. -- [**`gym_ignition_environments`**](python/gym_ignition_environments): - Demo environments created with `gym_ignition` and [`gym-ignition-models`](https://github.com/dic-iit/gym-ignition-models) - that show the recommended structure. - -This project provides the complete implementation of ScenarI/O for the Ignition Gazebo simulator. -We expose all the physics engines supported by Ignition Gazebo. -Currently, the default and only physics engine is [DART](https://github.com/dartsim/dart). +Gym-ignition does not provide out-of-the-box environments ready to be used. +Rather, its aim is simplifying and streamlining their development. +Nonetheless, for illustrative purpose, it includes canonical examples in the +[`gym_ignition_environments`](python/gym_ignition_environments) package. -We are currently working on backends based on robotic middleware to transparently execute the environments developed -with `gym_ignition` on real robots. +Visit the [website][website] for more information about the project. -If you're interested to know the reasons why we started developing gym-ignition and why we selected Ignition Gazebo for -our simulations, visit the _Motivations_ section of the [website](https://robotology.github.io/gym-ignition). +[website]: https://robotology.github.io/gym-ignition -## Setup +## Installation -1. Install the latest Ignition suite following the [official instructions](https://ignitionrobotics.org/docs/dome). -1. Execute `pip install gym-ignition`, preferably in a virtual environment. +1. First, follow the installation instructions of [ScenarIO](scenario/). +2. `pip install gym-ignition`, preferably in a [virtual environment](https://docs.python.org/3.8/tutorial/venv.html). -**Note**: `gym-ignition` currently only supports the latest version of the ignition suite. For more information on supported versions please refer to the [Support Policy](https://robotology.github.io/gym-ignition/master/installation/support_policy.html). +## Contributing +You can visit our community forum hosted in [GitHub Discussions](https://github.com/robotology/gym-ignition/discussions). +Even without coding skills, replying user's questions is a great way of contributing. +If you use gym-ignition in your application and want to show it off, visit the +[Show and tell](https://github.com/robotology/gym-ignition/discussions/categories/show-and-tell) section! +You can advertise there your environments created with gym-ignition. -Then, for some simple examples, visit the _Getting Started_ section of the [website](https://robotology.github.io/gym-ignition). +Pull requests are welcome. -You can decide to install only the C++ resources if you are not interested in using Python. -We also offer a constantly updated pre-release channel with the last development updates. -You can find all the details about the different types of installations we support in the [website](https://robotology.github.io/gym-ignition). - -|||| -|:---:|:---:|:---:| -| ![](https://user-images.githubusercontent.com/469199/99262383-321fb200-281e-11eb-89cc-cc31f590daa3.png) | ![](https://user-images.githubusercontent.com/469199/99263111-0cdf7380-281f-11eb-9cfe-338b2aae0503.png) | ![](https://user-images.githubusercontent.com/469199/99262746-9e021a80-281e-11eb-9df1-d70134b0801a.png) | +For major changes, please open a [discussion](https://github.com/robotology/gym-ignition/discussions) +first to propose what you would like to change. ## Citation -``` +```bibtex @INPROCEEDINGS{ferigo2020gymignition, title={Gym-Ignition: Reproducible Robotic Simulations for Reinforcement Learning}, author={D. {Ferigo} and S. {Traversaro} and G. {Metta} and D. {Pucci}}, @@ -132,6 +104,10 @@ You can find all the details about the different types of installations we suppo } ``` +## License + +[LGPL v2.1](https://choosealicense.com/licenses/lgpl-2.1/) or any later version. + --- -**Disclaimer:** Gym-Ignition is an independent project and is not related by any means to OpenAI and Open Robotics. +**Disclaimer:** Gym-ignition is an independent project and is not related by any means to OpenAI and Open Robotics. diff --git a/cmake/FindPython/Support.cmake b/cmake/FindPython/Support.cmake deleted file mode 100644 index a80640690..000000000 --- a/cmake/FindPython/Support.cmake +++ /dev/null @@ -1,2586 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -# Taken from: https://github.com/Kitware/CMake/blob/v3.17.0/Modules/FindPython/Support.cmake - -# -# This file is a "template" file used by various FindPython modules. -# - -cmake_policy (GET CMP0094 _${_PYTHON_PREFIX}_LOOKUP_POLICY) - -cmake_policy (VERSION 3.7) - -if (_${_PYTHON_PREFIX}_LOOKUP_POLICY) - cmake_policy (SET CMP0094 ${_${_PYTHON_PREFIX}_LOOKUP_POLICY}) -endif() - -# -# Initial configuration -# -if (NOT DEFINED _PYTHON_PREFIX) - message (FATAL_ERROR "FindPython: INTERNAL ERROR") -endif() -if (NOT DEFINED _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - message (FATAL_ERROR "FindPython: INTERNAL ERROR") -endif() -if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL 3) - set(_${_PYTHON_PREFIX}_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) -elseif (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL 2) - set(_${_PYTHON_PREFIX}_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) -else() - message (FATAL_ERROR "FindPython: INTERNAL ERROR") -endif() - -get_property(_${_PYTHON_PREFIX}_CMAKE_ROLE GLOBAL PROPERTY CMAKE_ROLE) - - -# -# helper commands -# -macro (_PYTHON_DISPLAY_FAILURE _PYTHON_MSG) - if (${_PYTHON_PREFIX}_FIND_REQUIRED) - message (FATAL_ERROR "${_PYTHON_MSG}") - else() - if (NOT ${_PYTHON_PREFIX}_FIND_QUIETLY) - message(STATUS "${_PYTHON_MSG}") - endif () - endif() - - set (${_PYTHON_PREFIX}_FOUND FALSE) - string (TOUPPER "${_PYTHON_PREFIX}" _${_PYTHON_PREFIX}_UPPER_PREFIX) - set (${_PYTHON_UPPER_PREFIX}_FOUND FALSE) - return() -endmacro() - - -function (_PYTHON_MARK_AS_INTERNAL) - foreach (var IN LISTS ARGV) - if (DEFINED CACHE{${var}}) - set_property (CACHE ${var} PROPERTY TYPE INTERNAL) - endif() - endforeach() -endfunction() - - -macro (_PYTHON_SELECT_LIBRARY_CONFIGURATIONS _PYTHON_BASENAME) - if(NOT DEFINED ${_PYTHON_BASENAME}_LIBRARY_RELEASE) - set(${_PYTHON_BASENAME}_LIBRARY_RELEASE "${_PYTHON_BASENAME}_LIBRARY_RELEASE-NOTFOUND") - endif() - if(NOT DEFINED ${_PYTHON_BASENAME}_LIBRARY_DEBUG) - set(${_PYTHON_BASENAME}_LIBRARY_DEBUG "${_PYTHON_BASENAME}_LIBRARY_DEBUG-NOTFOUND") - endif() - - get_property(_PYTHON_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if (${_PYTHON_BASENAME}_LIBRARY_DEBUG AND ${_PYTHON_BASENAME}_LIBRARY_RELEASE AND - NOT ${_PYTHON_BASENAME}_LIBRARY_DEBUG STREQUAL ${_PYTHON_BASENAME}_LIBRARY_RELEASE AND - (_PYTHON_isMultiConfig OR CMAKE_BUILD_TYPE)) - # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for - # single-config generators, set optimized and debug libraries - set (${_PYTHON_BASENAME}_LIBRARY "") - foreach (_PYTHON_libname IN LISTS ${_PYTHON_BASENAME}_LIBRARY_RELEASE ) - list( APPEND ${_PYTHON_BASENAME}_LIBRARY optimized "${_PYTHON_libname}" ) - endforeach() - foreach (_PYTHON_libname IN LISTS ${_PYTHON_BASENAME}_LIBRARY_DEBUG ) - list( APPEND ${_PYTHON_BASENAME}_LIBRARY debug "${_PYTHON_libname}" ) - endforeach() - elseif (${_PYTHON_BASENAME}_LIBRARY_RELEASE) - set (${_PYTHON_BASENAME}_LIBRARY "${${_PYTHON_BASENAME}_LIBRARY_RELEASE}") - elseif (${_PYTHON_BASENAME}_LIBRARY_DEBUG) - set (${_PYTHON_BASENAME}_LIBRARY "${${_PYTHON_BASENAME}_LIBRARY_DEBUG}") - else() - set (${_PYTHON_BASENAME}_LIBRARY "${_PYTHON_BASENAME}_LIBRARY-NOTFOUND") - endif() - - set (${_PYTHON_BASENAME}_LIBRARIES "${${_PYTHON_BASENAME}_LIBRARY}") -endmacro() - - -macro (_PYTHON_FIND_FRAMEWORKS) - set (${_PYTHON_PREFIX}_FRAMEWORKS) - if (CMAKE_HOST_APPLE OR APPLE) - file(TO_CMAKE_PATH "$ENV{CMAKE_FRAMEWORK_PATH}" _pff_CMAKE_FRAMEWORK_PATH) - set (_pff_frameworks ${CMAKE_FRAMEWORK_PATH} - ${_pff_CMAKE_FRAMEWORK_PATH} - ~/Library/Frameworks - /usr/local/Frameworks - ${CMAKE_SYSTEM_FRAMEWORK_PATH}) - list (REMOVE_DUPLICATES _pff_frameworks) - foreach (_pff_framework IN LISTS _pff_frameworks) - if (EXISTS ${_pff_framework}/Python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}.framework) - list (APPEND ${_PYTHON_PREFIX}_FRAMEWORKS ${_pff_framework}/Python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}.framework) - endif() - if (EXISTS ${_pff_framework}/Python.framework) - list (APPEND ${_PYTHON_PREFIX}_FRAMEWORKS ${_pff_framework}/Python.framework) - endif() - endforeach() - unset (_pff_frameworks) - unset (_pff_framework) - endif() -endmacro() - -function (_PYTHON_GET_FRAMEWORKS _PYTHON_PGF_FRAMEWORK_PATHS _PYTHON_VERSION) - set (_PYTHON_FRAMEWORK_PATHS) - foreach (_PYTHON_FRAMEWORK IN LISTS ${_PYTHON_PREFIX}_FRAMEWORKS) - list (APPEND _PYTHON_FRAMEWORK_PATHS - "${_PYTHON_FRAMEWORK}/Versions/${_PYTHON_VERSION}") - endforeach() - set (${_PYTHON_PGF_FRAMEWORK_PATHS} ${_PYTHON_FRAMEWORK_PATHS} PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_REGISTRIES _PYTHON_PGR_REGISTRY_PATHS _PYTHON_VERSION) - string (REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PYTHON_VERSION}) - set (${_PYTHON_PGR_REGISTRY_PATHS} - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_PYTHON_VERSION}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_PYTHON_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] - PARENT_SCOPE) -endfunction() - - -function (_PYTHON_GET_ABIFLAGS _PGABIFLAGS) - set (abiflags) - list (GET _${_PYTHON_PREFIX}_FIND_ABI 0 pydebug) - list (GET _${_PYTHON_PREFIX}_FIND_ABI 1 pymalloc) - list (GET _${_PYTHON_PREFIX}_FIND_ABI 2 unicode) - - if (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ANY") - set (abiflags "mu" "m" "u" "") - elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ON") - set (abiflags "mu" "u") - elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "OFF") - set (abiflags "m" "") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY") - set (abiflags "mu" "m") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ON") - set (abiflags "mu") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "OFF") - set (abiflags "m") - elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY") - set (abiflags "u" "") - elseif (pymalloc STREQUAL "OFF" AND unicode STREQUAL "ON") - set (abiflags "u") - endif() - - if (pydebug STREQUAL "ON") - if (abiflags) - list (TRANSFORM abiflags PREPEND "d") - else() - set (abiflags "d") - endif() - elseif (pydebug STREQUAL "ANY") - if (abiflags) - set (flags "${abiflags}") - list (TRANSFORM flags PREPEND "d") - list (APPEND abiflags "${flags}") - else() - set (abiflags "" "d") - endif() - endif() - - set (${_PGABIFLAGS} "${abiflags}" PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_PATH_SUFFIXES _PYTHON_PGPS_PATH_SUFFIXES) - cmake_parse_arguments (PARSE_ARGV 1 _PGPS "LIBRARY;INCLUDE" "VERSION" "") - - if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS) - set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}") - else() - set (abi "mu" "m" "u" "") - endif() - - set (path_suffixes) - if (_PGPS_LIBRARY) - if (CMAKE_LIBRARY_ARCHITECTURE) - list (APPEND path_suffixes lib/${CMAKE_LIBRARY_ARCHITECTURE}) - endif() - list (APPEND path_suffixes lib libs) - - if (CMAKE_LIBRARY_ARCHITECTURE) - set (suffixes "${abi}") - if (suffixes) - list (TRANSFORM suffixes PREPEND "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") - list (TRANSFORM suffixes APPEND "-${CMAKE_LIBRARY_ARCHITECTURE}") - else() - set (suffixes "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}-${CMAKE_LIBRARY_ARCHITECTURE}") - endif() - list (APPEND path_suffixes ${suffixes}) - endif() - set (suffixes "${abi}") - if (suffixes) - list (TRANSFORM suffixes PREPEND "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") - else() - set (suffixes "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") - endif() - list (APPEND path_suffixes ${suffixes}) - elseif (_PGPS_INCLUDE) - set (suffixes "${abi}") - if (suffixes) - list (TRANSFORM suffixes PREPEND "include/python${_PGPS_VERSION}") - else() - set (suffixes "include/python${_PGPS_VERSION}") - endif() - list (APPEND path_suffixes ${suffixes} include) - endif() - - set (${_PYTHON_PGPS_PATH_SUFFIXES} ${path_suffixes} PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_NAMES _PYTHON_PGN_NAMES) - cmake_parse_arguments (PARSE_ARGV 1 _PGN "POSIX;EXECUTABLE;CONFIG;LIBRARY;WIN32;DEBUG" "VERSION" "") - - set (names) - - if (_PGN_WIN32) - string (REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PGN_VERSION}) - - set (name python${_PYTHON_VERSION_NO_DOTS}) - if (_PGN_DEBUG) - string (APPEND name "_d") - endif() - - list (APPEND names "${name}") - endif() - - if (_PGN_POSIX) - if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS) - set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}") - else() - if (_PGN_EXECUTABLE OR _PGN_CONFIG) - set (abi "") - else() - set (abi "mu" "m" "u" "") - endif() - endif() - - if (abi) - if (_PGN_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - set (abinames "${abi}") - list (TRANSFORM abinames PREPEND "${CMAKE_LIBRARY_ARCHITECTURE}-python${_PGN_VERSION}") - list (TRANSFORM abinames APPEND "-config") - list (APPEND names ${abinames}) - endif() - set (abinames "${abi}") - list (TRANSFORM abinames PREPEND "python${_PGN_VERSION}") - if (_PGN_CONFIG) - list (TRANSFORM abinames APPEND "-config") - endif() - list (APPEND names ${abinames}) - else() - if (_PGN_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - set (abinames "${CMAKE_LIBRARY_ARCHITECTURE}-python${_PGN_VERSION}") - endif() - list (APPEND abinames "python${_PGN_VERSION}") - if (_PGN_CONFIG) - list (TRANSFORM abinames APPEND "-config") - endif() - list (APPEND names ${abinames}) - endif() - endif() - - set (${_PYTHON_PGN_NAMES} ${names} PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_CONFIG_VAR _PYTHON_PGCV_VALUE NAME) - unset (${_PYTHON_PGCV_VALUE} PARENT_SCOPE) - - if (NOT NAME MATCHES "^(PREFIX|ABIFLAGS|CONFIGDIR|INCLUDES|LIBS|SOABI)$") - return() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - if (NAME STREQUAL "SOABI") - set (config_flag "--extension-suffix") - else() - set (config_flag "--${NAME}") - endif() - string (TOLOWER "${config_flag}" config_flag) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" ${config_flag} - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - if (NAME STREQUAL "INCLUDES") - # do some clean-up - string (REGEX MATCHALL "(-I|-iwithsysroot)[ ]*[^ ]+" _values "${_values}") - string (REGEX REPLACE "(-I|-iwithsysroot)[ ]*" "" _values "${_values}") - list (REMOVE_DUPLICATES _values) - elseif (NAME STREQUAL "SOABI") - # clean-up: remove prefix character and suffix - string (REGEX REPLACE "^[.-](.+)(${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.(so|pyd))$" "\\1" _values "${_values}") - endif() - endif() - endif() - - if (_${_PYTHON_PREFIX}_EXECUTABLE AND NOT CMAKE_CROSSCOMPILING) - if (NAME STREQUAL "PREFIX") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; from distutils import sysconfig; sys.stdout.write(';'.join([sysconfig.PREFIX,sysconfig.EXEC_PREFIX,sysconfig.BASE_EXEC_PREFIX]))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - list (REMOVE_DUPLICATES _values) - endif() - elseif (NAME STREQUAL "INCLUDES") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; from distutils import sysconfig; sys.stdout.write(';'.join([sysconfig.get_python_inc(plat_specific=True),sysconfig.get_python_inc(plat_specific=False)]))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - endif() - elseif (NAME STREQUAL "SOABI") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; from distutils import sysconfig;sys.stdout.write(';'.join([sysconfig.get_config_var('SOABI') or '',sysconfig.get_config_var('EXT_SUFFIX') or '']))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _soabi - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - else() - list (GET _soabi 0 _values) - if (NOT _values) - # try to compute SOABI from EXT_SUFFIX - list (GET _soabi 1 _values) - if (_values) - # clean-up: remove prefix character and suffix - string (REGEX REPLACE "^[.-](.+)(${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.(so|pyd))$" "\\1" _values "${_values}") - endif() - endif() - endif() - else() - set (config_flag "${NAME}") - if (NAME STREQUAL "CONFIGDIR") - set (config_flag "LIBPL") - endif() - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_config_var('${config_flag}'))" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _values - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_result) - unset (_values) - endif() - endif() - endif() - - if (config_flag STREQUAL "ABIFLAGS") - set (${_PYTHON_PGCV_VALUE} "${_values}" PARENT_SCOPE) - return() - endif() - - if (NOT _values OR _values STREQUAL "None") - return() - endif() - - if (NAME STREQUAL "LIBS") - # do some clean-up - string (REGEX MATCHALL "-(l|framework)[ ]*[^ ]+" _values "${_values}") - # remove elements relative to python library itself - list (FILTER _values EXCLUDE REGEX "-lpython") - list (REMOVE_DUPLICATES _values) - endif() - - set (${_PYTHON_PGCV_VALUE} "${_values}" PARENT_SCOPE) -endfunction() - -function (_PYTHON_GET_VERSION) - cmake_parse_arguments (PARSE_ARGV 0 _PGV "LIBRARY;INCLUDE" "PREFIX" "") - - unset (${_PGV_PREFIX}VERSION PARENT_SCOPE) - unset (${_PGV_PREFIX}VERSION_MAJOR PARENT_SCOPE) - unset (${_PGV_PREFIX}VERSION_MINOR PARENT_SCOPE) - unset (${_PGV_PREFIX}VERSION_PATCH PARENT_SCOPE) - unset (${_PGV_PREFIX}ABI PARENT_SCOPE) - - if (_PGV_LIBRARY) - # retrieve version and abi from library name - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # extract version from library name - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "python([23])([0-9]+)") - set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) - elseif (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "python([23])\\.([0-9]+)([dmu]*)") - set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE) - set (${_PGV_PREFIX}ABI "${CMAKE_MATCH_3}" PARENT_SCOPE) - endif() - endif() - else() - if (_${_PYTHON_PREFIX}_INCLUDE_DIR) - # retrieve version from header file - file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/patchlevel.h" version - REGEX "^#define[ \t]+PY_VERSION[ \t]+\"[^\"]+\"") - string (REGEX REPLACE "^#define[ \t]+PY_VERSION[ \t]+\"([^\"]+)\".*" "\\1" - version "${version}") - string (REGEX MATCHALL "[0-9]+" versions "${version}") - list (GET versions 0 version_major) - list (GET versions 1 version_minor) - list (GET versions 2 version_patch) - - set (${_PGV_PREFIX}VERSION "${version_major}.${version_minor}" PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MAJOR ${version_major} PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_MINOR ${version_minor} PARENT_SCOPE) - set (${_PGV_PREFIX}VERSION_PATCH ${version_patch} PARENT_SCOPE) - - # compute ABI flags - if (version_major VERSION_GREATER 2) - file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/pyconfig.h" config REGEX "(Py_DEBUG|WITH_PYMALLOC|Py_UNICODE_SIZE|MS_WIN32)") - set (abi) - if (config MATCHES "#[ ]*define[ ]+MS_WIN32") - # ABI not used on Windows - set (abi "") - else() - if (config MATCHES "#[ ]*define[ ]+Py_DEBUG[ ]+1") - string (APPEND abi "d") - endif() - if (config MATCHES "#[ ]*define[ ]+WITH_PYMALLOC[ ]+1") - string (APPEND abi "m") - endif() - if (config MATCHES "#[ ]*define[ ]+Py_UNICODE_SIZE[ ]+4") - string (APPEND abi "u") - endif() - set (${_PGV_PREFIX}ABI "${abi}" PARENT_SCOPE) - endif() - else() - # ABI not supported - set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) - endif() - endif() - endif() -endfunction() - - -function (_PYTHON_VALIDATE_INTERPRETER) - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - return() - endif() - - cmake_parse_arguments (PARSE_ARGV 0 _PVI "EXACT;CHECK_EXISTS" "" "") - if (_PVI_UNPARSED_ARGUMENTS) - set (expected_version ${_PVI_UNPARSED_ARGUMENTS}) - else() - unset (expected_version) - endif() - - if (_PVI_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_EXECUTABLE}") - # interpreter does not exist anymore - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot find the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - - # validate ABI compatibility - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write(sys.abiflags)" - RESULT_VARIABLE result - OUTPUT_VARIABLE abi - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result) - # assume ABI is not supported - set (abi "") - endif() - if (NOT abi IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # incompatible ABI - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong ABI for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - endif() - - get_filename_component (python_name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME) - - if (expected_version AND NOT python_name STREQUAL "pythonexpectedversion{abi}${CMAKE_EXECUTABLE_SUFFIX}") - # executable found must have a specific version - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:2]]))" - RESULT_VARIABLE result - OUTPUT_VARIABLE version - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result) - # interpreter is not usable - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - else() - if (_PVI_EXACT AND NOT version VERSION_EQUAL expected_version) - # interpreter has wrong version - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - else() - # check that version is OK - string(REGEX REPLACE "^([0-9]+)\\..*$" "\\1" major_version "${version}") - string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${expected_version}") - if (NOT major_version VERSION_EQUAL expected_major_version - OR NOT version VERSION_GREATER_EQUAL expected_version) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - endif() - endif() - endif() - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - return() - endif() - else() - if (NOT python_name STREQUAL "python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}${CMAKE_EXECUTABLE_SUFFIX}") - # executable found do not have version in name - # ensure major version is OK - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write(str(sys.version_info[0]))" - RESULT_VARIABLE result - OUTPUT_VARIABLE version - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result OR NOT version EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - # interpreter not usable or has wrong major version - if (result) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - else() - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong major version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - endif() - endif() - - if (CMAKE_SIZEOF_VOID_P AND "Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND NOT CMAKE_CROSSCOMPILING) - # In this case, interpreter must have same architecture as environment - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys, struct; sys.stdout.write(str(struct.calcsize(\"P\")))" - RESULT_VARIABLE result - OUTPUT_VARIABLE size - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (result OR NOT size EQUAL CMAKE_SIZEOF_VOID_P) - # interpreter not usable or has wrong architecture - if (result) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - else() - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong architecture for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") - return() - endif() - endif() -endfunction() - - -function (_PYTHON_VALIDATE_COMPILER expected_version) - if (NOT _${_PYTHON_PREFIX}_COMPILER) - return() - endif() - - cmake_parse_arguments (_PVC "EXACT;CHECK_EXISTS" "" "" ${ARGN}) - if (_PVC_UNPARSED_ARGUMENTS) - set (major_version FALSE) - set (expected_version ${_PVC_UNPARSED_ARGUMENTS}) - else() - set (major_version TRUE) - set (expected_version ${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}) - set (_PVC_EXACT TRUE) - endif() - - if (_PVC_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_COMPILER}") - # Compiler does not exist anymore - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot find the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") - return() - endif() - - # retrieve python environment version from compiler - set (working_dir "CMAKECURRENTBINARYDIR{CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") - if (major_version) - # check only major version - file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write(str(sys.version_info[0]))") - else() - file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:2]]))\n") - endif() - execute_process (COMMAND "${_${_PYTHON_PREFIX}_COMPILER}" /target:exe /embed "${working_dir}/version.py" - WORKING_DIRECTORY "${working_dir}" - OUTPUT_QUIET - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process (COMMAND "${working_dir}/version" - WORKING_DIRECTORY "${working_dir}" - RESULT_VARIABLE result - OUTPUT_VARIABLE version - ERROR_QUIET) - file (REMOVE_RECURSE "${_${_PYTHON_PREFIX}_VERSION_DIR}") - - if (result OR (_PVC_EXACT AND NOT version VERSION_EQUAL expected_version) OR (version VERSION_LESS expected_version)) - # Compiler not usable or has wrong version - if (result) - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot use the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - else() - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") - endif() -endfunction() - - -function (_PYTHON_VALIDATE_LIBRARY) - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - return() - endif() - - cmake_parse_arguments (PARSE_ARGV 0 _PVL "EXACT;CHECK_EXISTS" "" "") - if (_PVL_UNPARSED_ARGUMENTS) - set (expected_version ${_PVL_UNPARSED_ARGUMENTS}) - else() - unset (expected_version) - endif() - - if (_PVL_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - # library does not exist anymore - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - if (WIN32) - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_DEBUG PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND") - endif() - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - return() - endif() - - # retrieve version and abi from library name - _python_get_version (LIBRARY PREFIX lib_) - - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT lib_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # incompatible ABI - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - else() - if (expected_version) - if ((_PVL_EXACT AND NOT lib_VERSION VERSION_EQUAL expected_version) OR (lib_VERSION VERSION_LESS expected_version)) - # library has wrong version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - endif() - else() - if (NOT lib_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - # library has wrong major version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong major version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - endif() - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - if (WIN32) - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_DEBUG PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND") - endif() - unset (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE CACHE) - unset (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG CACHE) - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() -endfunction() - - -function (_PYTHON_VALIDATE_INCLUDE_DIR) - if (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - return() - endif() - - cmake_parse_arguments (PARSE_ARGV 0 _PVID "EXACT;CHECK_EXISTS" "" "") - if (_PVID_UNPARSED_ARGUMENTS) - set (expected_version ${_PVID_UNPARSED_ARGUMENTS}) - else() - unset (expected_version) - endif() - - if (_PVID_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - # include file does not exist anymore - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - return() - endif() - - # retrieve version from header file - _python_get_version (INCLUDE PREFIX inc_) - - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT inc_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # incompatible ABI - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - else() - if (expected_version) - if ((_PVID_EXACT AND NOT inc_VERSION VERSION_EQUAL expected_version) OR (inc_VERSION VERSION_LESS expected_version)) - # include dir has wrong version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() - else() - if (NOT inc_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - # include dir has wrong major version - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong major version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() - endif() - endif() -endfunction() - - -function (_PYTHON_FIND_RUNTIME_LIBRARY _PYTHON_LIB) - string (REPLACE "_RUNTIME" "" _PYTHON_LIB "${_PYTHON_LIB}") - # look at runtime part on systems supporting it - if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR - (CMAKE_SYSTEM_NAME MATCHES "MSYS|CYGWIN" - AND ${_PYTHON_LIB} MATCHES "${CMAKE_IMPORT_LIBRARY_SUFFIX}$")) - set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX}) - # MSYS has a special syntax for runtime libraries - if (CMAKE_SYSTEM_NAME MATCHES "MSYS") - list (APPEND CMAKE_FIND_LIBRARY_PREFIXES "msys-") - endif() - find_library (${ARGV}) - endif() -endfunction() - - -function (_PYTHON_SET_LIBRARY_DIRS _PYTHON_SLD_RESULT) - unset (_PYTHON_DIRS) - set (_PYTHON_LIBS ${ARGN}) - foreach (_PYTHON_LIB IN LISTS _PYTHON_LIBS) - if (${_PYTHON_LIB}) - get_filename_component (_PYTHON_DIR "${${_PYTHON_LIB}}" DIRECTORY) - list (APPEND _PYTHON_DIRS "${_PYTHON_DIR}") - endif() - endforeach() - if (_PYTHON_DIRS) - list (REMOVE_DUPLICATES _PYTHON_DIRS) - endif() - set (${_PYTHON_SLD_RESULT} ${_PYTHON_DIRS} PARENT_SCOPE) -endfunction() - - -# If major version is specified, it must be the same as internal major version -if (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR - AND NOT ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong major version specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}\", but expected major version is \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") -endif() - - -# handle components -if (NOT ${_PYTHON_PREFIX}_FIND_COMPONENTS) - set (${_PYTHON_PREFIX}_FIND_COMPONENTS Interpreter) - set (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter TRUE) -endif() -if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND ${_PYTHON_PREFIX}_FIND_COMPONENTS "Interpreter" "Development") - list (REMOVE_DUPLICATES ${_PYTHON_PREFIX}_FIND_COMPONENTS) -endif() -foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development NumPy) - set (${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_FOUND FALSE) -endforeach() -unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) - -# Set versions to search -## default: search any version -set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSIONS}) - -if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT GREATER 1) - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}) - else() - unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) - # add all compatible versions - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS) - if (_${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION) - list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION}) - endif() - endforeach() - endif() -endif() - -# Set ABIs to search -## default: search any ABI -if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_LESS 3) - # ABI not supported - unset (_${_PYTHON_PREFIX}_FIND_ABI) - set (_${_PYTHON_PREFIX}_ABIFLAGS "") -else() - unset (_${_PYTHON_PREFIX}_FIND_ABI) - unset (_${_PYTHON_PREFIX}_ABIFLAGS) - if (DEFINED ${_PYTHON_PREFIX}_FIND_ABI) - # normalization - string (TOUPPER "${${_PYTHON_PREFIX}_FIND_ABI}" _${_PYTHON_PREFIX}_FIND_ABI) - list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(TRUE|Y(ES)?|1)$" "ON") - list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(FALSE|N(O)?|0)$" "OFF") - if (NOT _${_PYTHON_PREFIX}_FIND_ABI MATCHES "^(ON|OFF|ANY);(ON|OFF|ANY);(ON|OFF|ANY)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_ABI}: invalid value for '${_PYTHON_PREFIX}_FIND_ABI'. Ignore it") - unset (_${_PYTHON_PREFIX}_FIND_ABI) - endif() - _python_get_abiflags (_${_PYTHON_PREFIX}_ABIFLAGS) - endif() -endif() -unset (${_PYTHON_PREFIX}_SOABI) - -# Define lookup strategy -if (_${_PYTHON_PREFIX}_LOOKUP_POLICY STREQUAL "NEW") - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "LOCATION") -else() - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "VERSION") -endif() -if (DEFINED ${_PYTHON_PREFIX}_FIND_STRATEGY) - if (NOT ${_PYTHON_PREFIX}_FIND_STRATEGY MATCHES "^(VERSION|LOCATION)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_STRATEGY}: invalid value for '${_PYTHON_PREFIX}_FIND_STRATEGY'. 'VERSION' or 'LOCATION' expected.") - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "VERSION") - else() - set (_${_PYTHON_PREFIX}_FIND_STRATEGY "${${_PYTHON_PREFIX}_FIND_STRATEGY}") - endif() -endif() - -# Python and Anaconda distributions: define which architectures can be used -if (CMAKE_SIZEOF_VOID_P) - # In this case, search only for 64bit or 32bit - math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") - set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) -else() - # architecture unknown, search for both 64bit and 32bit - set (_${_PYTHON_PREFIX}_ARCH 64) - set (_${_PYTHON_PREFIX}_ARCH2 32) -endif() - -# IronPython support -if (CMAKE_SIZEOF_VOID_P) - # In this case, search only for 64bit or 32bit - math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") - set (_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES ipy${_${_PYTHON_PREFIX}_ARCH} ipy) -else() - # architecture unknown, search for natural interpreter - set (_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES ipy) -endif() -set (_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES net45 net40) - -# Apple frameworks handling -_python_find_frameworks () - -set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK "FIRST") - -if (DEFINED ${_PYTHON_PREFIX}_FIND_FRAMEWORK) - if (NOT ${_PYTHON_PREFIX}_FIND_FRAMEWORK MATCHES "^(FIRST|LAST|NEVER)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_FRAMEWORK}: invalid value for '${_PYTHON_PREFIX}_FIND_FRAMEWORK'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") - else() - set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK ${${_PYTHON_PREFIX}_FIND_FRAMEWORK}) - endif() -elseif (DEFINED CMAKE_FIND_FRAMEWORK) - if (CMAKE_FIND_FRAMEWORK STREQUAL "ONLY") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: CMAKE_FIND_FRAMEWORK: 'ONLY' value is not supported. 'FIRST' will be used instead.") - elseif (NOT CMAKE_FIND_FRAMEWORK MATCHES "^(FIRST|LAST|NEVER)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${CMAKE_FIND_FRAMEWORK}: invalid value for 'CMAKE_FIND_FRAMEWORK'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") - else() - set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) - endif() -endif() - -# Save CMAKE_FIND_APPBUNDLE -if (DEFINED CMAKE_FIND_APPBUNDLE) - set (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE}) -else() - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) -endif() -# To avoid app bundle lookup -set (CMAKE_FIND_APPBUNDLE "NEVER") - -# Save CMAKE_FIND_FRAMEWORK -if (DEFINED CMAKE_FIND_FRAMEWORK) - set (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) -else() - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) -endif() -# To avoid framework lookup -set (CMAKE_FIND_FRAMEWORK "NEVER") - -# Windows Registry handling -if (DEFINED ${_PYTHON_PREFIX}_FIND_REGISTRY) - if (NOT ${_PYTHON_PREFIX}_FIND_REGISTRY MATCHES "^(FIRST|LAST|NEVER)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_REGISTRY}: invalid value for '${_PYTHON_PREFIX}_FIND_REGISTRY'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") - set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST") - else() - set (_${_PYTHON_PREFIX}_FIND_REGISTRY ${${_PYTHON_PREFIX}_FIND_REGISTRY}) - endif() -else() - set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST") -endif() - -# virtual environments recognition -if (DEFINED ENV{VIRTUAL_ENV} OR DEFINED ENV{CONDA_PREFIX}) - if (DEFINED ${_PYTHON_PREFIX}_FIND_VIRTUALENV) - if (NOT ${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY|STANDARD)$") - message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}: invalid value for '${_PYTHON_PREFIX}_FIND_VIRTUALENV'. 'FIRST', 'ONLY' or 'STANDARD' expected. 'FIRST' will be used instead.") - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV "FIRST") - else() - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}) - endif() - else() - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV FIRST) - endif() -else() - set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STANDARD) -endif() - - -# Compute search signature -# This signature will be used to check validity of cached variables on new search -set (_${_PYTHON_PREFIX}_SIGNATURE "${${_PYTHON_PREFIX}_ROOT_DIR}:${${_PYTHON_PREFIX}_FIND_STRATEGY}:${${_PYTHON_PREFIX}_FIND_VIRTUALENV}") -if (NOT WIN32) - string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${${_PYTHON_PREFIX}_USE_STATIC_LIBS}:") -endif() -if (CMAKE_HOST_APPLE) - string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${${_PYTHON_PREFIX}_FIND_FRAMEWORK}") -endif() -if (CMAKE_HOST_WIN32) - string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${${_PYTHON_PREFIX}_FIND_REGISTRY}") -endif() - - -unset (_${_PYTHON_PREFIX}_REQUIRED_VARS) -unset (_${_PYTHON_PREFIX}_CACHED_VARS) -unset (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE) -unset (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE) -unset (_${_PYTHON_PREFIX}_Development_REASON_FAILURE) -unset (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE) - - -# first step, search for the interpreter -if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_EXECUTABLE) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_EXECUTABLE) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_EXECUTABLE - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_EXECUTABLE}") - if (NOT ${_PYTHON_PREFIX}_EXECUTABLE STREQUAL _${_PYTHON_PREFIX}_EXECUTABLE) - # invalidate cache properties - unset (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES CACHE) - endif() - set (_${_PYTHON_PREFIX}_EXECUTABLE "${${_PYTHON_PREFIX}_EXECUTABLE}" CACHE INTERNAL "") - elseif (DEFINED _${_PYTHON_PREFIX}_EXECUTABLE) - # compute interpreter signature and check validity of definition - string (MD5 __${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_EXECUTABLE}") - if (__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE) - # check version validity - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) - else() - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) - endif() - else() - unset (_${_PYTHON_PREFIX}_EXECUTABLE CACHE) - endif() - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - unset (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE CACHE) - unset (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - unset (_${_PYTHON_PREFIX}_NAMES) - unset (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - unset (_${_PYTHON_PREFIX}_REGISTRY_PATHS) - - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # build all executable names - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX EXECUTABLE) - list (APPEND _${_PYTHON_PREFIX}_NAMES ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - - # Registry Paths - _python_get_registries (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath]) - endforeach() - list (APPEND _${_PYTHON_PREFIX}_NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} python) - - while (TRUE) - # Virtual environments handling - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX - PATH_SUFFIXES bin Scripts - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - if (NOT _${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") - break() - endif() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - - # try using HINTS - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - # try using standard paths - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - _python_validate_interpreter (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endif() - - break() - endwhile() - else() - # look-up for various versions and locations - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - _python_get_names (_${_PYTHON_PREFIX}_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX EXECUTABLE) - list (APPEND _${_PYTHON_PREFIX}_NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} - python) - - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - - # Virtual environments handling - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX - PATH_SUFFIXES bin Scripts - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") - continue() - endif() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - - # try using HINTS - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - # try using standard paths. - # NAMES_PER_DIR is not defined on purpose to have a chance to find - # expected version. - # For example, typical systems have 'python' for version 2.* and 'python3' - # for version 3.*. So looking for names per dir will find, potentially, - # systematically 'python' (i.e. version 2) even if version 3 is searched. - if (CMAKE_HOST_WIN32) - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - python - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES}) - else() - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES}) - endif() - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - endif() - - # Windows registry - if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES ${_${_PYTHON_PREFIX}_NAMES} - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES bin ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - endif() - - _python_validate_interpreter (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_EXECUTABLE) - break() - endif() - endforeach() - - if (NOT _${_PYTHON_PREFIX}_EXECUTABLE AND - NOT _${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") - # No specific version found. Retry with generic names and standard paths. - # NAMES_PER_DIR is not defined on purpose to have a chance to find - # expected version. - # For example, typical systems have 'python' for version 2.* and 'python3' - # for version 3.*. So looking for names per dir will find, potentially, - # systematically 'python' (i.e. version 2) even if version 3 is searched. - find_program (_${_PYTHON_PREFIX}_EXECUTABLE - NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} - python - ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES}) - _python_validate_interpreter () - endif() - endif() - endif() - - set (${_PYTHON_PREFIX}_EXECUTABLE "${_${_PYTHON_PREFIX}_EXECUTABLE}") - - # retrieve exact version of executable found - if (_${_PYTHON_PREFIX}_EXECUTABLE) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c - "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_VERSION - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (NOT _${_PYTHON_PREFIX}_RESULT) - set (_${_PYTHON_PREFIX}_EXECUTABLE_USABLE TRUE) - else() - # Interpreter is not usable - set (_${_PYTHON_PREFIX}_EXECUTABLE_USABLE FALSE) - unset (${_PYTHON_PREFIX}_VERSION) - set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot run the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") - endif() - endif() - - if (_${_PYTHON_PREFIX}_EXECUTABLE AND _${_PYTHON_PREFIX}_EXECUTABLE_USABLE) - if (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES) - set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 0 ${_PYTHON_PREFIX}_INTERPRETER_ID) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 1 ${_PYTHON_PREFIX}_VERSION_MAJOR) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 2 ${_PYTHON_PREFIX}_VERSION_MINOR) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 3 ${_PYTHON_PREFIX}_VERSION_PATH) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 4 _${_PYTHON_PREFIX}_ARCH) - set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 5 _${_PYTHON_PREFIX}_ABIFLAGS) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 6 ${_PYTHON_PREFIX}_SOABI) - - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 7 ${_PYTHON_PREFIX}_STDLIB) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 8 ${_PYTHON_PREFIX}_STDARCH) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 9 ${_PYTHON_PREFIX}_SITELIB) - list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 10 ${_PYTHON_PREFIX}_SITEARCH) - else() - string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${${_PYTHON_PREFIX}_VERSION}") - list (GET _${_PYTHON_PREFIX}_VERSIONS 0 ${_PYTHON_PREFIX}_VERSION_MAJOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 1 ${_PYTHON_PREFIX}_VERSION_MINOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 2 ${_PYTHON_PREFIX}_VERSION_PATCH) - - if (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) - - # Use interpreter version and ABI for future searches to ensure consistency - set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; sys.stdout.write(sys.abiflags)" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_ABIFLAGS - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assunme ABI is not supported - set (_${_PYTHON_PREFIX}_ABIFLAGS "") - endif() - endif() - - if (${_PYTHON_PREFIX}_Interpreter_FOUND) - # compute and save interpreter signature - string (MD5 __${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_EXECUTABLE}") - set (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}" CACHE INTERNAL "") - - if (NOT CMAKE_SIZEOF_VOID_P) - # determine interpreter architecture - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; print(sys.maxsize > 2**32)" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_IS64BIT - ERROR_VARIABLE ${_PYTHON_PREFIX}_IS64BIT) - if (NOT _${_PYTHON_PREFIX}_RESULT) - if (${_PYTHON_PREFIX}_IS64BIT) - set (_${_PYTHON_PREFIX}_ARCH 64) - set (_${_PYTHON_PREFIX}_ARCH2 64) - else() - set (_${_PYTHON_PREFIX}_ARCH 32) - set (_${_PYTHON_PREFIX}_ARCH2 32) - endif() - endif() - endif() - - # retrieve interpreter identity - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -V - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID - ERROR_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID) - if (NOT _${_PYTHON_PREFIX}_RESULT) - if (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Anaconda") - set (${_PYTHON_PREFIX}_INTERPRETER_ID "Anaconda") - elseif (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Enthought") - set (${_PYTHON_PREFIX}_INTERPRETER_ID "Canopy") - else() - string (REGEX REPLACE "^([^ ]+).*" "\\1" ${_PYTHON_PREFIX}_INTERPRETER_ID "${${_PYTHON_PREFIX}_INTERPRETER_ID}") - if (${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "Python") - # try to get a more precise ID - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; print(sys.copyright)" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE ${_PYTHON_PREFIX}_COPYRIGHT - ERROR_QUIET) - if (${_PYTHON_PREFIX}_COPYRIGHT MATCHES "ActiveState") - set (${_PYTHON_PREFIX}_INTERPRETER_ID "ActivePython") - endif() - endif() - endif() - else() - set (${_PYTHON_PREFIX}_INTERPRETER_ID Python) - endif() - - # retrieve various package installation directories - execute_process (COMMAND "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; from distutils import sysconfig;sys.stdout.write(';'.join([sysconfig.get_python_lib(plat_specific=False,standard_lib=True),sysconfig.get_python_lib(plat_specific=True,standard_lib=True),sysconfig.get_python_lib(plat_specific=False,standard_lib=False),sysconfig.get_python_lib(plat_specific=True,standard_lib=False)]))" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_LIBPATHS - ERROR_QUIET) - if (NOT _${_PYTHON_PREFIX}_RESULT) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 0 ${_PYTHON_PREFIX}_STDLIB) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 1 ${_PYTHON_PREFIX}_STDARCH) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 2 ${_PYTHON_PREFIX}_SITELIB) - list (GET _${_PYTHON_PREFIX}_LIBPATHS 3 ${_PYTHON_PREFIX}_SITEARCH) - else() - unset (${_PYTHON_PREFIX}_STDLIB) - unset (${_PYTHON_PREFIX}_STDARCH) - unset (${_PYTHON_PREFIX}_SITELIB) - unset (${_PYTHON_PREFIX}_SITEARCH) - endif() - - if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_GREATER_EQUAL 3) - _python_get_config_var (${_PYTHON_PREFIX}_SOABI SOABI) - endif() - - # store properties in the cache to speed-up future searches - set (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES - "${${_PYTHON_PREFIX}_INTERPRETER_ID};${${_PYTHON_PREFIX}_VERSION_MAJOR};${${_PYTHON_PREFIX}_VERSION_MINOR};${${_PYTHON_PREFIX}_VERSION_PATCH};${_${_PYTHON_PREFIX}_ARCH};${_${_PYTHON_PREFIX}_ABIFLAGS};${${_PYTHON_PREFIX}_SOABI};${${_PYTHON_PREFIX}_STDLIB};${${_PYTHON_PREFIX}_STDARCH};${${_PYTHON_PREFIX}_SITELIB};${${_PYTHON_PREFIX}_SITEARCH}" CACHE INTERNAL "${_PYTHON_PREFIX} Properties") - else() - unset (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE CACHE) - unset (${_PYTHON_PREFIX}_INTERPRETER_ID) - endif() - endif() - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_EXECUTABLE - _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES - _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE) -endif() - - -# second step, search for compiler (IronPython) -if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_COMPILER) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Compiler) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_COMPILER) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_COMPILER - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_COMPILER}") - set (_${_PYTHON_PREFIX}_COMPILER "${${_PYTHON_PREFIX}_COMPILER}" CACHE INTERNAL "") - elseif (DEFINED _${_PYTHON_PREFIX}_COMPILER) - # compute compiler signature and check validity of definition - string (MD5 __${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_COMPILER}") - if (__${_PYTHON_PREFIX}_COMPILER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_COMPILER_SIGNATURE) - # check version validity - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) - else() - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) - endif() - else() - unset (_${_PYTHON_PREFIX}_COMPILER CACHE) - unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_COMPILER) - # IronPython specific artifacts - # If IronPython interpreter is found, use its path - unset (_${_PYTHON_PREFIX}_IRON_ROOT) - if (${_PYTHON_PREFIX}_Interpreter_FOUND AND ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") - get_filename_component (_${_PYTHON_PREFIX}_IRON_ROOT "${${_PYTHON_PREFIX}_EXECUTABLE}" DIRECTORY) - endif() - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - set (_${_PYTHON_PREFIX}_REGISTRY_PATHS) - - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # Registry Paths - list (APPEND _${_PYTHON_PREFIX}_REGISTRY_PATHS - [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath]) - endforeach() - - while (TRUE) - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - endif() - - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${${_PYTHON_PREFIX}_FIND_VERSION}) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - endif() - - break() - endwhile() - else() - # try using root dir and registry - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - endif() - - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - - if (_${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES} - NO_DEFAULT_PATH) - _python_validate_compiler (${_${_PYTHON_PREFIX}_VERSION} EXACT) - if (_${_PYTHON_PREFIX}_COMPILER) - break() - endif() - endif() - endforeach() - - # no specific version found, re-try in standard paths - find_program (_${_PYTHON_PREFIX}_COMPILER - NAMES ipyc - HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}) - endif() - endif() - - set (${_PYTHON_PREFIX}_COMPILER "${_${_PYTHON_PREFIX}_COMPILER}") - - if (_${_PYTHON_PREFIX}_COMPILER) - # retrieve python environment version from compiler - set (_${_PYTHON_PREFIX}_VERSION_DIR "CMAKECURRENTBINARYDIR{CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") - file (WRITE "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))\n") - execute_process (COMMAND "${_${_PYTHON_PREFIX}_COMPILER}" /target:exe /embed "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" - WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" - OUTPUT_QUIET - ERROR_QUIET) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_VERSION_DIR}/version" - WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_VERSION - ERROR_QUIET) - if (NOT _${_PYTHON_PREFIX}_RESULT) - set (_${_PYTHON_PREFIX}_COMPILER_USABLE TRUE) - string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${_${_PYTHON_PREFIX}_VERSION}") - list (GET _${_PYTHON_PREFIX}_VERSIONS 0 _${_PYTHON_PREFIX}_VERSION_MAJOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 1 _${_PYTHON_PREFIX}_VERSION_MINOR) - list (GET _${_PYTHON_PREFIX}_VERSIONS 2 _${_PYTHON_PREFIX}_VERSION_PATCH) - - if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND) - # set public version information - set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) - set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) - set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) - endif() - else() - # compiler not usable - set (_${_PYTHON_PREFIX}_COMPILER_USABLE FALSE) - set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot run the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") - endif() - file (REMOVE_RECURSE "${_${_PYTHON_PREFIX}_VERSION_DIR}") - endif() - - if (_${_PYTHON_PREFIX}_COMPILER AND _${_PYTHON_PREFIX}_COMPILER_USABLE) - if (${_PYTHON_PREFIX}_Interpreter_FOUND) - # Compiler must be compatible with interpreter - if (${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR} VERSION_EQUAL ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) - endif() - elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) - # Use compiler version for future searches to ensure consistency - set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) - endif() - endif() - - if (${_PYTHON_PREFIX}_Compiler_FOUND) - # compute and save compiler signature - string (MD5 __${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_COMPILER}") - set (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${__${_PYTHON_PREFIX}_COMPILER_SIGNATURE}" CACHE INTERNAL "") - - set (${_PYTHON_PREFIX}_COMPILER_ID IronPython) - else() - unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) - unset (${_PYTHON_PREFIX}_COMPILER_ID) - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_COMPILER - _${_PYTHON_PREFIX}_COMPILER_SIGNATURE) -endif() - - -# third step, search for the development artifacts -## Development environment is not compatible with IronPython interpreter -if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND NOT ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_INCLUDE_DIR) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_LIBRARIES - ${_PYTHON_PREFIX}_INCLUDE_DIRS) - endif() - - if (DEFINED _${_PYTHON_PREFIX}_LIBRARY_RELEASE OR DEFINED _${_PYTHON_PREFIX}_INCLUDE_DIR) - # compute development signature and check validity of definition - string (MD5 __${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}:${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - if (WIN32 AND NOT DEFINED _${_PYTHON_PREFIX}_LIBRARY_DEBUG) - set (_${_PYTHON_PREFIX}_LIBRARY_DEBUG "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND" CACHE INTERNAL "") - endif() - if (NOT DEFINED _${_PYTHON_PREFIX}_INCLUDE_DIR) - set (_${_PYTHON_PREFIX}_INCLUDE_DIR "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND" CACHE INTERNAL "") - endif() - if (__${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE) - # check version validity - if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) - _python_validate_library (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) - _python_validate_include_dir (${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) - else() - _python_validate_library (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) - _python_validate_include_dir (${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) - endif() - else() - unset (_${_PYTHON_PREFIX}_LIBRARY_RELEASE CACHE) - unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG CACHE) - unset (_${_PYTHON_PREFIX}_INCLUDE_DIR CACHE) - endif() - endif() - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE OR NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - unset (_${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE CACHE) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_LIBRARY - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_LIBRARY}") - set (_${_PYTHON_PREFIX}_LIBRARY_RELEASE "${${_PYTHON_PREFIX}_LIBRARY}" CACHE INTERNAL "") - unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG CACHE) - unset (_${_PYTHON_PREFIX}_INCLUDE_DIR CACHE) - endif() - if (DEFINED ${_PYTHON_PREFIX}_INCLUDE_DIR - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_INCLUDE_DIR "${${_PYTHON_PREFIX}_INCLUDE_DIR}" CACHE INTERNAL "") - endif() - - # Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) - if (DEFINED ${_PYTHON_PREFIX}_USE_STATIC_LIBS AND NOT WIN32) - set(_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - if(${_PYTHON_PREFIX}_USE_STATIC_LIBS) - set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - list (REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE OR NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - # if python interpreter is found, use it to look-up for artifacts - # to ensure consistency between interpreter and development environments. - # If not, try to locate a compatible config tool - if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND OR CMAKE_CROSSCOMPILING) - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) - endif() - unset (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - set (_${_PYTHON_PREFIX}_CONFIG_NAMES) - - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX CONFIG) - list (APPEND _${_PYTHON_PREFIX}_CONFIG_NAMES ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - endforeach() - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES bin) - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --help - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_HELP - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume config tool is not usable - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --abiflags - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_ABIFLAGS - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume ABI is not supported - set (__${_PYTHON_PREFIX}_ABIFLAGS "") - endif() - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # Wrong ABI - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - # check that config tool match library architecture - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --configdir - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_CONFIGDIR - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - else() - string(FIND "${_${_PYTHON_PREFIX}_CONFIGDIR}" "${CMAKE_LIBRARY_ARCHITECTURE}" _${_PYTHON_PREFIX}_RESULT) - if (_${_PYTHON_PREFIX}_RESULT EQUAL -1) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - endif() - else() - foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # try to use pythonX.Y-config tool - _python_get_names (_${_PYTHON_PREFIX}_CONFIG_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX CONFIG) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES bin) - - # Apple frameworks handling - if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - find_program (_${_PYTHON_PREFIX}_CONFIG - NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} - NAMES_PER_DIR - PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES bin - NO_DEFAULT_PATH) - endif() - - unset (_${_PYTHON_PREFIX}_CONFIG_NAMES) - - if (_${_PYTHON_PREFIX}_CONFIG) - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --help - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_HELP - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume config tool is not usable - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_CONFIG) - continue() - endif() - - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --abiflags - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE __${_PYTHON_PREFIX}_ABIFLAGS - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - # assume ABI is not supported - set (__${_PYTHON_PREFIX}_ABIFLAGS "") - endif() - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) - # Wrong ABI - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - continue() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) - # check that config tool match library architecture - execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --configdir - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_CONFIGDIR - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_${_PYTHON_PREFIX}_RESULT) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - continue() - endif() - string (FIND "${_${_PYTHON_PREFIX}_CONFIGDIR}" "${CMAKE_LIBRARY_ARCHITECTURE}" _${_PYTHON_PREFIX}_RESULT) - if (_${_PYTHON_PREFIX}_RESULT EQUAL -1) - unset (_${_PYTHON_PREFIX}_CONFIG CACHE) - continue() - endif() - endif() - - if (_${_PYTHON_PREFIX}_CONFIG) - break() - endif() - endforeach() - endif() - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - if ((${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING) OR _${_PYTHON_PREFIX}_CONFIG) - # retrieve root install directory - _python_get_config_var (_${_PYTHON_PREFIX}_PREFIX PREFIX) - - # enforce current ABI - _python_get_config_var (_${_PYTHON_PREFIX}_ABIFLAGS ABIFLAGS) - - set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") - - # retrieve library - ## compute some paths and artifact names - if (_${_PYTHON_PREFIX}_CONFIG) - string (REGEX REPLACE "^.+python([0-9.]+)[a-z]*-config" "\\1" _${_PYTHON_PREFIX}_VERSION "${_${_PYTHON_PREFIX}_CONFIG}") - else() - set (_${_PYTHON_PREFIX}_VERSION "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}") - endif() - _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} LIBRARY) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 POSIX LIBRARY) - - _python_get_config_var (_${_PYTHON_PREFIX}_CONFIGDIR CONFIGDIR) - list (APPEND _${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_CONFIGDIR}") - - list (APPEND _${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # Rely on HINTS and standard paths if interpreter or config tool failed to locate artifacts - if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) - endif() - - if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") - unset (_${_PYTHON_PREFIX}_LIB_NAMES) - unset (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG) - unset (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - unset (_${_PYTHON_PREFIX}_REGISTRY_PATHS) - unset (_${_PYTHON_PREFIX}_PATH_SUFFIXES) - - foreach (_${_PYTHON_PREFIX}_LIB_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - # library names - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 POSIX LIBRARY) - list (APPEND _${_PYTHON_PREFIX}_LIB_NAMES ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - _python_get_names (_${_PYTHON_PREFIX}_VERSION_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 DEBUG) - list (APPEND _${_PYTHON_PREFIX}_LIB_NAMES_DEBUG ${_${_PYTHON_PREFIX}_VERSION_NAMES}) - - # Framework Paths - _python_get_frameworks (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - - # Registry Paths - _python_get_registries (_${_PYTHON_PREFIX}_VERSION_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - list (APPEND _${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - - # Paths suffixes - _python_get_path_suffixes (_${_PYTHON_PREFIX}_VERSION_PATHS VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} LIBRARY) - list (APPEND _${_PYTHON_PREFIX}_PATH_SUFFIXES ${_${_PYTHON_PREFIX}_VERSION_PATHS}) - endforeach() - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # search in HINTS locations - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) - endif() - - # search in all default paths - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - PATHS ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) - else() - foreach (_${_PYTHON_PREFIX}_LIB_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 POSIX LIBRARY) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 DEBUG) - - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_LIB_VERSION}) - - _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} LIBRARY) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # search in HINTS locations - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) - endif() - - # search in all default paths - find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - PATHS ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - break() - endif() - endforeach() - endif() - endif() - endif() - - # finalize library version information - _python_get_version (LIBRARY PREFIX _${_PYTHON_PREFIX}_) - - set (${_PYTHON_PREFIX}_LIBRARY_RELEASE "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") - set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") - endif() - - set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) - - if (WIN32 AND _${_PYTHON_PREFIX}_LIBRARY_RELEASE) - # search for debug library - # use release library location as a hint - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 DEBUG) - get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) - find_library (_${_PYTHON_PREFIX}_LIBRARY_DEBUG - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} - NAMES_PER_DIR - HINTS "${_${_PYTHON_PREFIX}_PATH}" ${_${_PYTHON_PREFIX}_HINTS} - NO_DEFAULT_PATH) - endif() - - # retrieve runtime libraries - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 POSIX LIBRARY) - get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) - get_filename_component (_${_PYTHON_PREFIX}_PATH2 "${_${_PYTHON_PREFIX}_PATH}" DIRECTORY) - _python_find_runtime_library (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} - NAMES_PER_DIR - HINTS "${_${_PYTHON_PREFIX}_PATH}" "${_${_PYTHON_PREFIX}_PATH2}" ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin) - endif() - if (_${_PYTHON_PREFIX}_LIBRARY_DEBUG) - _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 DEBUG) - get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_DEBUG}" DIRECTORY) - get_filename_component (_${_PYTHON_PREFIX}_PATH2 "${_${_PYTHON_PREFIX}_PATH}" DIRECTORY) - _python_find_runtime_library (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG - NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} - NAMES_PER_DIR - HINTS "${_${_PYTHON_PREFIX}_PATH}" "${_${_PYTHON_PREFIX}_PATH2}" ${_${_PYTHON_PREFIX}_HINTS} - PATH_SUFFIXES bin) - endif() - - # Don't search for include dir if no library was founded - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - if ((${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING) OR _${_PYTHON_PREFIX}_CONFIG) - _python_get_config_var (_${_PYTHON_PREFIX}_INCLUDE_DIRS INCLUDES) - - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_DIRS} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # Rely on HINTS and standard paths if interpreter or config tool failed to locate artifacts - if (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) - unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) - if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") - set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) - endif() - unset (_${_PYTHON_PREFIX}_INCLUDE_HINTS) - - # Use the library's install prefix as a hint - if (${_${_PYTHON_PREFIX}_LIBRARY_RELEASE} MATCHES "^(.+/Frameworks/Python.framework/Versions/[0-9.]+)") - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") - elseif (${_${_PYTHON_PREFIX}_LIBRARY_RELEASE} MATCHES "^(.+)/lib(64|32)?/python[0-9.]+/config") - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") - elseif (DEFINED CMAKE_LIBRARY_ARCHITECTURE AND ${_${_PYTHON_PREFIX}_LIBRARY_RELEASE} MATCHES "^(.+)/lib/${CMAKE_LIBRARY_ARCHITECTURE}") - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") - else() - # assume library is in a directory under root - get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) - get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_PREFIX}" DIRECTORY) - list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") - endif() - - _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_VERSION}) - _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} INCLUDE) - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) - endif() - - if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") - set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) - else() - unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) - endif() - - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h - HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} - PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} - ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} - ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} - PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} - NO_SYSTEM_ENVIRONMENT_PATH - NO_CMAKE_SYSTEM_PATH) - endif() - - # search header file in standard locations - find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR - NAMES Python.h) - endif() - - set (${_PYTHON_PREFIX}_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - - if (_${_PYTHON_PREFIX}_INCLUDE_DIR AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") - endif() - - if (_${_PYTHON_PREFIX}_INCLUDE_DIR) - # retrieve version from header file - _python_get_version (INCLUDE PREFIX _${_PYTHON_PREFIX}_INC_) - - # update versioning - if (_${_PYTHON_PREFIX}_INC_VERSION VERSION_EQUAL ${_${_PYTHON_PREFIX}_VERSION}) - set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH}) - endif() - endif() - - if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT ${_PYTHON_PREFIX}_Compiler_FOUND) - # set public version information - set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) - set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) - set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) - endif() - - # define public variables - set (${_PYTHON_PREFIX}_LIBRARY_DEBUG "${_${_PYTHON_PREFIX}_LIBRARY_DEBUG}") - _python_select_library_configurations (${_PYTHON_PREFIX}) - - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") - - if (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - elseif (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") - else() - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_PYTHON_PREFIX}_RUNTIME_LIBRARY-NOTFOUND") - endif() - - _python_set_library_dirs (${_PYTHON_PREFIX}_LIBRARY_DIRS - _${_PYTHON_PREFIX}_LIBRARY_RELEASE _${_PYTHON_PREFIX}_LIBRARY_DEBUG) - if (UNIX) - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") - set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS ${${_PYTHON_PREFIX}_LIBRARY_DIRS}) - endif() - else() - _python_set_library_dirs (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) - endif() - - if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND _${_PYTHON_PREFIX}_INCLUDE_DIR) - if (${_PYTHON_PREFIX}_Interpreter_FOUND OR ${_PYTHON_PREFIX}_Compiler_FOUND) - # development environment must be compatible with interpreter/compiler - if (${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR} VERSION_EQUAL ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR} - AND ${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR} VERSION_EQUAL ${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_Development_FOUND TRUE) - endif() - elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR - AND ${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR} VERSION_EQUAL ${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}) - set (${_PYTHON_PREFIX}_Development_FOUND TRUE) - endif() - if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND - (NOT _${_PYTHON_PREFIX}_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS - OR NOT _${_PYTHON_PREFIX}_INC_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)) - set (${_PYTHON_PREFIX}_Development_FOUND FALSE) - endif() - endif() - - if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_GREATER_EQUAL 3 - AND NOT DEFINED ${_PYTHON_PREFIX}_SOABI) - _python_get_config_var (${_PYTHON_PREFIX}_SOABI SOABI) - endif() - - if (${_PYTHON_PREFIX}_Development_FOUND) - # compute and save development signature - string (MD5 __${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}:${_${_PYTHON_PREFIX}_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE "${__${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE}" CACHE INTERNAL "") - else() - unset (_${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE CACHE) - endif() - - # Restore the original find library ordering - if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) - set (CMAKE_FIND_LIBRARY_SUFFIXES ${_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES}) - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE - _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG - _${_PYTHON_PREFIX}_INCLUDE_DIR - _${_PYTHON_PREFIX}_CONFIG - _${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE) -endif() - -if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS AND ${_PYTHON_PREFIX}_Interpreter_FOUND) - list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - if (${_PYTHON_PREFIX}_FIND_REQUIRED_NumPy) - list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS) - endif() - - if (DEFINED ${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR - AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}" CACHE INTERNAL "") - elseif (DEFINED _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - # compute numpy signature. Depends on interpreter and development signatures - string (MD5 __${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}:${_${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE}:${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - if (NOT __${_PYTHON_PREFIX}_NUMPY_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_NUMPY_SIGNATURE - OR NOT EXISTS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - unset (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR CACHE) - unset (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE CACHE) - endif() - endif() - - if (NOT _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - execute_process( - COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c - "from __future__ import print_function\ntry: import numpy; print(numpy.get_include(), end='')\nexcept:pass\n" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_NumPy_PATH - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if (NOT _${_PYTHON_PREFIX}_RESULT) - find_path (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR - NAMES "numpy/arrayobject.h" "numpy/numpyconfig.h" - HINTS "${_${_PYTHON_PREFIX}_NumPy_PATH}" - NO_DEFAULT_PATH) - endif() - endif() - - set (${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - - if(_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR AND NOT EXISTS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}\"") - set_property (CACHE _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR-NOTFOUND") - endif() - - if (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) - execute_process ( - COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c - "from __future__ import print_function\ntry: import numpy; print(numpy.__version__, end='')\nexcept:pass\n" - RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT - OUTPUT_VARIABLE _${_PYTHON_PREFIX}_NumPy_VERSION) - if (NOT _${_PYTHON_PREFIX}_RESULT) - set (${_PYTHON_PREFIX}_NumPy_VERSION "${_${_PYTHON_PREFIX}_NumPy_VERSION}") - else() - unset (${_PYTHON_PREFIX}_NumPy_VERSION) - endif() - - # final step: set NumPy founded only if Development component is founded as well - set(${_PYTHON_PREFIX}_NumPy_FOUND ${${_PYTHON_PREFIX}_Development_FOUND}) - else() - set (${_PYTHON_PREFIX}_NumPy_FOUND FALSE) - endif() - - if (${_PYTHON_PREFIX}_NumPy_FOUND) - # compute and save numpy signature - string (MD5 __${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}:${_${_PYTHON_PREFIX}_DEVELOPMENT_SIGNATURE}:${${_PYTHON_PREFIX}_NumPyINCLUDE_DIR}") - set (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${__${_PYTHON_PREFIX}_NUMPY_SIGNATURE}" CACHE INTERNAL "") - else() - unset (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE CACHE) - endif() - - _python_mark_as_internal (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR - _${_PYTHON_PREFIX}_NUMPY_SIGNATURE) -endif() - -# final validation -if (${_PYTHON_PREFIX}_VERSION_MAJOR AND - NOT ${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) - _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Found unsuitable major version \"${${_PYTHON_PREFIX}_VERSION_MAJOR}\", but required major version is exact version \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") -endif() - -unset (_${_PYTHON_PREFIX}_REASON_FAILURE) -foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development NumPy) - if (_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE) - string (APPEND _${_PYTHON_PREFIX}_REASON_FAILURE "\n ${_${_PYTHON_PREFIX}_COMPONENT}: ${_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE}") - endif() -endforeach() - -include (FindPackageHandleStandardArgs) -find_package_handle_standard_args (${_PYTHON_PREFIX} - REQUIRED_VARS ${_${_PYTHON_PREFIX}_REQUIRED_VARS} - VERSION_VAR ${_PYTHON_PREFIX}_VERSION - HANDLE_COMPONENTS - REASON_FAILURE_MESSAGE "${_${_PYTHON_PREFIX}_REASON_FAILURE}") - -# Create imported targets and helper functions -if(_${_PYTHON_PREFIX}_CMAKE_ROLE STREQUAL "PROJECT") - if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Interpreter_FOUND - AND NOT TARGET ${_PYTHON_PREFIX}::Interpreter) - add_executable (${_PYTHON_PREFIX}::Interpreter IMPORTED) - set_property (TARGET ${_PYTHON_PREFIX}::Interpreter - PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_EXECUTABLE}") - endif() - - if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Compiler_FOUND - AND NOT TARGET ${_PYTHON_PREFIX}::Compiler) - add_executable (${_PYTHON_PREFIX}::Compiler IMPORTED) - set_property (TARGET ${_PYTHON_PREFIX}::Compiler - PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_COMPILER}") - endif() - - if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS - AND ${_PYTHON_PREFIX}_Development_FOUND) - - macro (__PYTHON_IMPORT_LIBRARY __name) - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$" - OR ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) - set (_${_PYTHON_PREFIX}_LIBRARY_TYPE SHARED) - else() - set (_${_PYTHON_PREFIX}_LIBRARY_TYPE STATIC) - endif() - - if (NOT TARGET ${__name}) - add_library (${__name} ${_${_PYTHON_PREFIX}_LIBRARY_TYPE} IMPORTED) - endif() - - set_property (TARGET ${__name} - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}") - - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) - # System manage shared libraries in two parts: import and runtime - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) - set_property (TARGET ${__name} PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_IMPLIB_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" - IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_IMPLIB_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}" - IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") - else() - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_IMPLIB "${${_PYTHON_PREFIX}_LIBRARY}" - IMPORTED_LOCATION "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY}") - endif() - else() - if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) - set_property (TARGET ${__name} PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}") - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}") - else() - set_target_properties (${__name} - PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${${_PYTHON_PREFIX}_LIBRARY}") - endif() - endif() - - if (_${_PYTHON_PREFIX}_LIBRARY_TYPE STREQUAL "STATIC") - # extend link information with dependent libraries - _python_get_config_var (_${_PYTHON_PREFIX}_LINK_LIBRARIES LIBS) - if (_${_PYTHON_PREFIX}_LINK_LIBRARIES) - set_property (TARGET ${__name} - PROPERTY INTERFACE_LINK_LIBRARIES ${_${_PYTHON_PREFIX}_LINK_LIBRARIES}) - endif() - endif() - endmacro() - - __python_import_library (${_PYTHON_PREFIX}::Python) - - if (CMAKE_SYSTEM_NAME MATCHES "^(Windows.*|CYGWIN|MSYS)$") - # On Windows/CYGWIN/MSYS, Python::Module is the same as Python::Python - # but ALIAS cannot be used because the imported library is not GLOBAL. - __python_import_library (${_PYTHON_PREFIX}::Module) - else() - if (NOT TARGET ${_PYTHON_PREFIX}::Module ) - add_library (${_PYTHON_PREFIX}::Module INTERFACE IMPORTED) - endif() - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}") - - # When available, enforce shared library generation with undefined symbols - if (APPLE) - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") - endif() - if (CMAKE_SYSTEM_NAME STREQUAL "SunOS") - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-z,nodefs") - endif() - if (CMAKE_SYSTEM_NAME STREQUAL "AIX") - set_property (TARGET ${_PYTHON_PREFIX}::Module - PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-b,erok") - endif() - endif() - - # - # PYTHON_ADD_LIBRARY ( [STATIC|SHARED|MODULE] src1 src2 ... srcN) - # It is used to build modules for python. - # - function (__${_PYTHON_PREFIX}_ADD_LIBRARY prefix name) - cmake_parse_arguments (PARSE_ARGV 2 PYTHON_ADD_LIBRARY - "STATIC;SHARED;MODULE;WITH_SOABI" "" "") - - if (prefix STREQUAL "Python2" AND PYTHON_ADD_LIBRARY_WITH_SOABI) - message (AUTHOR_WARNING "FindPython2: Option `WITH_SOABI` is not supported for Python2 and will be ignored.") - unset (PYTHON_ADD_LIBRARY_WITH_SOABI) - endif() - - if (PYTHON_ADD_LIBRARY_STATIC) - set (type STATIC) - elseif (PYTHON_ADD_LIBRARY_SHARED) - set (type SHARED) - else() - set (type MODULE) - endif() - add_library (${name} ${type} ${PYTHON_ADD_LIBRARY_UNPARSED_ARGUMENTS}) - - get_property (type TARGET ${name} PROPERTY TYPE) - - if (type STREQUAL "MODULE_LIBRARY") - target_link_libraries (${name} PRIVATE ${prefix}::Module) - # customize library name to follow module name rules - set_property (TARGET ${name} PROPERTY PREFIX "") - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set_property (TARGET ${name} PROPERTY SUFFIX ".pyd") - endif() - - if (PYTHON_ADD_LIBRARY_WITH_SOABI AND ${prefix}_SOABI) - get_property (suffix TARGET ${name} PROPERTY SUFFIX) - if (NOT suffix) - set (suffix "${CMAKE_SHARED_MODULE_SUFFIX}") - endif() - set_property (TARGET ${name} PROPERTY SUFFIX ".${${prefix}_SOABI}${suffix}") - endif() - else() - if (PYTHON_ADD_LIBRARY_WITH_SOABI) - message (AUTHOR_WARNING "Find${prefix}: Option `WITH_SOABI` is only supported for `MODULE` library type.") - endif() - target_link_libraries (${name} PRIVATE ${prefix}::Python) - endif() - endfunction() - endif() - - if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS AND ${_PYTHON_PREFIX}_NumPy_FOUND - AND NOT TARGET ${_PYTHON_PREFIX}::NumPy AND TARGET ${_PYTHON_PREFIX}::Module) - add_library (${_PYTHON_PREFIX}::NumPy INTERFACE IMPORTED) - set_property (TARGET ${_PYTHON_PREFIX}::NumPy - PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS}") - target_link_libraries (${_PYTHON_PREFIX}::NumPy INTERFACE ${_PYTHON_PREFIX}::Module) - endif() -endif() - -# final clean-up - -# Restore CMAKE_FIND_APPBUNDLE -if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) - set (CMAKE_FIND_APPBUNDLE ${_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE}) - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) -else() - unset (CMAKE_FIND_APPBUNDLE) -endif() -# Restore CMAKE_FIND_FRAMEWORK -if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) - set (CMAKE_FIND_FRAMEWORK ${_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK}) - unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) -else() - unset (CMAKE_FIND_FRAMEWORK) -endif() diff --git a/cmake/FindPython3.cmake b/cmake/FindPython3.cmake deleted file mode 100644 index e88e58864..000000000 --- a/cmake/FindPython3.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# Taken from: https://github.com/Kitware/CMake/blob/v3.17.0/Modules/FindPython3.cmake - -set (_PYTHON_PREFIX Python3) - -set (_Python3_REQUIRED_VERSION_MAJOR 3) - -include (${CMAKE_CURRENT_LIST_DIR}/FindPython/Support.cmake) - -if (COMMAND __Python3_add_library) - macro (Python3_add_library) - __Python3_add_library (Python3 ${ARGV}) - endmacro() -endif() - -unset (_PYTHON_PREFIX) diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/WorldVelocityCmd.h b/cpp/scenario/gazebo/include/scenario/gazebo/components/WorldVelocityCmd.h deleted file mode 100644 index 13a8d7490..000000000 --- a/cpp/scenario/gazebo/include/scenario/gazebo/components/WorldVelocityCmd.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT) - * All rights reserved. - * - * This project is dual licensed under LGPL v2.1+ or Apache License. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * This software may be modified and distributed under the terms of the - * GNU Lesser General Public License v2.1 or any later version. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IGNITION_GAZEBO_COMPONENTS_WORLDVELOCITYCMD_H -#define IGNITION_GAZEBO_COMPONENTS_WORLDVELOCITYCMD_H - -#include - -#include -#include - -#include "ignition/gazebo/components/Component.hh" -#include - -namespace ignition::gazebo { - // Inline bracket to help doxygen filtering. - inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { - struct WorldVelocity - { - math::Vector3d linear; - math::Vector3d angular; - - bool operator==(const WorldVelocity& other) const - { - return this->linear == other.linear - && this->angular == other.angular; - } - }; - - namespace components { - /// \brief A component type that contains commanded velocity of - /// an entity in the world frame represented by - /// ignition::math::Vector3d. - using WorldVelocityCmd = - Component; - IGN_GAZEBO_REGISTER_COMPONENT( - "ign_gazebo_components.WorldVelocityCmdTag", - WorldVelocityCmd) - } // namespace components - } // namespace IGNITION_GAZEBO_VERSION_NAMESPACE -} // namespace ignition::gazebo -#endif // IGNITION_GAZEBO_COMPONENTS_WORLDVELOCITYCMD_H diff --git a/cpp/scenario/plugins/ECMProvider/CMakeLists.txt b/cpp/scenario/plugins/ECMProvider/CMakeLists.txt deleted file mode 100644 index 0d1c811d6..000000000 --- a/cpp/scenario/plugins/ECMProvider/CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT) -# All rights reserved. -# -# This project is dual licensed under LGPL v2.1+ or Apache License. -# -# - - - - - - - - - - - - - - - - - - -# -# This software may be modified and distributed under the terms of the -# GNU Lesser General Public License v2.1 or any later version. -# -# - - - - - - - - - - - - - - - - - - -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# =========== -# ECMProvider -# =========== - -add_library(ECMProvider SHARED - include/scenario/plugins/gazebo/ECMProvider.h - ECMProvider.cpp) - -target_link_libraries(ECMProvider - PUBLIC - ${ignition-gazebo.core} - PRIVATE - ECMSingleton - ScenarioGazebo::ScenarioGazebo - ScenarioGazebo::ExtraComponents) - -target_include_directories(ECMProvider PRIVATE - $) - -# ============ -# ECMSingleton -# ============ - -# Always compile the singletons as shared libraries even if -# they are not plugins -add_library(ECMSingleton SHARED - include/scenario/plugins/gazebo/ECMSingleton.h - ECMSingleton.cpp) -add_library(ScenarioGazeboPlugins::ECMSingleton ALIAS ECMSingleton) - -target_include_directories(ECMSingleton PUBLIC - $ - $) - -target_link_libraries(ECMSingleton - PUBLIC - ${ignition-gazebo.core} - PRIVATE - ScenarioGazebo::ScenarioGazebo) - -set_target_properties(ECMSingleton PROPERTIES - PUBLIC_HEADER include/scenario/plugins/gazebo/ECMSingleton.h) - -# =================== -# Install the targets -# =================== - -install( - TARGETS ECMProvider - LIBRARY DESTINATION ${SCENARIO_INSTALL_LIBDIR}/scenario/plugins - ARCHIVE DESTINATION ${SCENARIO_INSTALL_LIBDIR}/scenario/plugins - RUNTIME DESTINATION ${SCENARIO_INSTALL_BINDIR}) - -install( - TARGETS ECMSingleton - EXPORT ScenarioGazeboPluginsExport - LIBRARY DESTINATION ${SCENARIO_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${SCENARIO_INSTALL_LIBDIR} - RUNTIME DESTINATION ${SCENARIO_INSTALL_BINDIR} - PUBLIC_HEADER DESTINATION - ${SCENARIO_INSTALL_INCLUDEDIR}/scenario/plugins/gazebo) diff --git a/cpp/scenario/plugins/ECMProvider/ECMProvider.cpp b/cpp/scenario/plugins/ECMProvider/ECMProvider.cpp deleted file mode 100644 index 33693ed1d..000000000 --- a/cpp/scenario/plugins/ECMProvider/ECMProvider.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT) - * All rights reserved. - * - * This project is dual licensed under LGPL v2.1+ or Apache License. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * This software may be modified and distributed under the terms of the - * GNU Lesser General Public License v2.1 or any later version. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "scenario/plugins/gazebo/ECMProvider.h" -#include "scenario/gazebo/Log.h" -#include "scenario/gazebo/helpers.h" -#include "scenario/plugins/gazebo/ECMSingleton.h" - -#include -#include -#include -#include - -using namespace scenario::gazebo; -using namespace scenario::plugins::gazebo; - -class ECMProvider::Impl -{ -public: -}; - -ECMProvider::ECMProvider() - : System() - , pImpl{std::make_unique()} -{} - -ECMProvider::~ECMProvider() -{ - ECMSingleton::Instance().clean(); - sDebug << "Destroying the ECMProvider" << std::endl; -}; - -void ECMProvider::Configure(const ignition::gazebo::Entity& entity, - const std::shared_ptr& /*sdf*/, - ignition::gazebo::EntityComponentManager& ecm, - ignition::gazebo::EventManager& eventMgr) -{ - if (!ecm.EntityHasComponentType( - entity, ignition::gazebo::components::World::typeId)) { - sError << "The ECMProvider plugin was not inserted " - << "in a world element" << std::endl; - return; - } - - const auto& worldName = utils::getExistingComponentData< // - ignition::gazebo::components::Name>(&ecm, entity); - - if (ECMSingleton::Instance().hasWorld(worldName)) { - sWarning << "Resources of world " << worldName << " already inserted" - << std::endl; - return; - } - - if (!ECMSingleton::Instance().storePtrs(&ecm, &eventMgr, worldName)) { - sError << "Failed to store resources of world " << worldName << " [" - << entity << "]" << std::endl; - return; - } - - sDebug << "World '" << worldName - << "' successfully processed by ECMProvider" << std::endl; -} - -IGNITION_ADD_PLUGIN(scenario::plugins::gazebo::ECMProvider, - scenario::plugins::gazebo::ECMProvider::System, - scenario::plugins::gazebo::ECMProvider::ISystemConfigure) diff --git a/cpp/scenario/plugins/ECMProvider/ECMSingleton.cpp b/cpp/scenario/plugins/ECMProvider/ECMSingleton.cpp deleted file mode 100644 index 1676223b5..000000000 --- a/cpp/scenario/plugins/ECMProvider/ECMSingleton.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT) - * All rights reserved. - * - * This project is dual licensed under LGPL v2.1+ or Apache License. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * This software may be modified and distributed under the terms of the - * GNU Lesser General Public License v2.1 or any later version. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "scenario/plugins/gazebo/ECMSingleton.h" -#include "scenario/gazebo/Log.h" - -#include -#include -#include -#include -#include - -using namespace scenario::plugins::gazebo; - -class ECMSingleton::Impl -{ -public: - struct ResourcePtrs - { - ResourcePtrs() = delete; - ResourcePtrs(ignition::gazebo::EntityComponentManager* _ecm, - ignition::gazebo::EventManager* _eventMgr) - : ecm(_ecm) - , eventMgr(_eventMgr) - {} - - ignition::gazebo::EntityComponentManager* ecm = nullptr; - ignition::gazebo::EventManager* eventMgr = nullptr; - }; - - mutable std::recursive_mutex mutex; - - using WorldName = std::string; - std::unordered_map resources; -}; - -ECMSingleton::ECMSingleton() - : pImpl{new Impl()} -{} - -ECMSingleton::~ECMSingleton() = default; - -ECMSingleton& ECMSingleton::Instance() -{ - static ECMSingleton instance; - return instance; -} - -void ECMSingleton::clean(const std::string& worldName) -{ - std::unique_lock lock(pImpl->mutex); - - if (worldName.empty()) { - pImpl->resources.clear(); - return; - } - - if (!this->hasWorld(worldName)) { - sError << "Resources of world " << worldName << " not found" - << std::endl; - return; - } - - pImpl->resources.erase(worldName); -} - -bool ECMSingleton::valid(const std::string& worldName) const -{ - std::unique_lock lock(pImpl->mutex); - - if (!this->hasWorld(worldName)) { - sDebug << "World" << worldName << " not found" << std::endl; - return false; - } - - if (!worldName.empty()) { - const auto& ptrs = pImpl->resources.at(worldName); - return ptrs.ecm && ptrs.eventMgr; - } - else { - bool valid = true; - for (const auto& [_, resources] : pImpl->resources) { - valid = valid && resources.ecm && resources.eventMgr; - } - - return valid; - } -} - -bool ECMSingleton::hasWorld(const std::string& worldName) const -{ - std::unique_lock lock(pImpl->mutex); - - if (worldName.empty()) { - return pImpl->resources.size() != 0; - } - - return pImpl->resources.find(worldName) != pImpl->resources.end(); -} - -std::vector ECMSingleton::worldNames() const -{ - std::unique_lock lock(pImpl->mutex); - std::vector worldNames; - - for (const auto& [key, _] : pImpl->resources) { - worldNames.emplace_back(key); - } - - return worldNames; -} - -ignition::gazebo::EventManager* -ECMSingleton::getEventManager(const std::string& worldName) const -{ - std::unique_lock lock(pImpl->mutex); - - if (!this->hasWorld(worldName)) { - sError << "Resources of world " << worldName << " not found" - << std::endl; - return nullptr; - } - - if (!this->valid(worldName)) { - sError << "Resources of world " << worldName << " not valid" - << std::endl; - return nullptr; - } - - return pImpl->resources.at(worldName).eventMgr; -} - -ignition::gazebo::EntityComponentManager* -ECMSingleton::getECM(const std::string& worldName) const -{ - std::unique_lock lock(pImpl->mutex); - - if (!this->hasWorld(worldName)) { - sError << "Resources of world " << worldName << " not found" - << std::endl; - return nullptr; - } - - if (!this->valid(worldName)) { - sError << "Resources of world " << worldName << " not valid" - << std::endl; - return nullptr; - } - - return pImpl->resources.at(worldName).ecm; -} - -bool ECMSingleton::storePtrs(ignition::gazebo::EntityComponentManager* ecm, - ignition::gazebo::EventManager* eventMgr, - const std::string& worldName) -{ - if (!ecm || !eventMgr) { - sError << "The pointer to the ECM or EventManager is not valid" - << std::endl; - return false; - } - - if (worldName.empty()) { - sError << "The world name is empty" << std::endl; - return false; - } - - std::unique_lock lock(pImpl->mutex); - - if (this->hasWorld(worldName)) { - sError << "Resources of world " << worldName - << " have been already stored" << std::endl; - return false; - } - - pImpl->resources.emplace(worldName, Impl::ResourcePtrs(ecm, eventMgr)); - return true; -} diff --git a/cpp/scenario/plugins/ECMProvider/include/scenario/plugins/gazebo/ECMProvider.h b/cpp/scenario/plugins/ECMProvider/include/scenario/plugins/gazebo/ECMProvider.h deleted file mode 100644 index f99dafa2b..000000000 --- a/cpp/scenario/plugins/ECMProvider/include/scenario/plugins/gazebo/ECMProvider.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT) - * All rights reserved. - * - * This project is dual licensed under LGPL v2.1+ or Apache License. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * This software may be modified and distributed under the terms of the - * GNU Lesser General Public License v2.1 or any later version. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SCENARIO_PLUGINS_GAZEBO_ECMPROVIDER -#define SCENARIO_PLUGINS_GAZEBO_ECMPROVIDER - -#include -#include -#include -#include -#include - -#include - -namespace scenario::plugins::gazebo { - class ECMProvider; -} // namespace scenario::plugins::gazebo - -class scenario::plugins::gazebo::ECMProvider final - : public ignition::gazebo::System - , public ignition::gazebo::ISystemConfigure -{ -public: - ECMProvider(); - ~ECMProvider() override; - - void Configure(const ignition::gazebo::Entity& entity, - const std::shared_ptr& sdf, - ignition::gazebo::EntityComponentManager& ecm, - ignition::gazebo::EventManager& eventMgr) override; - -private: - class Impl; - std::unique_ptr pImpl = nullptr; -}; - -#endif // SCENARIO_PLUGINS_GAZEBO_ECMPROVIDER diff --git a/cpp/scenario/plugins/ECMProvider/include/scenario/plugins/gazebo/ECMSingleton.h b/cpp/scenario/plugins/ECMProvider/include/scenario/plugins/gazebo/ECMSingleton.h deleted file mode 100644 index f9400ccb5..000000000 --- a/cpp/scenario/plugins/ECMProvider/include/scenario/plugins/gazebo/ECMSingleton.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT) - * All rights reserved. - * - * This project is dual licensed under LGPL v2.1+ or Apache License. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * This software may be modified and distributed under the terms of the - * GNU Lesser General Public License v2.1 or any later version. - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SCENARIO_PLUGINS_GAZEBO_ECMSINGLETON_H -#define SCENARIO_PLUGINS_GAZEBO_ECMSINGLETON_H - -#include "ignition/gazebo/EntityComponentManager.hh" -#include "ignition/gazebo/EventManager.hh" - -#include - -namespace scenario::plugins::gazebo { - class ECMSingleton; -} // namespace scenario::plugins::gazebo - -class scenario::plugins::gazebo::ECMSingleton -{ -public: - ECMSingleton(); - ~ECMSingleton(); - - ECMSingleton(ECMSingleton&) = delete; - void operator=(const ECMSingleton&) = delete; - - static ECMSingleton& Instance(); - - void clean(const std::string& worldName = {}); - bool valid(const std::string& worldName = {}) const; - bool hasWorld(const std::string& worldName = {}) const; - - std::vector worldNames() const; - ignition::gazebo::EventManager* - getEventManager(const std::string& worldName) const; - ignition::gazebo::EntityComponentManager* - getECM(const std::string& worldName) const; - - bool storePtrs(ignition::gazebo::EntityComponentManager* ecm, - ignition::gazebo::EventManager* eventMgr, - const std::string& worldName); - -private: - class Impl; - std::unique_ptr pImpl; -}; - -#endif // SCENARIO_PLUGINS_GAZEBO_ECMSINGLETON_H diff --git a/cpp/scenario/plugins/Physics/Physics.cpp b/cpp/scenario/plugins/Physics/Physics.cpp deleted file mode 100644 index efdd5181f..000000000 --- a/cpp/scenario/plugins/Physics/Physics.cpp +++ /dev/null @@ -1,2556 +0,0 @@ -/* - * Copyright (C) 2020 Open Source Robotics Foundation - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Physics.h" -#include "scenario/gazebo/components/ExternalWorldWrenchCmdWithDuration.h" -#include "scenario/gazebo/components/HistoryOfAppliedJointForces.h" -#include "scenario/gazebo/components/JointAcceleration.h" -#include "scenario/gazebo/components/SimulatedTime.h" -#include "scenario/gazebo/components/WorldVelocityCmd.h" -#include "scenario/gazebo/helpers.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using namespace ignition; -using namespace scenario::plugins::gazebo; -using namespace ignition::gazebo; -using namespace ignition::gazebo::systems; -using namespace ignition::gazebo::components; - -class Physics::Impl -{ -public: - /// \brief This is the minimum set of features that any physics engine must - /// implement to be supported by this system. - /// New features can't be added to this list in minor / patch releases, in - /// order to maintain backwards compatibility with downstream physics - /// plugins. - struct MinimumFeatureList - : ignition::physics::FeatureList< // - ignition::physics::FindFreeGroupFeature, - ignition::physics::SetFreeGroupWorldPose, - ignition::physics::FreeGroupFrameSemantics, - ignition::physics::LinkFrameSemantics, - ignition::physics::SetFreeGroupWorldVelocity, - ignition::physics::ForwardStep, - ignition::physics::RemoveEntities, - ignition::physics::sdf::ConstructSdfLink, - ignition::physics::sdf::ConstructSdfModel, - ignition::physics::sdf::ConstructSdfWorld> - {}; - - /// \brief Engine type with just the minimum features. - using EnginePtrType = ignition::physics::EnginePtr< // - ignition::physics::FeaturePolicy3d, - MinimumFeatureList>; - - /// \brief World type with just the minimum features. - using WorldPtrType = ignition::physics::WorldPtr< // - ignition::physics::FeaturePolicy3d, - MinimumFeatureList>; - - /// \brief Model type with just the minimum features. - using ModelPtrType = ignition::physics::ModelPtr< // - ignition::physics::FeaturePolicy3d, - MinimumFeatureList>; - - /// \brief Link type with just the minimum features. - using LinkPtrType = ignition::physics::LinkPtr< // - ignition::physics::FeaturePolicy3d, - MinimumFeatureList>; - - /// \brief Free group type with just the minimum features. - using FreeGroupPtrType = ignition::physics::FreeGroupPtr< // - ignition::physics::FeaturePolicy3d, - MinimumFeatureList>; - - /// \brief Create physics entities - /// \param[in] _ecm Constant reference to ECM. - void CreatePhysicsEntities(const EntityComponentManager& _ecm); - - /// \brief Remove physics entities if they are removed from the ECM - /// \param[in] _ecm Constant reference to ECM. - void RemovePhysicsEntities(const EntityComponentManager& _ecm); - - /// \brief Update physics from components - /// \param[in] _ecm Constant reference to ECM. - void UpdatePhysics(const ignition::gazebo::UpdateInfo& _info, - EntityComponentManager& _ecm); - - /// \brief Step the simulationrfor each world - /// \param[in] _dt Duration - void Step(const std::chrono::steady_clock::duration& _dt); - - /// \brief Update components from physics simulation - /// \param[in] _ecm Mutable reference to ECM. - void UpdateSim(const ignition::gazebo::UpdateInfo& _info, - EntityComponentManager& _ecm); - - /// \brief Update collision components from physics simulation - /// \param[in] _ecm Mutable reference to ECM. - void UpdateCollisions(EntityComponentManager& _ecm); - - /// \brief FrameData relative to world at a given offset pose - /// \param[in] _link ign-physics link - /// \param[in] _pose Offset pose in which to compute the frame data - /// \returns FrameData at the given offset pose - ignition::physics::FrameData3d - LinkFrameDataAtOffset(const LinkPtrType& _link, - const math::Pose3d& _pose) const; - - /// \brief Get transform from one ancestor entity to a descendant entity - /// that are in the same model. - /// \param[in] _from An ancestor of the _to entity. - /// \param[in] _to A descendant of the _from entity. - /// \return Pose transform between the two entities - ignition::math::Pose3d - RelativePose(const Entity& _from, - const Entity& _to, - const EntityComponentManager& _ecm) const; - - /// \brief A map between world entity ids in the ECM to World Entities in - /// ign-physics. - std::unordered_map entityWorldMap; - - /// \brief A map between model entity ids in the ECM to Model Entities in - /// ign-physics. - std::unordered_map entityModelMap; - - /// \brief A map between link entity ids in the ECM to Link Entities in - /// ign-physics. - std::unordered_map entityLinkMap; - - /// \brief Reverse of entityLinkMap. This is used for finding the Entity - /// associated with a physics Link - std::unordered_map linkEntityMap; - - /// \brief A map between model entity ids in the ECM to whether its battery - /// has drained. - std::unordered_map entityOffMap; - - /// \brief used to store whether physics objects have been created. - bool initialized = false; - - /// \brief Pointer to the underlying ign-physics Engine entity. - EnginePtrType engine = nullptr; - - /// \brief Vector3d equality comparison function. - std::function vec3Eql{ - [](const math::Vector3d& _a, const math::Vector3d& _b) { - return _a.Equal(_b, 1e-6); - }}; - - /// \brief Pose3d equality comparison function. - std::function pose3Eql{ - [](const math::Pose3d& _a, const math::Pose3d& _b) { - return _a.Pos().Equal(_b.Pos(), 1e-6) - && math::equal(_a.Rot().X(), _b.Rot().X(), 1e-6) - && math::equal(_a.Rot().Y(), _b.Rot().Y(), 1e-6) - && math::equal(_a.Rot().Z(), _b.Rot().Z(), 1e-6) - && math::equal(_a.Rot().W(), _b.Rot().W(), 1e-6); - }}; - - /// \brief AxisAlignedBox equality comparison function. - std::function - axisAlignedBoxEql{ - [](const math::AxisAlignedBox& _a, const math::AxisAlignedBox& _b) { - return _a == _b; - }}; - - /// \brief Environment variable which holds paths to look for engine plugins - std::string pluginPathEnv = "IGN_GAZEBO_PHYSICS_ENGINE_PATH"; - - ////////////////////////////////////////////////// - // Slip Compliance - - /// \brief Feature list to process `FrictionPyramidSlipCompliance` - /// components. - using FrictionPyramidSlipComplianceFeatureList = physics::FeatureList< - MinimumFeatureList, - ignition::physics::GetShapeFrictionPyramidSlipCompliance, - ignition::physics::SetShapeFrictionPyramidSlipCompliance>; - - /// \brief Shape type with slip compliance features. - using ShapeSlipParamPtrType = - physics::ShapePtr; - - /// \brief A map between shape entity ids in the ECM to Shape Entities in - /// ign-physics - /// All shapes on this map are also in `entityCollisionMap`. The difference - /// is that here they've been casted for - /// `FrictionPyramidSlipComplianceFeatureList`. - std::unordered_map entityShapeSlipParamMap; - - ////////////////////////////////////////////////// - // Joints - - /// \brief Feature list to handle joints. - using JointFeatureList = ignition::physics::FeatureList< - MinimumFeatureList, - ignition::physics::GetBasicJointProperties, - ignition::physics::GetBasicJointState, - ignition::physics::SetBasicJointState, - ignition::physics::sdf::ConstructSdfJoint>; - - /// \brief Joint type with joint features. - using JointPtrType = ignition::physics::JointPtr< // - ignition::physics::FeaturePolicy3d, - JointFeatureList>; - - /// \brief Model type with joint features (models to attach to). - using ModelJointPtrType = ignition::physics::ModelPtr< // - ignition::physics::FeaturePolicy3d, - JointFeatureList>; - - /// \brief A map between joint entity ids in the ECM to Joint Entities in - /// ign-physics - std::unordered_map entityJointMap; - - /// \brief A map between model entity ids in the ECM to Model Entities in - /// ign-physics, with attach feature. - /// All models on this map are also in `entityModelMap`. The difference is - /// that here they've been casted for `JointFeatureList`. - std::unordered_map entityModelJointMap; - - ////////////////////////////////////////////////// - // Detachable joints - - /// \brief Feature list to process `DetachableJoint` components. - using DetachableJointFeatureList = ignition::physics::FeatureList< // - JointFeatureList, - ignition::physics::AttachFixedJointFeature, - ignition::physics::DetachJointFeature, - ignition::physics::SetJointTransformFromParentFeature>; - - /// \brief Joint type with detachable joint features. - using JointDetachableJointPtrType = ignition::physics::JointPtr< // - ignition::physics::FeaturePolicy3d, - DetachableJointFeatureList>; - - /// \brief Link type with detachable joint features (links to attach to). - using LinkDetachableJointPtrType = ignition::physics::LinkPtr< // - ignition::physics::FeaturePolicy3d, - DetachableJointFeatureList>; - - /// \brief A map between joint entity ids in the ECM to Joint Entities in - /// ign-physics, with detach feature. - /// All joints on this map are also in `entityJointMap`. The difference is - /// that here they've been casted for `physics::DetachJointFeature`. - std::unordered_map - entityJointDetachableJointMap; - - /// \brief A map between link entity ids in the ECM to Link Entities in - /// ign-physics, with attach feature. - /// All links on this map are also in `entityLinkMap`. The difference is - /// that here they've been casted for `DetachableJointFeatureList`. - std::unordered_map - entityLinkDetachableJointMap; - - ////////////////////////////////////////////////// - // Collisions - - /// \brief Feature list to handle collisions. - using CollisionFeatureList = ignition::physics::FeatureList< // - MinimumFeatureList, - ignition::physics::GetContactsFromLastStepFeature, - ignition::physics::sdf::ConstructSdfCollision>; - - /// \brief Collision type with collision features. - using ShapePtrType = ignition::physics::ShapePtr< // - ignition::physics::FeaturePolicy3d, - CollisionFeatureList>; - - /// \brief Link type with collision features. - using LinkShapePtrType = ignition::physics::LinkPtr< // - ignition::physics::FeaturePolicy3d, - CollisionFeatureList>; - - /// \brief World type with collision features. - using WorldShapePtrType = ignition::physics::WorldPtr< // - ignition::physics::FeaturePolicy3d, - CollisionFeatureList>; - - /// \brief World type with just the minimum features. Non-pointer. - using WorldShapeType = ignition::physics::World< // - ignition::physics::FeaturePolicy3d, - CollisionFeatureList>; - - /// \brief A map between collision entity ids in the ECM to Shape Entities - /// in ign-physics. - std::unordered_map entityCollisionMap; - - /// \brief A map between shape entities in ign-physics to collision entities - /// in the ECM. This is the reverse map of entityCollisionMap. - std::unordered_map collisionEntityMap; - - /// \brief A map between link entity ids in the ECM to Link Entities in - /// ign-physics, with attach feature. - /// All links on this map are also in `entityLinkMap`. The difference is - /// that here they've been casted for `CollisionFeatureList`. - std::unordered_map entityLinkCollisionMap; - - /// \brief A map between world entity ids in the ECM to World Entities in - /// ign-physics, with attach feature. - /// All worlds on this map are also in `entityWorldMap`. The difference is - /// that here they've been casted for `CollisionFeatureList`. - std::unordered_map entityWorldCollisionMap; - - ////////////////////////////////////////////////// - // Collision filtering with bitmasks - - /// \brief Feature list to filter collisions with bitmasks. - using CollisionMaskFeatureList = ignition::physics::FeatureList< - CollisionFeatureList, - ignition::physics::CollisionFilterMaskFeature>; - - /// \brief Collision type with collision filtering features. - using ShapeFilterMaskPtrType = - ignition::physics::ShapePtr; - - /// \brief A map between collision entity ids in the ECM to Shape Entities - /// in ign-physics, with collision filtering feature. All links on this map - /// are also in `entityCollisionMap`. The difference is that here they've - /// been casted for `CollisionMaskFeatureList`. - std::unordered_map entityShapeMaskMap; - - ////////////////////////////////////////////////// - // Link force - - /// \brief Feature list for applying forces to links. - using LinkForceFeatureList = ignition::physics::FeatureList< // - ignition::physics::AddLinkExternalForceTorque>; - - /// \brief Link type with bounding box feature. - using LinkForcePtrType = ignition::physics::LinkPtr< // - ignition::physics::FeaturePolicy3d, - LinkForceFeatureList>; - - /// \brief A map between link entity ids in the ECM to Link Entities in - /// ign-physics, with force feature. - /// All links on this map are also in `entityLinkMap`. The difference is - /// that here they've been casted for `LinkForceFeatureList`. - std::unordered_map entityLinkForceMap; - - ////////////////////////////////////////////////// - // Bounding box - - /// \brief Feature list for model bounding box. - using BoundingBoxFeatureList = ignition::physics::FeatureList< // - MinimumFeatureList, - ignition::physics::GetModelBoundingBox>; - - /// \brief Model type with bounding box feature. - using ModelBoundingBoxPtrType = ignition::physics::ModelPtr< // - ignition::physics::FeaturePolicy3d, - BoundingBoxFeatureList>; - - /// \brief A map between model entity ids in the ECM to Model Entities in - /// ign-physics, with bounding box feature. - /// All models on this map are also in `entityModelMap`. The difference is - /// that here they've been casted for `BoundingBoxFeatureList`. - std::unordered_map - entityModelBoundingBoxMap; - - ////////////////////////////////////////////////// - // Joint velocity command - - /// \brief Feature list for set joint velocity command. - using JointVelocityCommandFeatureList = ignition::physics::FeatureList< // - physics::SetJointVelocityCommandFeature>; - - /// \brief Joint type with set joint velocity command. - using JointVelocityCommandPtrType = ignition::physics::JointPtr< // - ignition::physics::FeaturePolicy3d, - JointVelocityCommandFeatureList>; - - /// \brief A map between joint entity ids in the ECM to Joint Entities in - /// ign-physics, with velocity command feature. - /// All joints on this map are also in `entityJointMap`. The difference is - /// that here they've been casted for `JointVelocityCommandFeatureList`. - std::unordered_map - entityJointVelocityCommandMap; - - ////////////////////////////////////////////////// - // World velocity command - using WorldVelocityCommandFeatureList = ignition::physics::FeatureList< - ignition::physics::SetFreeGroupWorldVelocity>; - - /// \brief Free group type with world velocity command. - using FreeGroupVelocityCmdPtrType = - ignition::physics::FreeGroupPtr; - - /// \brief A map between free group entity ids in the ECM - /// to FreeGroup Entities in ign-physics, with velocity command feature. - /// All FreeGroup on this map are casted for - /// `WorldVelocityCommandFeatureList`. - std::unordered_map - entityWorldVelocityCommandMap; - - ////////////////////////////////////////////////// - // Meshes - - /// \brief Feature list for meshes. - /// Include MinimumFeatureList so created collision can be automatically - /// up-cast. - using MeshFeatureList = ignition::physics::FeatureList< // - CollisionFeatureList, - ignition::physics::mesh::AttachMeshShapeFeature>; - - /// \brief Link type with meshes. - using LinkMeshPtrType = physics::LinkPtr< // - ignition::physics::FeaturePolicy3d, - MeshFeatureList>; - - /// \brief A map between link entity ids in the ECM to Link Entities in - /// ign-physics, with mesh feature. - /// All links on this map are also in `entityLinkMap`. The difference is - /// that here they've been casted for `MeshFeatureList`. - std::unordered_map entityLinkMeshMap; - - ////////////////////////////////////////////////// - // Nested Models - - /// \brief Feature list to construct nested models - using NestedModelFeatureList = ignition::physics::FeatureList< - MinimumFeatureList, - ignition::physics::sdf::ConstructSdfNestedModel>; - - /// \brief Model type with nested model feature. - using ModelNestedModelPtrType = - physics::ModelPtr; - - /// \brief World type with nested model feature. - using WorldNestedModelPtrType = - physics::WorldPtr; - - /// \brief A map between model entity ids in the ECM to Model Entities in - /// ign-physics, with Nested Model feature. - /// All models on this map are also in `entityModelMap`. The difference is - /// that here they've been casted for `ConstructedSdfNestedModel`. - std::unordered_map - entityModelNestedModelMap; - - /// \brief A map between model entity ids in the ECM to World Entities in - /// ign-physics, with Nested Model feature. - /// All models on this map are also in `entityWorldMap`. The difference is - /// that here they've been casted for `ConstructedSdfNestedModel`. - std::unordered_map - entityWorldNestedModelMap; - - /// \brief Boolean value that is true only the first call of Configure and - /// PreUpdate. - bool firstRun = true; -}; - -Physics::Physics() - : System() - , pImpl(std::make_unique()) -{} - -void Physics::Configure(const Entity& _entity, - const std::shared_ptr& _sdf, - EntityComponentManager& _ecm, - EventManager& /*_eventMgr*/) -{ - std::string pluginLib; - - // 1. Engine from component (from command line / ServerConfig) - auto engineComp = - _ecm.Component( - _entity); - if (engineComp && !engineComp->Data().empty()) { - pluginLib = engineComp->Data(); - } - // 2. Engine from SDF - else if (_sdf->HasElement("engine")) { - auto sdfClone = _sdf->Clone(); - auto engineElem = sdfClone->GetElement("engine"); - pluginLib = engineElem->Get("filename", pluginLib).first; - } - - // 3. Use DART by default - if (pluginLib.empty()) { - pluginLib = "ignition-physics-dartsim-plugin"; - } - - // Update component - if (!engineComp) { - _ecm.CreateComponent(_entity, - components::PhysicsEnginePlugin(pluginLib)); - } - else { - engineComp->SetData(pluginLib, - [](const std::string& _a, const std::string& _b) { - return _a == _b; - }); - } - - // Find engine shared library - // Look in: - // * Paths from environment variable - // * Engines installed with ign-physics - common::SystemPaths systemPaths; - systemPaths.SetPluginPathEnv(pImpl->pluginPathEnv); - systemPaths.AddPluginPaths({IGNITION_PHYSICS_ENGINE_INSTALL_DIR}); - - auto pathToLib = systemPaths.FindSharedLibrary(pluginLib); - if (pathToLib.empty()) { - ignerr << "Failed to find plugin [" << pluginLib - << "]. Have you checked the " << pImpl->pluginPathEnv - << " environment variable?" << std::endl; - return; - } - - // Load engine plugin - ignition::plugin::Loader pluginLoader; - auto plugins = pluginLoader.LoadLib(pathToLib); - if (plugins.empty()) { - ignerr << "Unable to load the [" << pathToLib << "] library.\n"; - return; - } - - auto classNames = pluginLoader.AllPlugins(); - if (classNames.empty()) { - ignerr << "No plugins found in library [" << pathToLib << "]." - << std::endl; - return; - } - - // Get the first plugin that works - for (auto className : classNames) { - auto plugin = pluginLoader.Instantiate(className); - - if (!plugin) - continue; - - pImpl->engine = ignition::physics::RequestEngine< - ignition::physics::FeaturePolicy3d, - Impl::MinimumFeatureList>::From(plugin); - - if (nullptr != pImpl->engine) { - igndbg << "Loaded [" << className << "] from library [" << pathToLib - << "]" << std::endl; - break; - } - - auto missingFeatures = ignition::physics::RequestEngine< - ignition::physics::FeaturePolicy3d, - Impl::MinimumFeatureList>::MissingFeatureNames(plugin); - - std::stringstream msg; - msg << "Plugin [" << className - << "] misses required features:" << std::endl; - for (auto feature : missingFeatures) { - msg << "- " << feature << std::endl; - } - ignwarn << msg.str(); - } - - if (nullptr == pImpl->engine) { - ignerr << "Failed to load a valid physics engine from [" << pathToLib - << "]." << std::endl; - } -} - -Physics::~Physics() = default; - -void Physics::Update(const UpdateInfo& _info, EntityComponentManager& _ecm) -{ - // \TODO(anyone) Support rewind - if (_info.dt < std::chrono::steady_clock::duration::zero()) { - ignwarn << "Detected jump back in time [" - << std::chrono::duration_cast(_info.dt) - .count() - << "s]. System may not work properly." << std::endl; - } - - // Update the component with the time in seconds that the simulation - // will have after the step - _ecm.Each( - [&](const Entity& worldEntity, - const components::World*, - components::SimulatedTime*) { - scenario::gazebo::utils::setExistingComponentData< - ignition::gazebo::components::SimulatedTime>( - &_ecm, worldEntity, _info.simTime); - return true; - }); - - if (this->pImpl->engine) { - this->pImpl->CreatePhysicsEntities(_ecm); - this->pImpl->UpdatePhysics(_info, _ecm); - - // Only step if not paused. - if (!_info.paused) { - this->pImpl->Step(_info.dt); - } - - this->pImpl->UpdateSim(_info, _ecm); - - // Entities scheduled to be removed should be removed from physics - // after the simulation step. Otherwise, since the to-be-removed - // entity still shows up in the ECM::Each the UpdatePhysics and - // UpdateSim calls will have an error - this->pImpl->RemovePhysicsEntities(_ecm); - } -} - -void Physics::Impl::CreatePhysicsEntities(const EntityComponentManager& _ecm) -{ - auto processWorld = [&](const Entity& _entity, - const components::World* /* _world */, - const components::Name* _name, - const components::Gravity* _gravity) -> bool { - if (this->entityWorldMap.find(_entity) != this->entityWorldMap.end()) { - ignwarn << "World entity [" << _entity - << "] marked as new, but it's already on the map." - << std::endl; - return true; - } - - sdf::World world; - world.SetName(_name->Data()); - world.SetGravity(_gravity->Data()); - auto worldPtrPhys = this->engine->ConstructWorld(world); - this->entityWorldMap.insert(std::make_pair(_entity, worldPtrPhys)); - - return true; - }; - - auto processModel = [&](const Entity& _entity, - const components::Model*, - const components::Name* _name, - const components::Pose* _pose, - const components::ParentEntity* _parent) -> bool { - // ignerr << "model " << _name->Data() << std::endl; - // Check if model already exists - if (this->entityModelMap.find(_entity) != this->entityModelMap.end()) { - ignwarn << "Model entity [" << _entity - << "] marked as new, but it's already on the map." - << std::endl; - return true; - } - - // TODO(anyone) Don't load models unless they have collisions - - // Check if parent world / model exists - sdf::Model model; - model.SetName(_name->Data()); - model.SetRawPose(_pose->Data()); - - auto staticComp = _ecm.Component(_entity); - if (staticComp && staticComp->Data()) { - model.SetStatic(staticComp->Data()); - } - - auto selfCollideComp = _ecm.Component(_entity); - if (selfCollideComp && selfCollideComp->Data()) { - model.SetSelfCollide(selfCollideComp->Data()); - } - - // check if parent is a world - auto worldIt = this->entityWorldMap.find(_parent->Data()); - if (worldIt != this->entityWorldMap.end()) { - auto worldPtrPhys = worldIt->second; - - // Use the ConstructNestedModel feature for nested models - if (model.ModelCount() > 0) { - auto nestedModelFeature = - entityCast(_parent->Data(), - worldPtrPhys, - this->entityWorldNestedModelMap); - if (!nestedModelFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to construct nested models, but the " - << "phyiscs engine doesn't support feature " - << "[ConstructSdfNestedModelFeature]. " - << "Nested model will be ignored." << std::endl; - informed = true; - } - return true; - } - auto modelPtrPhys = - nestedModelFeature->ConstructNestedModel(model); - this->entityModelMap.insert( - std::make_pair(_entity, modelPtrPhys)); - } - else { - auto modelPtrPhys = worldPtrPhys->ConstructModel(model); - this->entityModelMap.insert( - std::make_pair(_entity, modelPtrPhys)); - } - } - // check if parent is a model (nested model) - else { - auto parentIt = this->entityModelMap.find(_parent->Data()); - if (parentIt != this->entityModelMap.end()) { - auto parentPtrPhys = parentIt->second; - - auto nestedModelFeature = - entityCast(_parent->Data(), - parentPtrPhys, - this->entityModelNestedModelMap); - if (!nestedModelFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to construct nested models, but the " - << "physics engine doesn't support feature " - << "[ConstructSdfNestedModelFeature]. " - << "Nested model will be ignored." << std::endl; - informed = true; - } - return true; - } - - // override static property only if parent is static. - auto parentStaticComp = - _ecm.Component(_parent->Data()); - if (parentStaticComp && parentStaticComp->Data()) - model.SetStatic(true); - - auto modelPtrPhys = - nestedModelFeature->ConstructNestedModel(model); - if (modelPtrPhys) { - this->entityModelMap.insert( - std::make_pair(_entity, modelPtrPhys)); - } - else { - ignerr << "Model: '" << _name->Data() << "' not loaded. " - << "Failed to create nested model." << std::endl; - } - } - else { - ignwarn << "Model's parent entity [" << _parent->Data() - << "] not found on world / model map." << std::endl; - return true; - } - } - - return true; - }; - - auto processLink = [&](const Entity& _entity, - const components::Link* /* _link */, - const components::Name* _name, - const components::Pose* _pose, - const components::ParentEntity* _parent) -> bool { - // Check if link already exists - if (this->entityLinkMap.find(_entity) != this->entityLinkMap.end()) { - ignwarn << "Link entity [" << _entity - << "] marked as new, but it's already on the map." - << std::endl; - return true; - } - - // TODO(anyone) Don't load links unless they have collisions - - // Check if parent model exists - if (this->entityModelMap.find(_parent->Data()) - == this->entityModelMap.end()) { - ignwarn << "Link's parent entity [" << _parent->Data() - << "] not found on model map." << std::endl; - return true; - } - auto modelPtrPhys = this->entityModelMap.at(_parent->Data()); - - sdf::Link link; - link.SetName(_name->Data()); - link.SetRawPose(_pose->Data()); - - // get link inertial - auto inertial = _ecm.Component(_entity); - if (inertial) { - link.SetInertial(inertial->Data()); - } - - auto linkPtrPhys = modelPtrPhys->ConstructLink(link); - this->entityLinkMap.insert(std::make_pair(_entity, linkPtrPhys)); - this->linkEntityMap.insert(std::make_pair(linkPtrPhys, _entity)); - - return true; - }; - - auto processCollision = - [&](const Entity& _entity, - const components::Collision*, - const components::Name* _name, - const components::Pose* _pose, - const components::Geometry* _geom, - const components::CollisionElement* _collElement, - const components::ParentEntity* _parent) -> bool { - if (this->entityCollisionMap.find(_entity) - != this->entityCollisionMap.end()) { - ignwarn << "Collision entity [" << _entity - << "] marked as new, but it's already on the map." - << std::endl; - return true; - } - - // Check if parent link exists - if (this->entityLinkMap.find(_parent->Data()) - == this->entityLinkMap.end()) { - ignwarn << "Collision's parent entity [" << _parent->Data() - << "] not found on link map." << std::endl; - return true; - } - auto linkPtrPhys = this->entityLinkMap.at(_parent->Data()); - - // Make a copy of the collision DOM so we can set its pose which has - // been resolved and is now expressed w.r.t the parent link of the - // collision. - sdf::Collision collision = _collElement->Data(); - collision.SetRawPose(_pose->Data()); - collision.SetPoseRelativeTo(""); - auto collideBitmask = collision.Surface()->Contact()->CollideBitmask(); - - ShapePtrType collisionPtrPhys; - if (_geom->Data().Type() == sdf::GeometryType::MESH) { - const sdf::Mesh* meshSdf = _geom->Data().MeshShape(); - if (nullptr == meshSdf) { - ignwarn << "Mesh geometry for collision [" << _name->Data() - << "] missing mesh shape." << std::endl; - return true; - } - - auto& meshManager = *ignition::common::MeshManager::Instance(); - auto fullPath = asFullPath(meshSdf->Uri(), meshSdf->FilePath()); - auto* mesh = meshManager.Load(fullPath); - if (nullptr == mesh) { - ignwarn << "Failed to load mesh from [" << fullPath << "]." - << std::endl; - return true; - } - - auto linkMeshFeature = entityCast( - _parent->Data(), linkPtrPhys, this->entityLinkMeshMap); - if (!linkMeshFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to process mesh geometries, but the " - << "physics engine doesn't support feature " - << "[AttachMeshShapeFeature]. Meshes will be ignored." - << std::endl; - informed = true; - } - return true; - } - - collisionPtrPhys = linkMeshFeature->AttachMeshShape( - _name->Data(), - *mesh, - math::eigen3::convert(_pose->Data()), - math::eigen3::convert(meshSdf->Scale())); - } - else { - auto linkCollisionFeature = entityCast( - _parent->Data(), linkPtrPhys, this->entityLinkCollisionMap); - if (!linkCollisionFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to process collisions, but the physics " - << "engine doesn't support feature " - << "[ConstructSdfCollision]. Collisions will be " - << "ignored." << std::endl; - informed = true; - } - return true; - } - - collisionPtrPhys = - linkCollisionFeature->ConstructCollision(collision); - } - // Check that the physics engine has a filter mask feature - // Set the collide_bitmask if it does - auto filterMaskFeature = - entityCast(_parent->Data(), collisionPtrPhys, entityShapeMaskMap); - if (filterMaskFeature) { - filterMaskFeature->SetCollisionFilterMask(collideBitmask); - } - else { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to set collision bitmasks, but the physics " - << "engine doesn't support feature [CollisionFilterMask]. " - << "Collision bitmasks will be ignored." << std::endl; - informed = true; - } - } - - this->entityCollisionMap.insert( - std::make_pair(_entity, collisionPtrPhys)); - this->collisionEntityMap.insert( - std::make_pair(collisionPtrPhys, _entity)); - return true; - }; - - auto processJoint = - [&](const Entity& _entity, - const components::Joint* /* _joint */, - const components::Name* _name, - const components::JointType* _jointType, - const components::Pose* _pose, - const components::ThreadPitch* _threadPitch, - const components::ParentEntity* _parentModel, - const components::ParentLinkName* _parentLinkName, - const components::ChildLinkName* _childLinkName) -> bool { - // Check if joint already exists - if (this->entityJointMap.find(_entity) != this->entityJointMap.end()) { - ignwarn << "Joint entity [" << _entity - << "] marked as new, but it's already on the map." - << std::endl; - return true; - } - - // Check if parent model exists - if (this->entityModelMap.find(_parentModel->Data()) - == this->entityModelMap.end()) { - ignwarn << "Joint's parent entity [" << _parentModel->Data() - << "] not found on model map." << std::endl; - return true; - } - auto modelPtrPhys = this->entityModelMap.at(_parentModel->Data()); - - auto modelJointFeature = entityCast( - _parentModel->Data(), modelPtrPhys, this->entityModelJointMap); - if (!modelJointFeature) { - static bool informed{false}; - if (!informed) { - igndbg << "Attempting to process joints, but the physics " - << "engine doesn't support joint features. " - << "Joints will be ignored." << std::endl; - informed = true; - } - - // Break Each call since no joints can be processed - return false; - } - - sdf::Joint joint; - joint.SetName(_name->Data()); - joint.SetType(_jointType->Data()); - joint.SetRawPose(_pose->Data()); - joint.SetThreadPitch(_threadPitch->Data()); - - joint.SetParentLinkName(_parentLinkName->Data()); - joint.SetChildLinkName(_childLinkName->Data()); - - auto jointAxis = _ecm.Component(_entity); - auto jointAxis2 = _ecm.Component(_entity); - - // Since we're making copies of the joint axes that were created - // using `Model::Load`, frame semantics should work for resolving - // their xyz axis - if (jointAxis) - joint.SetAxis(0, jointAxis->Data()); - if (jointAxis2) - joint.SetAxis(1, jointAxis2->Data()); - - // Use the parent link's parent model as the model of this joint - auto jointPtrPhys = modelJointFeature->ConstructJoint(joint); - - if (jointPtrPhys.Valid()) { - // Some joints may not be supported, so only add them to the map - // if the physics entity is valid - this->entityJointMap.insert(std::make_pair(_entity, jointPtrPhys)); - } - return true; - }; - - auto processDetachableJoint = - [&](const Entity& _entity, - const components::DetachableJoint* _jointInfo) -> bool { - if (_jointInfo->Data().jointType != "fixed") { - ignerr << "Detachable joint type [" << _jointInfo->Data().jointType - << "] is currently not supported" << std::endl; - return true; - } - // Check if joint already exists - if (this->entityJointMap.find(_entity) != this->entityJointMap.end()) { - ignwarn << "Joint entity [" << _entity - << "] marked as new, but it's already on the map." - << std::endl; - return true; - } - - // Check if the link entities exist in the physics engine - auto parentLinkPhysIt = - this->entityLinkMap.find(_jointInfo->Data().parentLink); - if (parentLinkPhysIt == this->entityLinkMap.end()) { - ignwarn << "DetachableJoint's parent link entity [" - << _jointInfo->Data().parentLink - << "] not found in link map." << std::endl; - return true; - } - - auto childLinkEntity = _jointInfo->Data().childLink; - - // Get child link - auto childLinkIt = this->entityLinkMap.find(childLinkEntity); - if (childLinkIt == this->entityLinkMap.end()) { - ignwarn << "Failed to find joint's child link [" << childLinkEntity - << "]." << std::endl; - return true; - } - - auto childLinkDetachableJointFeature = - entityCast(childLinkEntity, - childLinkIt->second, - this->entityLinkDetachableJointMap); - if (!childLinkDetachableJointFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to create a detachable joint, but the " - "physics" - << " engine doesn't support feature " - << "[AttachFixedJointFeature]. Detachable joints will be " - << "ignored." << std::endl; - informed = true; - } - - // Break Each call since no DetachableJoints can be processed - return false; - } - - const auto poseParent = - parentLinkPhysIt->second->FrameDataRelativeToWorld().pose; - const auto poseChild = - childLinkDetachableJointFeature->FrameDataRelativeToWorld().pose; - - // Pose of child relative to parent - auto poseParentChild = poseParent.inverse() * poseChild; - auto jointPtrPhys = childLinkDetachableJointFeature->AttachFixedJoint( - parentLinkPhysIt->second); - if (jointPtrPhys.Valid()) { - // We let the joint be at the origin of the child link. - jointPtrPhys->SetTransformFromParent(poseParentChild); - - igndbg << "Creating detachable joint [" << _entity << "]" - << std::endl; - this->entityJointMap.insert(std::make_pair(_entity, jointPtrPhys)); - } - else { - ignwarn << "DetachableJoint could not be created." << std::endl; - } - return true; - }; - - if (this->firstRun) { - this->firstRun = false; - - _ecm.Each( - processWorld); - - _ecm.Each(processModel); - - _ecm.Each(processLink); - - // We don't need to add visuals to the physics engine. - - _ecm.Each(processCollision); - - _ecm.Each(processJoint); - - _ecm.Each( - [&](const Entity& _entity, const components::BatterySoC*) -> bool { - // Parent entity of battery is model entity - this->entityOffMap.insert( - std::make_pair(_ecm.ParentEntity(_entity), false)); - return true; - }); - - _ecm.Each(processDetachableJoint); - } - else { - _ecm.EachNew( - processWorld); - - _ecm.EachNew(processModel); - - _ecm.EachNew(processLink); - - // We don't need to add visuals to the physics engine. - - _ecm.EachNew(processCollision); - - _ecm.EachNew(processJoint); - - _ecm.EachNew( - [&](const Entity& _entity, const components::BatterySoC*) -> bool { - // Parent entity of battery is model entity - this->entityOffMap.insert( - std::make_pair(_ecm.ParentEntity(_entity), false)); - return true; - }); - - _ecm.EachNew(processDetachableJoint); - } -} - -void Physics::Impl::RemovePhysicsEntities(const EntityComponentManager& _ecm) -{ - // Assume the world will not be erased - // Only removing models is supported by ign-physics right now so we only - // remove links, joints and collisions if they are children of the - // removed model. We assume the links, joints and collisions will be - // removed from the physics engine when the containing model gets - // removed so, here, we only remove the entities from the gazebo - // entity->physics entity map. - _ecm.EachRemoved([&](const Entity& _entity, - const components::Model* - /* _model */) -> bool { - // Remove model if found - auto modelIt = this->entityModelMap.find(_entity); - if (modelIt != this->entityModelMap.end()) { - // Remove child links, collisions and joints first - for (const auto& childLink : - _ecm.ChildrenByComponents(_entity, components::Link())) { - for (const auto& childCollision : _ecm.ChildrenByComponents( - childLink, components::Collision())) { - auto collIt = this->entityCollisionMap.find(childCollision); - if (collIt != this->entityCollisionMap.end()) { - this->collisionEntityMap.erase(collIt->second); - this->entityCollisionMap.erase(collIt); - } - } - // First erase the entry associated with this link from the - // linkEntityMap which is the reverse of entityLinkMap - auto linkPhysIt = this->entityLinkMap.find(childLink); - if (linkPhysIt != this->entityLinkMap.end()) { - this->linkEntityMap.erase(linkPhysIt->second); - } - this->entityLinkMap.erase(childLink); - } - - for (const auto& childJoint : - _ecm.ChildrenByComponents(_entity, components::Joint())) { - this->entityJointMap.erase(childJoint); - } - - // Remove the model from the physics engine - modelIt->second->Remove(); - this->entityModelMap.erase(_entity); - } - return true; - }); - - _ecm.EachRemoved( - [&](const Entity& _entity, const components::DetachableJoint*) -> bool { - auto jointIt = this->entityJointMap.find(_entity); - if (jointIt == this->entityJointMap.end()) { - ignwarn << "Failed to find joint [" << _entity << "]." - << std::endl; - return true; - } - - auto castEntity = entityCast( - _entity, jointIt->second, this->entityJointDetachableJointMap); - if (!castEntity) { - static bool informed{false}; - if (!informed) { - igndbg << "Attempting to detach a joint, but the physics " - << "engine doesn't support feature " - << "[DetachJointFeature]. Joint won't be detached." - << std::endl; - informed = true; - } - - // Break Each call since no DetachableJoints can be processed - return false; - } - - igndbg << "Detaching joint [" << _entity << "]" << std::endl; - castEntity->Detach(); - return true; - }); -} - -void Physics::Impl::UpdatePhysics(const ignition::gazebo::UpdateInfo& _info, - EntityComponentManager& _ecm) -{ - // Battery state - _ecm.Each( - [&](const Entity& _entity, const components::BatterySoC* _bat) { - if (_bat->Data() <= 0) - entityOffMap[_ecm.ParentEntity(_entity)] = true; - else - entityOffMap[_ecm.ParentEntity(_entity)] = false; - return true; - }); - - // Handle joint state - _ecm.Each([&](const Entity& _entity, - const components::Joint*, - const components::Name* - _name) { - auto jointIt = this->entityJointMap.find(_entity); - if (jointIt == this->entityJointMap.end()) - return true; - - // Model is out of battery - if (this->entityOffMap[_ecm.ParentEntity(_entity)]) { - std::size_t nDofs = jointIt->second->GetDegreesOfFreedom(); - for (std::size_t i = 0; i < nDofs; ++i) { - jointIt->second->SetForce(i, 0); - } - return true; - } - - auto posReset = _ecm.Component(_entity); - auto velReset = _ecm.Component(_entity); - - // Reset the velocity - if (velReset) { - auto& jointVelocity = velReset->Data(); - - if (jointVelocity.size() - != jointIt->second->GetDegreesOfFreedom()) { - ignwarn - << "There is a mismatch in the degrees of freedom " - "between " - << "Joint [" << _name->Data() << "(Entity=" << _entity - << ")] and its JointVelocityReset component. The joint has " - << jointIt->second->GetDegreesOfFreedom() << " while the " - << " component has " << jointVelocity.size() << ".\n"; - } - - std::size_t nDofs = std::min( - jointVelocity.size(), jointIt->second->GetDegreesOfFreedom()); - - for (std::size_t i = 0; i < nDofs; ++i) { - jointIt->second->SetVelocity(i, jointVelocity[i]); - } - } - - // Reset the position - if (posReset) { - auto& jointPosition = posReset->Data(); - - if (jointPosition.size() - != jointIt->second->GetDegreesOfFreedom()) { - ignwarn - << "There is a mismatch in the degrees of freedom " - "between " - << "Joint [" << _name->Data() << "(Entity=" << _entity - << ")] and its JointPositionReset component. The joint has " - << jointIt->second->GetDegreesOfFreedom() << " while the " - << " component has " << jointPosition.size() << ".\n"; - } - std::size_t nDofs = std::min( - jointPosition.size(), jointIt->second->GetDegreesOfFreedom()); - for (std::size_t i = 0; i < nDofs; ++i) { - jointIt->second->SetPosition(i, jointPosition[i]); - } - } - - auto force = _ecm.Component(_entity); - auto velCmd = _ecm.Component(_entity); - - if (force) { - if (force->Data().size() - != jointIt->second->GetDegreesOfFreedom()) { - ignwarn << "There is a mismatch in the degrees of freedom " - "between " - << "Joint [" << _name->Data() << "(Entity=" << _entity - << ")] and its JointForceCmd component. The joint has " - << jointIt->second->GetDegreesOfFreedom() - << " while the " - << " component has " << force->Data().size() << ".\n"; - } - std::size_t nDofs = std::min( - force->Data().size(), jointIt->second->GetDegreesOfFreedom()); - for (std::size_t i = 0; i < nDofs; ++i) { - jointIt->second->SetForce(i, force->Data()[i]); - } - } - else { - // Only set joint velocity if joint force is not set. - // If both the cmd and reset components are found, cmd is - // ignored. - if (velCmd) { - auto velocityCmd = velCmd->Data(); - - if (velReset) { - ignwarn << "Found both JointVelocityReset and " - << "JointVelocityCmd components " - << "for Joint [" << _name->Data() - << "(Entity=" << _entity - << "]). Ignoring JointVelocityReset component." - << std::endl; - return true; - } - - if (velocityCmd.size() - != jointIt->second->GetDegreesOfFreedom()) { - ignwarn << "There is a mismatch in the degrees of freedom " - << "between Joint [" << _name->Data() - << "(Entity=" << _entity - << ")] and its JointVelocityCmd component. The " - << "joint has " - << jointIt->second->GetDegreesOfFreedom() - << " while the component has " << velocityCmd.size() - << ".\n"; - } - - auto jointVelFeature = - entityCast(_entity, - jointIt->second, - this->entityJointVelocityCommandMap); - if (!jointVelFeature) { - return true; - } - - std::size_t nDofs = std::min( - velocityCmd.size(), jointIt->second->GetDegreesOfFreedom()); - - for (std::size_t i = 0; i < nDofs; ++i) { - jointVelFeature->SetVelocityCommand(i, velocityCmd[i]); - } - } - } - return true; - }); - - // Link wrenches - _ecm.Each( - [&](const Entity& _entity, - const components::ExternalWorldWrenchCmd* _wrenchComp) { - auto linkIt = this->entityLinkMap.find(_entity); - if (linkIt == this->entityLinkMap.end()) { - ignwarn << "Failed to find link [" << _entity << "]." - << std::endl; - return true; - } - - auto linkForceFeature = - entityCast(_entity, linkIt->second, this->entityLinkForceMap); - if (!linkForceFeature) { - static bool informed{false}; - if (!informed) { - igndbg << "Attempting to apply a wrench, but the physics " - << "engine doesn't support feature " - << "[AddLinkExternalForceTorque]. Wrench will be " - "ignored." - << std::endl; - informed = true; - } - - // Break Each call since no ExternalWorldWrenchCmd's can be - // processed - return false; - } - - math::Vector3 force = msgs::Convert(_wrenchComp->Data().force()); - math::Vector3 torque = msgs::Convert(_wrenchComp->Data().torque()); - linkForceFeature->AddExternalForce(math::eigen3::convert(force)); - linkForceFeature->AddExternalTorque(math::eigen3::convert(torque)); - - return true; - }); - - // Link wrenches with duration - if (!_info.paused) { - _ecm.Each( - [&](const Entity& _entity, - components::ExternalWorldWrenchCmdWithDuration* - _wrenchWithDurComp) { - auto linkIt = this->entityLinkMap.find(_entity); - if (linkIt == this->entityLinkMap.end()) { - ignwarn << "Failed to find link [" << _entity << "]." - << std::endl; - return true; - } - - auto linkForceFeature = entityCast( - _entity, linkIt->second, this->entityLinkForceMap); - if (!linkForceFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to apply a wrench, but the physics " - << "engine doesn't support feature " - << "[AddLinkExternalForceTorque]. Wrench will be " - "ignored." - << std::endl; - informed = true; - } - - // Break Each call since no ExternalWorldWrenchCmd's can be - // processed - return false; - } - - const auto& totalWrench = - _wrenchWithDurComp->Data().totalWrench(); - const math::Vector3 force = msgs::Convert(totalWrench.force()); - const math::Vector3 torque = - msgs::Convert(totalWrench.torque()); - - linkForceFeature->AddExternalForce( - math::eigen3::convert(force)); - linkForceFeature->AddExternalTorque( - math::eigen3::convert(torque)); - - // NOTE: Cleaning could be moved to UpdateSim, but let's - // keep things all together for now - auto simTimeAfterStep = _info.simTime; - _wrenchWithDurComp->Data().cleanExpired(simTimeAfterStep); - - return true; - }); - } - - // Update model pose - _ecm.Each( - [&](const Entity& _entity, - const components::Model*, - const components::WorldPoseCmd* _poseCmd) { - auto modelIt = this->entityModelMap.find(_entity); - if (modelIt == this->entityModelMap.end()) - return true; - - // world pose cmd currently not supported for nested models - if (_entity != topLevelModel(_entity, _ecm)) { - ignerr << "Unable to set world pose for nested models." - << std::endl; - return true; - } - - // The canonical link as specified by sdformat is different from - // the canonical link of the FreeGroup object - - // TODO(addisu) Store the free group instead of searching for it - // at every iteration - auto freeGroup = modelIt->second->FindFreeGroup(); - if (!freeGroup) - return true; - - // Get canonical link offset - auto linkEntityIt = - this->linkEntityMap.find(freeGroup->CanonicalLink()); - if (linkEntityIt == this->linkEntityMap.end()) - return true; - - // set world pose of canonical link in freegroup - // canonical link might be in a nested model so use RelativePose to - // get its pose relative to this model - math::Pose3d linkPose = - this->RelativePose(_entity, linkEntityIt->second, _ecm); - - freeGroup->SetWorldPose( - math::eigen3::convert(_poseCmd->Data() * linkPose)); - - // Process pose commands for static models here, as one-time - // changes - const components::Static* staticComp = - _ecm.Component(_entity); - if (staticComp && staticComp->Data()) { - auto worldPoseComp = _ecm.Component(_entity); - if (worldPoseComp) { - auto state = - worldPoseComp->SetData(_poseCmd->Data(), this->pose3Eql) - ? ComponentState::OneTimeChange - : ComponentState::NoChange; - _ecm.SetChanged(_entity, components::Pose::typeId, state); - } - } - - return true; - }); - - // Slip compliance on Collisions - _ecm.Each( - [&](const Entity& _entity, - const components::SlipComplianceCmd* _slipCmdComp) { - auto shapeIt = this->entityCollisionMap.find(_entity); - if (shapeIt == this->entityCollisionMap.end()) { - ignwarn << "Failed to find shape [" << _entity << "]." - << std::endl; - return true; - } - - auto slipComplianceShape = entityCast( - _entity, shapeIt->second, this->entityShapeSlipParamMap); - - if (!slipComplianceShape) { - ignwarn << "Can't process Wheel Slip component, physics engine " - << "missing SetShapeFrictionPyramidSlipCompliance" - << std::endl; - - // Break Each call since no SlipCompliances can be processed - return false; - } - - if (_slipCmdComp->Data().size() == 2) { - slipComplianceShape->SetPrimarySlipCompliance( - _slipCmdComp->Data()[0]); - slipComplianceShape->SetSecondarySlipCompliance( - _slipCmdComp->Data()[1]); - } - - return true; - }); - - // Update model angular velocity - _ecm.Each( - [&](const Entity& _entity, - const components::Model*, - const components::AngularVelocityCmd* _angularVelocityCmd) { - auto modelIt = this->entityModelMap.find(_entity); - if (modelIt == this->entityModelMap.end()) - return true; - - // angular vel cmd currently not supported for nested models - if (_entity != topLevelModel(_entity, _ecm)) { - ignerr << "Unable to set angular velocity for nested models." - << std::endl; - return true; - } - - auto freeGroup = modelIt->second->FindFreeGroup(); - if (!freeGroup) - return true; - - const components::Pose* poseComp = - _ecm.Component(_entity); - math::Vector3d worldAngularVel = - poseComp->Data().Rot() * _angularVelocityCmd->Data(); - - auto worldAngularVelFeature = entityCast( - _entity, freeGroup, this->entityWorldVelocityCommandMap); - if (!worldAngularVelFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to set model angular velocity, but the " - << "physics engine doesn't support velocity commands. " - << "Velocity won't be set." << std::endl; - informed = true; - } - return true; - } - - worldAngularVelFeature->SetWorldAngularVelocity( - math::eigen3::convert(worldAngularVel)); - - return true; - }); - - // Update model linear velocity - _ecm.Each( - [&](const Entity& _entity, - const components::Model*, - const components::LinearVelocityCmd* _linearVelocityCmd) { - auto modelIt = this->entityModelMap.find(_entity); - if (modelIt == this->entityModelMap.end()) - return true; - - // linear vel cmd currently not supported for nested models - if (_entity != topLevelModel(_entity, _ecm)) { - ignerr << "Unable to set linear velocity for nested models." - << std::endl; - return true; - } - - auto freeGroup = modelIt->second->FindFreeGroup(); - if (!freeGroup) - return true; - - const components::Pose* poseComp = - _ecm.Component(_entity); - math::Vector3d worldLinearVel = - poseComp->Data().Rot() * _linearVelocityCmd->Data(); - - auto worldLinearVelFeature = entityCast( - _entity, freeGroup, this->entityWorldVelocityCommandMap); - if (!worldLinearVelFeature) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to set model linear velocity, but the " - << "physics engine doesn't support velocity commands. " - << "Velocity won't be set." << std::endl; - informed = true; - } - return true; - } - - worldLinearVelFeature->SetWorldLinearVelocity( - math::eigen3::convert(worldLinearVel)); - - return true; - }); - - // Process WorldVelocityCmd - _ecm.Each( - [&](const Entity& _entity, - const components::Model*, - components::WorldVelocityCmd* _modelWorldVelocityCmd) { - auto modelIt = this->entityModelMap.find(_entity); - if (modelIt == this->entityModelMap.end()) - return true; - - // The canonical link as specified by sdformat is different from - // the canonical link of the FreeGroup object - - // TODO(addisu) Store the free group instead of searching for it - // at every iteration - - // The FreeGroup is created only for floating-base object that - // do not have any defined joint between the world and their - // base - auto freeGroup = modelIt->second->FindFreeGroup(); - if (!freeGroup) { - ignwarn << "Failed to find FreeGroup. Linear and angular " - "velocities commands ignored." - << std::endl; - return true; - } - - ignition::math::Vector3d& modelWorldLinearVelocity = - _modelWorldVelocityCmd->Data().linear; - ignition::math::Vector3d& modelWorldAngularVelocity = - _modelWorldVelocityCmd->Data().angular; - - freeGroup->SetWorldLinearVelocity( - math::eigen3::convert(modelWorldLinearVelocity)); - freeGroup->SetWorldAngularVelocity( - math::eigen3::convert(modelWorldAngularVelocity)); - - // TODO(diego): static models from above - return true; - }); - - // Clear pending commands - // Note: Removing components from inside an Each call can be dangerous. - // Instead, we collect all the entities that have the desired components - // and remove the component from them afterward. - std::vector entitiesWorldCmd; - _ecm.Each( - [&](const Entity& _entity, components::WorldPoseCmd*) -> bool { - entitiesWorldCmd.push_back(_entity); - return true; - }); - - for (const Entity& entity : entitiesWorldCmd) { - _ecm.RemoveComponent(entity); - } - - // Populate bounding box info - // Only compute bounding box if component exists to avoid unnecessary - // computations - _ecm.Each([&](const Entity& _entity, - const components::Model*, - components::AxisAlignedBox* - _bbox) { - auto modelIt = this->entityModelMap.find(_entity); - if (modelIt == this->entityModelMap.end()) { - ignwarn << "Failed to find model [" << _entity << "]." << std::endl; - return true; - } - - auto bbModel = entityCast( - _entity, modelIt->second, this->entityModelBoundingBoxMap); - if (!bbModel) { - static bool informed{false}; - if (!informed) { - igndbg - << "Attempting to get a bounding box, but the physics " - << "engine doesn't support feature " - << "[GetModelBoundingBox]. Bounding box won't be populated." - << std::endl; - informed = true; - } - - // Break Each call since no AxisAlignedBox'es can be processed - return false; - } - - math::AxisAlignedBox bbox = - math::eigen3::convert(bbModel->GetAxisAlignedBoundingBox()); - auto state = _bbox->SetData(bbox, this->axisAlignedBoxEql) - ? ComponentState::OneTimeChange - : ComponentState::NoChange; - _ecm.SetChanged(_entity, components::AxisAlignedBox::typeId, state); - - return true; - }); - - // Clear WorldVelocityCmd - entitiesWorldCmd.clear(); - _ecm.Each( - [&](const Entity& _entity, components::WorldVelocityCmd*) -> bool { - entitiesWorldCmd.push_back(_entity); - return true; - }); - - for (const Entity& entity : entitiesWorldCmd) { - _ecm.RemoveComponent(entity); - } -} - -void Physics::Impl::Step(const std::chrono::steady_clock::duration& _dt) -{ - ignition::physics::ForwardStep::Input input; - ignition::physics::ForwardStep::State state; - ignition::physics::ForwardStep::Output output; - - input.Get() = _dt; - - for (auto& world : this->entityWorldMap) { - world.second->Step(output, state, input); - } -} - -ignition::math::Pose3d -Physics::Impl::RelativePose(const Entity& _from, - const Entity& _to, - const EntityComponentManager& _ecm) const -{ - math::Pose3d transform; - - if (_from == _to) - return transform; - - auto currentEntity = _to; - auto parentComp = _ecm.Component(_to); - while (parentComp) { - auto parentEntity = parentComp->Data(); - - // get the entity pose - auto entityPoseComp = _ecm.Component(currentEntity); - - // update transform - transform = entityPoseComp->Data() * transform; - - if (parentEntity == _from) - break; - - // set current entity to parent - currentEntity = parentEntity; - - // get entity's parent - parentComp = _ecm.Component(parentEntity); - } - - return transform; -} - -void Physics::Impl::UpdateSim(const ignition::gazebo::UpdateInfo& _info, - EntityComponentManager& _ecm) -{ - // local pose - _ecm.Each([&](const Entity& _entity, - components::Link* /*_link*/, - components::Pose* _pose, - const components::ParentEntity* - _parent) -> bool { - // If parent is static, don't process pose changes as periodic - const auto* staticComp = - _ecm.Component(_parent->Data()); - - if (staticComp && staticComp->Data()) - return true; - - auto linkIt = this->entityLinkMap.find(_entity); - if (linkIt != this->entityLinkMap.end()) { - // get top level model of this link - auto topLevelModelEnt = topLevelModel(_parent->Data(), _ecm); - - auto canonicalLink = - _ecm.Component(_entity); - - auto frameData = linkIt->second->FrameDataRelativeToWorld(); - const auto& worldPose = frameData.pose; - - if (canonicalLink) { - // This is the canonical link, update the top level model. - // The pose of this link w.r.t its top level model never - // changes because it's "fixed" to the model. Instead, we - // change the top level model's pose here. The physics - // engine gives us the pose of this link relative to world - // so to set the top level model's pose, we have to - // post-multiply it by the inverse of the transform of the - // link w.r.t to its top level model. - math::Pose3d linkPoseFromTopLevelModel; - linkPoseFromTopLevelModel = - this->RelativePose(topLevelModelEnt, _entity, _ecm); - - // update top level model's pose - auto mutableModelPose = - _ecm.Component(topLevelModelEnt); - *(mutableModelPose) = - components::Pose(math::eigen3::convert(worldPose) - * linkPoseFromTopLevelModel.Inverse()); - - _ecm.SetChanged(topLevelModelEnt, - components::Pose::typeId, - ComponentState::PeriodicChange); - } - else { - // Compute the relative pose of this link from the top level - // model first get the world pose of the top level model - auto worldComp = - _ecm.Component(topLevelModelEnt); - // if the worldComp is a nullptr, something is wrong with ECS - if (!worldComp) { - ignerr - << "The parent component of " << topLevelModelEnt - << " could not be found. This should never happen!\n"; - return true; - } - math::Pose3d parentWorldPose = this->RelativePose( - worldComp->Data(), _parent->Data(), _ecm); - - // Unlike canonical links, pose of regular links can move - // relative. to the parent. Same for links inside nested models. - *_pose = components::Pose(math::eigen3::convert(worldPose) - + parentWorldPose.Inverse()); - _ecm.SetChanged(_entity, - components::Pose::typeId, - ComponentState::PeriodicChange); - } - - // Populate world poses, velocities and accelerations of the - // link. For now these components are updated only if another - // system has created the corresponding component on the entity. - auto worldPoseComp = _ecm.Component(_entity); - if (worldPoseComp) { - auto state = - worldPoseComp->SetData( - math::eigen3::convert(frameData.pose), this->pose3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged(_entity, components::WorldPose::typeId, state); - } - - // Velocity in world coordinates - auto worldLinVelComp = - _ecm.Component(_entity); - if (worldLinVelComp) { - auto state = - worldLinVelComp->SetData( - math::eigen3::convert(frameData.linearVelocity), - this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged( - _entity, components::WorldLinearVelocity::typeId, state); - } - - // Angular velocity in world frame coordinates - auto worldAngVelComp = - _ecm.Component(_entity); - if (worldAngVelComp) { - auto state = - worldAngVelComp->SetData( - math::eigen3::convert(frameData.angularVelocity), - this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged( - _entity, components::WorldAngularVelocity::typeId, state); - } - - // Acceleration in world frame coordinates - auto worldLinAccelComp = - _ecm.Component(_entity); - if (worldLinAccelComp) { - auto state = - worldLinAccelComp->SetData( - math::eigen3::convert(frameData.linearAcceleration), - this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged(_entity, - components::WorldLinearAcceleration::typeId, - state); - } - - // Angular acceleration in world frame coordinates - auto worldAngAccelComp = - _ecm.Component(_entity); - - if (worldAngAccelComp) { - auto state = - worldAngAccelComp->SetData( - math::eigen3::convert(frameData.angularAcceleration), - this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged(_entity, - components::WorldAngularAcceleration::typeId, - state); - } - - const Eigen::Matrix3d R_bs = - worldPose.linear().transpose(); // NOLINT - - // Velocity in body-fixed frame coordinates - auto bodyLinVelComp = - _ecm.Component(_entity); - if (bodyLinVelComp) { - Eigen::Vector3d bodyLinVel = R_bs * frameData.linearVelocity; - auto state = - bodyLinVelComp->SetData(math::eigen3::convert(bodyLinVel), - this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged( - _entity, components::LinearVelocity::typeId, state); - } - - // Angular velocity in body-fixed frame coordinates - auto bodyAngVelComp = - _ecm.Component(_entity); - if (bodyAngVelComp) { - Eigen::Vector3d bodyAngVel = R_bs * frameData.angularVelocity; - auto state = - bodyAngVelComp->SetData(math::eigen3::convert(bodyAngVel), - this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged( - _entity, components::AngularVelocity::typeId, state); - } - - // Acceleration in body-fixed frame coordinates - auto bodyLinAccelComp = - _ecm.Component(_entity); - if (bodyLinAccelComp) { - Eigen::Vector3d bodyLinAccel = - R_bs * frameData.linearAcceleration; - auto state = - bodyLinAccelComp->SetData( - math::eigen3::convert(bodyLinAccel), this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged( - _entity, components::LinearAcceleration::typeId, state); - } - - // Angular acceleration in world frame coordinates - auto bodyAngAccelComp = - _ecm.Component(_entity); - if (bodyAngAccelComp) { - Eigen::Vector3d bodyAngAccel = - R_bs * frameData.angularAcceleration; - auto state = - bodyAngAccelComp->SetData( - math::eigen3::convert(bodyAngAccel), this->vec3Eql) - ? ComponentState::PeriodicChange - : ComponentState::NoChange; - _ecm.SetChanged( - _entity, components::AngularAcceleration::typeId, state); - } - } - return true; - }); - - // history of applied joint forces (commands) - _ecm.Each([&](const Entity& _entity, - components::Joint* /*_joint*/, - components::Name* /*_name*/, - components::JointForceCmd* - _forceCmd) -> bool { - // Get the data from the components - auto jointForceCmdData = _forceCmd->Data(); - - // If the history is enabled, append the force command also there - auto historyComponent = _ecm.Component< - ignition::gazebo::components::HistoryOfAppliedJointForces>(_entity); - - // Since the operation is an append, we have to perform it only when - // the physics step is actually performed - if (!_info.paused && historyComponent) { - auto& history = scenario::gazebo::utils::getExistingComponentData< - ignition::gazebo::components::HistoryOfAppliedJointForces>( - &_ecm, _entity); - - for (const auto& jointForceCmd : jointForceCmdData) { - history.push(jointForceCmd); - } - } - - return true; - }); - - // pose/velocity/acceleration of non-link entities such as sensors / - // collisions. These get updated only if another system has created a - // components::WorldPose component for the entity. - // Populated components: - // * WorldPose - // * WorldLinearVelocity - // * AngularVelocity - // * LinearAcceleration - - // world pose - _ecm.Each( - [&](const Entity&, - const components::Pose* _pose, - components::WorldPose* _worldPose, - const components::ParentEntity* _parent) -> bool { - // check if parent entity is a link, e.g. entity is sensor / - // collision - auto linkIt = this->entityLinkMap.find(_parent->Data()); - if (linkIt != this->entityLinkMap.end()) { - const auto entityFrameData = - this->LinkFrameDataAtOffset(linkIt->second, _pose->Data()); - - *_worldPose = components::WorldPose( - math::eigen3::convert(entityFrameData.pose)); - } - - return true; - }); - - // world linear velocity - _ecm.Each( - [&](const Entity&, - const components::Pose* _pose, - components::WorldLinearVelocity* _worldLinearVel, - const components::ParentEntity* _parent) -> bool { - // check if parent entity is a link, e.g. entity is sensor / - // collision - auto linkIt = this->entityLinkMap.find(_parent->Data()); - if (linkIt != this->entityLinkMap.end()) { - const auto entityFrameData = - this->LinkFrameDataAtOffset(linkIt->second, _pose->Data()); - - // set entity world linear velocity - *_worldLinearVel = components::WorldLinearVelocity( - math::eigen3::convert(entityFrameData.linearVelocity)); - } - - return true; - }); - - // body angular velocity - _ecm.Each( - [&](const Entity&, - const components::Pose* _pose, - components::AngularVelocity* _angularVel, - const components::ParentEntity* _parent) -> bool { - // check if parent entity is a link, e.g. entity is sensor / - // collision - auto linkIt = this->entityLinkMap.find(_parent->Data()); - if (linkIt != this->entityLinkMap.end()) { - const auto entityFrameData = - this->LinkFrameDataAtOffset(linkIt->second, _pose->Data()); - - auto entityWorldPose = - math::eigen3::convert(entityFrameData.pose); - ignition::math::Vector3d entityWorldAngularVel = - math::eigen3::convert(entityFrameData.angularVelocity); - - auto entityBodyAngularVel = - entityWorldPose.Rot().RotateVectorReverse( - entityWorldAngularVel); - *_angularVel = - components::AngularVelocity(entityBodyAngularVel); - } - - return true; - }); - - // body linear acceleration - _ecm.Each( - [&](const Entity&, - const components::Pose* _pose, - components::LinearAcceleration* _linearAcc, - const components::ParentEntity* _parent) -> bool { - auto linkIt = this->entityLinkMap.find(_parent->Data()); - if (linkIt != this->entityLinkMap.end()) { - const auto entityFrameData = - this->LinkFrameDataAtOffset(linkIt->second, _pose->Data()); - - auto entityWorldPose = - math::eigen3::convert(entityFrameData.pose); - ignition::math::Vector3d entityWorldLinearAcc = - math::eigen3::convert(entityFrameData.linearAcceleration); - - auto entityBodyLinearAcc = - entityWorldPose.Rot().RotateVectorReverse( - entityWorldLinearAcc); - *_linearAcc = - components::LinearAcceleration(entityBodyLinearAcc); - } - - return true; - }); - - // Clear reset components - std::vector entitiesPositionReset; - _ecm.Each( - [&](const Entity& _entity, components::JointPositionReset*) -> bool { - entitiesPositionReset.push_back(_entity); - return true; - }); - - for (const auto entity : entitiesPositionReset) { - _ecm.RemoveComponent(entity); - } - - std::vector entitiesVelocityReset; - _ecm.Each( - [&](const Entity& _entity, components::JointVelocityReset*) -> bool { - entitiesVelocityReset.push_back(_entity); - return true; - }); - - for (const auto entity : entitiesVelocityReset) { - _ecm.RemoveComponent(entity); - } - - // Clear pending commands - _ecm.Each( - [&](const Entity&, components::JointForceCmd* _force) -> bool { - std::fill(_force->Data().begin(), _force->Data().end(), 0.0); - return true; - }); - - _ecm.Each( - [&](const Entity&, - components::ExternalWorldWrenchCmd* _wrench) -> bool { - _wrench->Data().Clear(); - return true; - }); - - _ecm.Each( - [&](const Entity&, components::JointVelocityCmd* _vel) -> bool { - std::fill(_vel->Data().begin(), _vel->Data().end(), 0.0); - return true; - }); - - _ecm.Each( - [&](const Entity&, components::SlipComplianceCmd* _slip) -> bool { - std::fill(_slip->Data().begin(), _slip->Data().end(), 0.0); - return true; - }); - - // Update joint positions - _ecm.Each( - [&](const Entity& _entity, - components::Joint*, - components::JointPosition* _jointPos) -> bool { - auto jointIt = this->entityJointMap.find(_entity); - if (jointIt != this->entityJointMap.end()) { - _jointPos->Data().resize( - jointIt->second->GetDegreesOfFreedom()); - for (std::size_t i = 0; - i < jointIt->second->GetDegreesOfFreedom(); - ++i) { - _jointPos->Data()[i] = jointIt->second->GetPosition(i); - } - } - return true; - }); - - // Update joint Velocities - _ecm.Each( - [&](const Entity& _entity, - components::Joint*, - components::JointVelocity* _jointVel) -> bool { - auto jointIt = this->entityJointMap.find(_entity); - if (jointIt != this->entityJointMap.end()) { - _jointVel->Data().resize( - jointIt->second->GetDegreesOfFreedom()); - for (std::size_t i = 0; - i < jointIt->second->GetDegreesOfFreedom(); - ++i) { - _jointVel->Data()[i] = jointIt->second->GetVelocity(i); - } - } - return true; - }); - - // Update joint acceleration - _ecm.Each( - [&](const Entity& _entity, - components::Joint*, - components::JointAcceleration* _jointAcc) -> bool { - auto jointIt = this->entityJointMap.find(_entity); - if (jointIt != this->entityJointMap.end()) { - _jointAcc->Data().resize( - jointIt->second->GetDegreesOfFreedom()); - for (std::size_t i = 0; - i < jointIt->second->GetDegreesOfFreedom(); - ++i) { - _jointAcc->Data()[i] = jointIt->second->GetAcceleration(i); - } - } - return true; - }); - - // Update joint forces - _ecm.Each( - [&](const Entity& _entity, - components::Joint*, - components::JointForce* _jointForce) -> bool { - auto jointIt = this->entityJointMap.find(_entity); - if (jointIt != this->entityJointMap.end()) { - _jointForce->Data().resize( - jointIt->second->GetDegreesOfFreedom()); - for (std::size_t i = 0; - i < jointIt->second->GetDegreesOfFreedom(); - ++i) { - _jointForce->Data()[i] = jointIt->second->GetForce(i); - } - } - return true; - }); - - // TODO(louise) Skip this if there are no collision features - this->UpdateCollisions(_ecm); -} - -void Physics::Impl::UpdateCollisions(EntityComponentManager& _ecm) -{ - // Quit early if the ContactData component hasn't been created. This - // means there are no systems that need contact information - if (!_ecm.HasComponentType(components::ContactSensorData::typeId)) - return; - - // TODO(addisu) If systems are assumed to only have one world, we should - // capture the world Entity in a Configure call - Entity worldEntity = _ecm.EntityByComponents(components::World()); - - if (worldEntity == kNullEntity) { - ignerr << "Missing world entity.\n"; - return; - } - - auto worldIt = this->entityWorldMap.find(worldEntity); - if (worldIt == this->entityWorldMap.end()) { - ignwarn << "Failed to find world [" << worldEntity << "]." << std::endl; - return; - } - - auto worldCollisionFeature = - entityCast(worldEntity, worldIt->second, this->entityWorldCollisionMap); - if (!worldCollisionFeature) { - static bool informed{false}; - if (!informed) { - igndbg << "Attempting process contacts, but the physics " - << "engine doesn't support collision features. " - << "Contacts won't be computed." << std::endl; - informed = true; - } - return; - } - - // Struct containing pointer of contact data - struct AllContactData - { - const WorldShapeType::ContactPoint* point; - const WorldShapeType::ExtraContactData* extra; - }; - - // Each contact object we get from ign-physics contains the EntityPtrs - // of the two colliding entities and other data about the contact such - // as the position. This map groups contacts so that it is easy to query - // all the contacts of one entity. - using EntityContactMap = - std::unordered_map>; - - // This data structure is essentially a mapping between a pair of - // entities and a list of pointers to their contact object. We use a map - // inside a map to create msgs::Contact objects conveniently later on. - std::unordered_map entityContactMap; - - // Note that we are temporarily storing pointers to elements in this - // ("allContacts") container. Thus, we must make sure it doesn't get - // destroyed until the end of this function. - auto allContacts = worldCollisionFeature->GetContactsFromLastStep(); - for (const auto& contactComposite : allContacts) { - // Get the RequireData - const auto& contact = - contactComposite.Get(); - auto coll1It = this->collisionEntityMap.find(contact.collision1); - auto coll2It = this->collisionEntityMap.find(contact.collision2); - - // Check the ExpectData - const auto* extraContactData = - contactComposite.Query(); - - if ((coll1It != this->collisionEntityMap.end()) - && (coll2It != this->collisionEntityMap.end())) { - - AllContactData allContactData; - allContactData.point = &contact; - - if (extraContactData) { - allContactData.extra = extraContactData; - } - - // Note that the ExtraContactData is valid only when the first - // collision is the first body. Quantities like the force and - // the normal must be flipped in the second case. - entityContactMap[coll1It->second][coll2It->second].push_back( - allContactData); - entityContactMap[coll2It->second][coll1It->second].push_back( - allContactData); - } - } - - // Go through each collision entity that has a ContactData component and - // set the component value to the list of contacts that correspond to - // the collision entity - _ecm.Each( - [&](const Entity& _collEntity1, - components::Collision*, - components::ContactSensorData* _contacts) -> bool { - if (entityContactMap.find(_collEntity1) == entityContactMap.end()) { - // Clear the last contact data - *_contacts = components::ContactSensorData(); - return true; - } - - const auto& contactMap = entityContactMap[_collEntity1]; - - msgs::Contacts contactsComp; - - for (const auto& [collEntity2, contactData] : contactMap) { - - msgs::Contact* contactMsg = contactsComp.add_contact(); - contactMsg->mutable_collision1()->set_id(_collEntity1); - contactMsg->mutable_collision2()->set_id(collEntity2); - - for (const auto& contact : contactData) { - auto* position = contactMsg->add_position(); - position->set_x(contact.point->point.x()); - position->set_y(contact.point->point.y()); - position->set_z(contact.point->point.z()); - - if (contact.extra) { - // Add the penetration depth - contactMsg->add_depth(contact.extra->depth); - - // Get the name of the collisions - auto collisionName1 = - _ecm.Component(_collEntity1) - ->Data(); - auto collisionName2 = - _ecm.Component(collEntity2) - ->Data(); - - // Add the wrench (only the force component) - auto* wrench = contactMsg->add_wrench(); - wrench->set_body_1_name(collisionName1); - wrench->set_body_2_name(collisionName2); - auto* wrench1 = wrench->mutable_body_1_wrench(); - auto* wrench2 = wrench->mutable_body_2_wrench(); - - auto* force1 = wrench1->mutable_force(); - auto* force2 = wrench2->mutable_force(); - auto* torque1 = wrench1->mutable_torque(); - auto* torque2 = wrench2->mutable_torque(); - - // The same ContactPoint and ExtraContactData are - // used for the contact between collision1 and - // collision2. In those structures there is some - // data, like the force and normal, that cannot - // commute. - if (_collEntity1 - == this->collisionEntityMap.at( - contact.point->collision1)) { - assert(collEntity2 - == this->collisionEntityMap.at( - contact.point->collision2)); - // Use the data as it is - *force1 = msgs::Convert( - math::eigen3::convert(contact.extra->force)); - *force2 = msgs::Convert( - -math::eigen3::convert(contact.extra->force)); - // Add the wrench normal - auto* normal = contactMsg->add_normal(); - normal->set_x(contact.extra->normal.x()); - normal->set_y(contact.extra->normal.y()); - normal->set_z(contact.extra->normal.z()); - } - else { - assert(collEntity2 - == this->collisionEntityMap.at( - contact.point->collision1)); - // Flip the force - *force1 = msgs::Convert( - -math::eigen3::convert(contact.extra->force)); - *force2 = msgs::Convert( - math::eigen3::convert(contact.extra->force)); - // Flip the normal - auto* normal = contactMsg->add_normal(); - normal->set_x(-contact.extra->normal.x()); - normal->set_y(-contact.extra->normal.y()); - normal->set_z(-contact.extra->normal.z()); - } - - *torque1 = msgs::Convert(math::Vector3d::Zero); - *torque2 = msgs::Convert(math::Vector3d::Zero); - } - } - } - *_contacts = components::ContactSensorData(contactsComp); - - return true; - }); -} - -physics::FrameData3d -Physics::Impl::LinkFrameDataAtOffset(const LinkPtrType& _link, - const math::Pose3d& _pose) const -{ - physics::FrameData3d parent; - parent.pose = math::eigen3::convert(_pose); - physics::RelativeFrameData3d relFrameData(_link->GetFrameID(), parent); - return this->engine->Resolve(relFrameData, physics::FrameID::World()); -} - -IGNITION_ADD_PLUGIN(Physics, - ignition::gazebo::System, - Physics::ISystemConfigure, - Physics::ISystemUpdate) -IGNITION_ADD_PLUGIN_ALIAS(Physics, "ignition::gazebo::systems::Physics") diff --git a/cpp/scenario/plugins/Physics/Physics.h b/cpp/scenario/plugins/Physics/Physics.h deleted file mode 100644 index 39d8612aa..000000000 --- a/cpp/scenario/plugins/Physics/Physics.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2020 Open Source Robotics Foundation - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SCENARIO_PLUGINS_GAZEBO_PHYSICS -#define SCENARIO_PLUGINS_GAZEBO_PHYSICS - -#include -#include -#include - -#include -#include -#include -#include - -// Features need to be defined ahead of entityCast -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace scenario::plugins::gazebo { - class Physics; - template - class ToEntity, - template - class MinimumEntity> - ignition::physics::EntityPtr> entityCast( - ignition::gazebo::Entity _entity, - const ignition::physics::EntityPtr< - MinimumEntity>& _minimumEntity, - std::unordered_map< - ignition::gazebo::Entity, - ignition::physics::EntityPtr>>& - _castMap); -} // namespace scenario::plugins::gazebo - -class scenario::plugins::gazebo::Physics final - : public ignition::gazebo::System - , public ignition::gazebo::ISystemConfigure - , public ignition::gazebo::ISystemUpdate -{ -public: - explicit Physics(); - ~Physics() override; - - // Documentation inherited - void Configure(const ignition::gazebo::Entity& entity, - const std::shared_ptr& sdf, - ignition::gazebo::EntityComponentManager& ecm, - ignition::gazebo::EventManager& eventMgr) final; - - void Update(const ignition::gazebo::UpdateInfo& info, - ignition::gazebo::EntityComponentManager& ecm) override; - -private: - class Impl; - std::unique_ptr pImpl; -}; - -/// \brief Helper function to cast from an entity type with minimum features -/// to an entity with a different set of features. When the entity is cast -/// successfully, it is added to _castMap so that subsequent casts will -/// use the entity from the map. -/// \tparam PolicyT The feature policy, such as -/// `ignition::physics::FeaturePolicy3d`. -/// \tparam ToFeatureList The list of features of the resulting entity. -/// \tparam MinimumFeatureList The minimum list of features. -/// \tparam ToEntity Type of entities with ToFeatureList -/// \tparam MinimumEntity Type of entities with MinimumFeatureList -/// \param[in] _entity Entity ID. -/// \param[in] _minimumEntity Entity pointer with minimum features. -/// \param[in] _castMap Map to store entities that have already been cast. -template - class ToEntity, - template - class MinimumEntity> -ignition::physics::EntityPtr> -scenario::plugins::gazebo::entityCast( - ignition::gazebo::Entity _entity, - const ignition::physics::EntityPtr< - MinimumEntity>& _minimumEntity, - std::unordered_map< - ignition::gazebo::Entity, - ignition::physics::EntityPtr>>& - _castMap) -{ - // Has already been cast - auto castIt = _castMap.find(_entity); - if (castIt != _castMap.end()) { - return castIt->second; - } - - ignition::physics::EntityPtr> castEntity; - - // Cast - castEntity = - ignition::physics::RequestFeatures::From(_minimumEntity); - - if (castEntity) { - _castMap.insert(std::make_pair(_entity, castEntity)); - } - - return castEntity; -} - -#endif // SCENARIO_PLUGINS_GAZEBO_PHYSICS diff --git a/docs/sphinx/CMakeLists.txt b/docs/sphinx/CMakeLists.txt index 5c6dffe29..5bdcf3a54 100644 --- a/docs/sphinx/CMakeLists.txt +++ b/docs/sphinx/CMakeLists.txt @@ -8,7 +8,7 @@ # 2. The folder containing the bindings Python modules # set(PYTHON_PACKAGES_DIR "${CMAKE_SOURCE_DIR}/python") -set(BINDINGS_MODULES_DIR "${PROJECT_BINARY_DIR}/bindings") +set(BINDINGS_MODULES_DIR "${PROJECT_BINARY_DIR}/scenario/bindings") # ============= # APIDOC TARGET @@ -25,6 +25,14 @@ set(BINDINGS_MODULES_DIR "${PROJECT_BINARY_DIR}/bindings") # - docs/sphinx/gym_ignition # - docs/sphinx/gym_ignition_environments +if (NOT TARGET ScenarioSwig::Core) + message(FATAL_ERROR "Target ScenarioSwig::Core not found") +endif() + +if (NOT TARGET ScenarioSwig::Gazebo) + message(FATAL_ERROR "Target ScenarioSwig::Gazebo not found") +endif() + find_package(SphinxApidoc REQUIRED) add_custom_target(apidoc ALL DEPENDS doxygen ScenarioSwig::Core ScenarioSwig::Gazebo) diff --git a/docs/sphinx/_templates/versioning.html b/docs/sphinx/_templates/versioning.html new file mode 100644 index 000000000..06bebcb05 --- /dev/null +++ b/docs/sphinx/_templates/versioning.html @@ -0,0 +1,14 @@ +{% if versions %} + +{% endif %} diff --git a/docs/sphinx/breathe/core.rst b/docs/sphinx/breathe/core.rst index 8d5d29964..ea1a4938b 100644 --- a/docs/sphinx/breathe/core.rst +++ b/docs/sphinx/breathe/core.rst @@ -3,19 +3,6 @@ Core ==== -.. doxygenclass:: scenario::core::World +.. doxygennamespace:: scenario::core + :project: scenario :members: - -.. doxygenclass:: scenario::core::Model - :members: - -.. doxygenclass:: scenario::core::Link - :members: - -.. doxygenclass:: scenario::core::Joint - :members: - -.. doxygenenum:: scenario::core::JointControlMode - -.. doxygenfile:: core/utils/utils.h - :project: scenario \ No newline at end of file diff --git a/docs/sphinx/breathe/gazebo.rst b/docs/sphinx/breathe/gazebo.rst index 2739dfffb..7cf5431d2 100644 --- a/docs/sphinx/breathe/gazebo.rst +++ b/docs/sphinx/breathe/gazebo.rst @@ -3,40 +3,6 @@ Gazebo ====== -.. doxygenclass:: scenario::gazebo::GazeboSimulator - :members: - -.. doxygenclass:: scenario::gazebo::World - :members: - -.. doxygenclass:: scenario::gazebo::Model - :members: - -.. doxygenclass:: scenario::gazebo::Link - :members: - -.. doxygenclass:: scenario::gazebo::Joint - :members: - -.. doxygenenum:: scenario::gazebo::PhysicsEngine - -.. doxygenfile:: gazebo/utils.h +.. doxygennamespace:: scenario::gazebo :project: scenario - -.. doxygenclass:: scenario::controllers::Controller - :members: - -.. doxygenclass:: scenario::controllers::UseScenarioModel - :members: - -.. doxygenclass:: scenario::controllers::UseScenarioModel - :members: - -.. doxygenclass:: scenario::controllers::SetJointReferences - :members: - -.. doxygenclass:: scenario::controllers::SetBaseReferences - :members: - -.. doxygenclass:: scenario::controllers::ComputedTorqueFixedBase :members: diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index 94fa03052..f3d87055b 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -16,9 +16,10 @@ # -- Project information ----------------------------------------------------- from datetime import datetime -project = 'gym-ignition' -copyright = f'{datetime.now().year}, Istituto Italiano di Tecnologia' -author = 'Diego Ferigo' + +project = "gym-ignition" +copyright = f"{datetime.now().year}, Istituto Italiano di Tecnologia" +author = "Diego Ferigo" # -- General configuration --------------------------------------------------- @@ -26,33 +27,33 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.mathjax', - 'sphinx.ext.githubpages', - 'sphinx.ext.napoleon', + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.mathjax", + "sphinx.ext.githubpages", + "sphinx.ext.napoleon", "sphinx.ext.extlinks", - 'sphinx_autodoc_typehints', + "sphinx_autodoc_typehints", "sphinx_multiversion", "sphinx_fontawesome", - 'breathe', - 'sphinx_tabs.tabs', + "breathe", + "sphinx_tabs.tabs", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- @@ -60,7 +61,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_book_theme' +html_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -72,15 +73,18 @@ "use_edit_page_button": True, "path_to_docs": "docs/sphinx", "home_page_in_toc": True, + "use_download_button": False, + "use_fullscreen_button": True, + "single_page": False, } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'pastie' +pygments_style = "pastie" # -- Extension configuration ------------------------------------------------- @@ -98,23 +102,23 @@ # From: https://holzhaus.github.io/sphinx-multiversion smv_prefer_remote_refs = False -smv_remote_whitelist = r'^(origin|upstream)$' -smv_tag_whitelist = None -smv_branch_whitelist = r'^(master|devel|docs/.*)$' -smv_released_pattern = r'^tags/.*$' -smv_outputdir_format = '{ref.name}' +smv_remote_whitelist = r"^(origin|upstream)$" +smv_tag_whitelist = r"^dummy" +smv_branch_whitelist = r"^(master|devel|docs/.*)$" +smv_released_pattern = r"^tags/.*$" +smv_outputdir_format = "{ref.name}" html_sidebars = { "**": [ - "about.html", - "navigation.html", - "versions.html", - "searchbox.html", - ], + "sbt-sidebar-nav.html", + "versioning.html", + "search-field.html", + "sbt-sidebar-footer.html", + ] } # -- Options for extlinks extension ---------------------------------- extlinks = { - 'issue': ('https://github.com/robotology/gym-ignition/issues/%s', '#'), - 'pr': ('https://github.com/robotology/gym-ignition/pull/%s', '#'), + "issue": ("https://github.com/robotology/gym-ignition/issues/%s", "#"), + "pr": ("https://github.com/robotology/gym-ignition/pull/%s", "#"), } diff --git a/docs/sphinx/getting_started/gym-ignition.rst b/docs/sphinx/getting_started/gym-ignition.rst index c98b714ce..16be71795 100644 --- a/docs/sphinx/getting_started/gym-ignition.rst +++ b/docs/sphinx/getting_started/gym-ignition.rst @@ -27,6 +27,12 @@ Beyond the abstractions provided by ScenarIO, gym-ignition introduces the follow Refer to :py:class:`~gym_ignition.rbd.idyntree.inverse_kinematics_nlp.InverseKinematicsNLP` and :py:class:`~gym_ignition.rbd.idyntree.kindyncomputations.KinDynComputations` for more details. +.. tip:: + + If you want to learn more about ``iDynTree``, the two classes we mainly use are ``iDynTree::KinDynComputations`` (`docs `__) and ``iDynTree::InverseKinematics`` (`docs `__). + + The theory and notation is summarized in `Multibody dynamics notation `_. + You can find demo environments created with ``gym-ignition`` in the `gym_ignition_environments `_ folder. These examples show how to structure a new standalone Python package containing the environment with your robots. @@ -35,13 +41,13 @@ For example, taking the cartpole balancing problem with discrete actions, the components you need to implement are the following: - A model :py:class:`~gym_ignition_environments.models.cartpole.CartPole` - (`cartpole.py `_) + (`model/cartpole.py `_) - A task :py:class:`~gym_ignition_environments.tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing` (`cartpole_discrete_balancing.py `_) - A randomizer :py:class:`~gym_ignition_environments.randomizers.cartpole.CartpoleEnvRandomizer` - (`cartpole.py `_) + (`randomizers/cartpole.py `_) - Environment registration as done in `__init__.py `_ diff --git a/docs/sphinx/getting_started/manipulation.rst b/docs/sphinx/getting_started/manipulation.rst index 11eafd3e5..d4c0623a7 100644 --- a/docs/sphinx/getting_started/manipulation.rst +++ b/docs/sphinx/getting_started/manipulation.rst @@ -33,19 +33,21 @@ It shows the following functionalities: .. code-block:: python - import time import enum - import numpy as np - import gym_ignition - from typing import List + import time from functools import partial + from typing import List + + import gym_ignition import gym_ignition_environments + import numpy as np + from gym_ignition.context.gazebo import controllers from gym_ignition.rbd import conversions + from gym_ignition.rbd.idyntree import inverse_kinematics_nlp + from scipy.spatial.transform import Rotation as R + from scenario import core as scenario_core from scenario import gazebo as scenario_gazebo - from scipy.spatial.transform import Rotation as R - from gym_ignition.context.gazebo import controllers - from gym_ignition.rbd.idyntree import inverse_kinematics_nlp # Configure verbosity scenario_gazebo.set_verbosity(scenario_gazebo.Verbosity_error) @@ -54,26 +56,31 @@ It shows the following functionalities: np.set_printoptions(precision=4, suppress=True) - def add_panda_controller(panda: gym_ignition_environments.models.panda.Panda, - controller_period: float) -> None: + def add_panda_controller( + panda: gym_ignition_environments.models.panda.Panda, controller_period: float + ) -> None: # Set the controller period assert panda.set_controller_period(period=controller_period) # Increase the max effort of the fingers - panda.get_joint(joint_name="panda_finger_joint1").to_gazebo(). \ - set_max_generalized_force(max_force=500.0) - panda.get_joint(joint_name="panda_finger_joint2").to_gazebo(). \ - set_max_generalized_force(max_force=500.0) + panda.get_joint( + joint_name="panda_finger_joint1" + ).to_gazebo().set_max_generalized_force(max_force=500.0) + panda.get_joint( + joint_name="panda_finger_joint2" + ).to_gazebo().set_max_generalized_force(max_force=500.0) # Insert the ComputedTorqueFixedBase controller - assert panda.to_gazebo().insert_model_plugin(*controllers.ComputedTorqueFixedBase( - kp=[100.0] * (panda.dofs() - 2) + [10000.0] * 2, - ki=[0.0] * panda.dofs(), - kd=[17.5] * (panda.dofs() - 2) + [100.0] * 2, - urdf=panda.get_model_file(), - joints=panda.joint_names(), - ).args()) + assert panda.to_gazebo().insert_model_plugin( + *controllers.ComputedTorqueFixedBase( + kp=[100.0] * (panda.dofs() - 2) + [10000.0] * 2, + ki=[0.0] * panda.dofs(), + kd=[17.5] * (panda.dofs() - 2) + [100.0] * 2, + urdf=panda.get_model_file(), + joints=panda.joint_names(), + ).args() + ) # Initialize the controller to the current state assert panda.set_joint_position_targets(panda.joint_positions()) @@ -81,34 +88,40 @@ It shows the following functionalities: assert panda.set_joint_acceleration_targets(panda.joint_accelerations()) - def get_panda_ik(panda: gym_ignition_environments.models.panda.Panda, - optimized_joints: List[str]) -> \ - inverse_kinematics_nlp.InverseKinematicsNLP: + def get_panda_ik( + panda: gym_ignition_environments.models.panda.Panda, optimized_joints: List[str] + ) -> inverse_kinematics_nlp.InverseKinematicsNLP: # Create IK ik = inverse_kinematics_nlp.InverseKinematicsNLP( urdf_filename=panda.get_model_file(), considered_joints=optimized_joints, - joint_serialization=panda.joint_names()) + joint_serialization=panda.joint_names(), + ) # Initialize IK - ik.initialize(verbosity=1, - floating_base=False, - cost_tolerance=1E-8, - constraints_tolerance=1E-8, - base_frame=panda.base_frame()) + ik.initialize( + verbosity=1, + floating_base=False, + cost_tolerance=1e-8, + constraints_tolerance=1e-8, + base_frame=panda.base_frame(), + ) # Set the current configuration ik.set_current_robot_configuration( base_position=np.array(panda.base_position()), base_quaternion=np.array(panda.base_orientation()), - joint_configuration=np.array(panda.joint_positions())) + joint_configuration=np.array(panda.joint_positions()), + ) # Add the cartesian target of the end effector end_effector = "end_effector_frame" - ik.add_target(frame_name=end_effector, - target_type=inverse_kinematics_nlp.TargetType.POSE, - as_constraint=False) + ik.add_target( + frame_name=end_effector, + target_type=inverse_kinematics_nlp.TargetType.POSE, + as_constraint=False, + ) return ik @@ -120,17 +133,20 @@ It shows the following functionalities: # Download the cube SDF file bucket_sdf = scenario_gazebo.get_model_file_from_fuel( - uri=uri(org="GoogleResearch", - name="Threshold_Basket_Natural_Finish_Fabric_Liner_Small"), - use_cache=False) + uri=uri( + org="GoogleResearch", + name="Threshold_Basket_Natural_Finish_Fabric_Liner_Small", + ), + use_cache=False, + ) # Assign a custom name to the model model_name = "bucket" # Insert the model - assert world.insert_model(bucket_sdf, - scenario_core.Pose([0.68, 0, 1.02], [1., 0, 0, 1]), - model_name) + assert world.insert_model( + bucket_sdf, scenario_core.Pose([0.68, 0, 1.02], [1.0, 0, 0, 1]), model_name + ) # Return the model return world.get_model(model_name=model_name) @@ -143,56 +159,61 @@ It shows the following functionalities: # Download the cube SDF file bucket_sdf = scenario_gazebo.get_model_file_from_fuel( - uri=uri(org="OpenRobotics", - name="Table"), - use_cache=False) + uri=uri(org="OpenRobotics", name="Table"), use_cache=False + ) # Assign a custom name to the model model_name = "table" # Insert the model - assert world.insert_model(bucket_sdf, - scenario_core.Pose_identity(), - model_name) + assert world.insert_model(bucket_sdf, scenario_core.Pose_identity(), model_name) # Return the model return world.get_model(model_name=model_name) - def insert_cube_in_operating_area(world: scenario_gazebo.World) -> scenario_gazebo.Model: + def insert_cube_in_operating_area( + world: scenario_gazebo.World, + ) -> scenario_gazebo.Model: # Insert objects from Fuel uri = lambda org, name: f"https://fuel.ignitionrobotics.org/{org}/models/{name}" # Download the cube SDF file cube_sdf = scenario_gazebo.get_model_file_from_fuel( - uri=uri(org="openrobotics", name="wood cube 5cm"), use_cache=False) + uri=uri(org="openrobotics", name="wood cube 5cm"), use_cache=False + ) # Sample a random position random_position = np.random.uniform(low=[0.2, -0.3, 1.01], high=[0.4, 0.3, 1.01]) # Get a unique name model_name = gym_ignition.utils.scenario.get_unique_model_name( - world=world, model_name="cube") + world=world, model_name="cube" + ) # Insert the model assert world.insert_model( - cube_sdf, scenario_core.Pose(random_position, [1., 0, 0, 0]), model_name) + cube_sdf, scenario_core.Pose(random_position, [1.0, 0, 0, 0]), model_name + ) # Return the model return world.get_model(model_name=model_name) - def solve_ik(target_position: np.ndarray, - target_orientation: np.ndarray, - ik: inverse_kinematics_nlp.InverseKinematicsNLP) -> np.ndarray: + def solve_ik( + target_position: np.ndarray, + target_orientation: np.ndarray, + ik: inverse_kinematics_nlp.InverseKinematicsNLP, + ) -> np.ndarray: quat_xyzw = R.from_euler(seq="y", angles=90, degrees=True).as_quat() ik.update_transform_target( target_name=ik.get_active_target_names()[0], position=target_position, - quaternion=conversions.Quaternion.to_wxyz(xyzw=quat_xyzw)) + quaternion=conversions.Quaternion.to_wxyz(xyzw=quat_xyzw), + ) # Run the IK ik.solve() @@ -200,17 +221,21 @@ It shows the following functionalities: return ik.get_reduced_solution().joint_configuration - def end_effector_reached(position: np.array, - end_effector_link: scenario_core.Link, - max_error_pos: float = 0.01, - max_error_vel: float = 0.5, - mask: np.ndarray = np.array([1., 1., 1.])) -> bool: + def end_effector_reached( + position: np.array, + end_effector_link: scenario_core.Link, + max_error_pos: float = 0.01, + max_error_vel: float = 0.5, + mask: np.ndarray = np.array([1.0, 1.0, 1.0]), + ) -> bool: masked_target = mask * position masked_current = mask * np.array(end_effector_link.position()) - return np.linalg.norm(masked_current - masked_target) < max_error_pos and \ - np.linalg.norm(end_effector_link.world_linear_velocity()) < max_error_vel + return ( + np.linalg.norm(masked_current - masked_target) < max_error_pos + and np.linalg.norm(end_effector_link.world_linear_velocity()) < max_error_vel + ) def get_unload_position(bucket: scenario_core.Model) -> np.ndarray: @@ -224,8 +249,9 @@ It shows the following functionalities: CLOSE = enum.auto() - def move_fingers(panda: gym_ignition_environments.models.panda.Panda, - action: FingersAction) -> None: + def move_fingers( + panda: gym_ignition_environments.models.panda.Panda, action: FingersAction + ) -> None: # Get the joints of the fingers finger1 = panda.get_joint(joint_name="panda_finger_joint1") @@ -246,7 +272,8 @@ It shows the following functionalities: # Get the simulator and the world gazebo, world = gym_ignition.utils.scenario.init_gazebo_sim( - step_size=0.001, real_time_factor=2.0, steps_per_run=1) + step_size=0.001, real_time_factor=2.0, steps_per_run=1 + ) # Open the GUI gazebo.gui() @@ -255,7 +282,11 @@ It shows the following functionalities: # Insert the Panda manipulator panda = gym_ignition_environments.models.panda.Panda( - world=world, position=[-0.1, 0, 1.0]) + world=world, position=[-0.1, 0, 1.0] + ) + + # Disable joint velocity limits + _ = [j.to_gazebo().set_velocity_limit(1_000) for j in panda.joints()] # Enable contacts only for the finger links panda.get_link("panda_leftfinger").to_gazebo().enable_contact_detection(True) @@ -277,7 +308,9 @@ It shows the following functionalities: gazebo.run(paused=True) # Create and configure IK for the panda - ik_joints = [j.name() for j in panda.joints() if j.type is not scenario_core.JointType_fixed ] + ik_joints = [ + j.name() for j in panda.joints() if j.type is not scenario_core.JointType_fixed + ] ik = get_panda_ik(panda=panda, optimized_joints=ik_joints) # Get some manipulator links @@ -304,7 +337,8 @@ It shows the following functionalities: over_joint_configuration = solve_ik( target_position=position_over_cube, target_orientation=np.array(cube.base_orientation()), - ik=ik) + ik=ik, + ) # Set the joint references assert panda.set_joint_position_targets(over_joint_configuration, ik_joints) @@ -313,10 +347,12 @@ It shows the following functionalities: panda.open_fingers() # Run the simulation until the EE reached the desired position - while not end_effector_reached(position=position_over_cube, - end_effector_link=end_effector_frame, - max_error_pos=0.05, - max_error_vel=0.5): + while not end_effector_reached( + position=position_over_cube, + end_effector_link=end_effector_frame, + max_error_pos=0.05, + max_error_vel=0.5, + ): gazebo.run() # Wait a bit more @@ -332,7 +368,8 @@ It shows the following functionalities: over_joint_configuration = solve_ik( target_position=np.array(cube.base_position()) + np.array([0, 0, 0.04]), target_orientation=np.array(cube.base_orientation()), - ik=ik) + ik=ik, + ) # Set the joint references assert panda.set_joint_position_targets(over_joint_configuration, ik_joints) @@ -340,8 +377,9 @@ It shows the following functionalities: # Run the simulation until the EE reached the desired position while not end_effector_reached( - position=np.array(cube.base_position()) + np.array([0, 0, 0.04]), - end_effector_link=end_effector_frame): + position=np.array(cube.base_position()) + np.array([0, 0, 0.04]), + end_effector_link=end_effector_frame, + ): gazebo.run() @@ -358,8 +396,10 @@ It shows the following functionalities: panda.close_fingers() # Detect a graps reading the contact wrenches of the finger links - while not (np.linalg.norm(finger_left.contact_wrench()) >= 50.0 and - np.linalg.norm(finger_right.contact_wrench()) >= 50.0): + while not ( + np.linalg.norm(finger_left.contact_wrench()) >= 50.0 + and np.linalg.norm(finger_right.contact_wrench()) >= 50.0 + ): gazebo.run() # ============= @@ -375,16 +415,19 @@ It shows the following functionalities: over_joint_configuration = solve_ik( target_position=position_over_cube, target_orientation=np.array(cube.base_orientation()), - ik=ik) + ik=ik, + ) # Set the joint references assert panda.set_joint_position_targets(over_joint_configuration, ik_joints) # Run the simulation until the EE reached the desired position - while not end_effector_reached(position=position_over_cube, - end_effector_link=end_effector_frame, - max_error_pos=0.1, - max_error_vel=0.5): + while not end_effector_reached( + position=position_over_cube, + end_effector_link=end_effector_frame, + max_error_pos=0.1, + max_error_vel=0.5, + ): gazebo.run() # Wait a bit more @@ -400,20 +443,21 @@ It shows the following functionalities: unload_joint_configuration = solve_ik( target_position=get_unload_position(bucket=bucket), target_orientation=np.array([0, 1.0, 0, 0]), - ik=ik) + ik=ik, + ) # Set the joint references - assert panda.set_joint_position_targets(unload_joint_configuration, - ik_joints) + assert panda.set_joint_position_targets(unload_joint_configuration, ik_joints) # Run the simulation until the EE reached the desired position while not end_effector_reached( - position=get_unload_position(bucket=bucket) + - np.random.uniform(low=-0.05, high=0.05, size=3), - end_effector_link=end_effector_frame, - max_error_pos=0.01, - max_error_vel=0.1, - mask=np.array([1, 1, 0])): + position=get_unload_position(bucket=bucket) + + np.random.uniform(low=-0.05, high=0.05, size=3), + end_effector_link=end_effector_frame, + max_error_pos=0.01, + max_error_vel=0.1, + mask=np.array([1, 1, 0]), + ): gazebo.run() diff --git a/docs/sphinx/getting_started/scenario.rst b/docs/sphinx/getting_started/scenario.rst index 3fc69d888..b4bb48c3f 100644 --- a/docs/sphinx/getting_started/scenario.rst +++ b/docs/sphinx/getting_started/scenario.rst @@ -1,7 +1,7 @@ .. _getting_started_scenario: -ScenarI/O -========= +ScenarIO +======== In this getting started section we show how to use the Gazebo ScenarIO library to simulate a pendulum system. We will use the models of the ground plane and the pendulum stored in the repository diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst index de12a98d0..3bb6bf52b 100644 --- a/docs/sphinx/index.rst +++ b/docs/sphinx/index.rst @@ -1,40 +1,27 @@ -.. _what_is_gym_ignition: +.. _scenario_and_gym_ignition: -What is gym-ignition? -===================== +ScenarIO and gym-ignition +========================= -**gym-ignition is a framework to create reproducible robotics environments for reinforcement learning research.** +This project targets both *control* and *robot learning* research domains: -The aims of the project are the following: - -- Provide unified APIs for interfacing with both simulated and real robots. -- Implement the simulation backend interfacing with the Ignition Gazebo simulator. -- Enable a seamless switch of all the physics engines supported by Ignition Gazebo. -- Guarantee the reproducibility and the scalability of the simulations by using the simulator as a library, without - relying on any network transport. -- Simplify the development of OpenAI Gym environments for robot learning research. - -**gym-ignition** targets both *control* and *robot learning* research domains: - -- Researchers in robotics and control can simulate their robots with familiar tools like Gazebo and URDF, +- Researchers in robotics and control can simulate their robots with familiar tools like Gazebo and URDF/SDF, without the need to rely on any middleware. - Researchers in robot learning can quickly develop new robotic environments that can scale to hundreds of parallel instances. -To know more about why we started developing gym-ignition, why we selected Ignition Gazebo for our simulations, -and what are our long-term goals, visit the :ref:`Motivations ` page. +We provide two related subprojects to each of these categories: -We are building an entire ecosystem around gym-ignition, if you're interested have a look to the other projects: +1. **ScenarIO** provides APIs to interface with the robots. +2. **gym-ignition** helps structuring environments compatible with OpenAI Gym, + while minimizing boilerplate code and providing common rigid-body dynamics utilities. -.. list-table:: - :header-rows: 1 - :align: center +Check the sections :ref:`What is ScenarIO ` and +:ref:`What is gym-ignition ` for more details, +and visit :ref:`Motivations ` for an extended overview. + +For a quick practical introduction, visit the :ref:`Getting Started ` page. - * - ScenarI/O and ``gym_ignition`` - - Robot Models - - Ignition Plugins - * - `robotology/gym-ignition `_ - - `robotology/gym-ignition-models `_ - - `dic-iit/gazebo-scenario-plugins `_ +If you use this project for your research, please check the FAQ about :ref:`how to give credit `. .. list-table:: @@ -49,14 +36,25 @@ We are building an entire ecosystem around gym-ignition, if you're interested ha .. toctree:: :hidden: :maxdepth: 1 - :caption: Motivations + :caption: What + + what/what_is_scenario + what/what_is_gym_ignition + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Why - motivations/why_gym_ignition + why/motivations + why/why_scenario + why/why_ignition_gazebo + why/why_gym_ignition .. toctree:: :hidden: :maxdepth: 1 - :caption: Installation + :caption: Installation (How) installation/support_policy installation/stable @@ -75,7 +73,7 @@ We are building an entire ecosystem around gym-ignition, if you're interested ha .. toctree:: :hidden: :maxdepth: 2 - :caption: ScenarI/O C++ API: + :caption: ScenarIO C++ API: breathe/core breathe/gazebo diff --git a/docs/sphinx/info/faq.rst b/docs/sphinx/info/faq.rst index ff0519d0f..202d62a68 100644 --- a/docs/sphinx/info/faq.rst +++ b/docs/sphinx/info/faq.rst @@ -1,6 +1,26 @@ FAQ === +.. _faq_citation: + +How to give credit? +------------------- + +If you use **ScenarIO** or **gym-ignition** for your research, +please cite the following reference: + +.. code-block:: bibtex + :caption: BibTeX entry + + @INPROCEEDINGS{ferigo2020gymignition, + title={Gym-Ignition: Reproducible Robotic Simulations for Reinforcement Learning}, + author={D. {Ferigo} and S. {Traversaro} and G. {Metta} and D. {Pucci}}, + booktitle={2020 IEEE/SICE International Symposium on System Integration (SII)}, + year={2020}, + pages={885-890}, + doi={10.1109/SII46433.2020.9025951} + } + Interaction with Tensorflow --------------------------- @@ -24,7 +44,7 @@ On GNU/Linux distributions that ship an old OpenGL version, the GUI could fail t error like *Unable to create the rendering window*. The reason is that Ignition Gazebo has `ogre-next `_ (also known as ogre2) as default rendering engine, and it requires OpenGL greater than 3.3. -You can find more details `here `_. +You can find more details `here `_. The workaround we recommend is modifying the file ``~/.ignition/gazebo/gui.config`` as follows: @@ -59,13 +79,13 @@ extracting it from the simulator with :cpp:func:`~scenario::gazebo::GazeboSimula - + true 0 0 10 0 0 0 - 1 1 1 1 - 0.5 0.5 0.5 1 + 0.8 0.8 0.8 1 + 0.2 0.2 0.2 1 1000 0.9 diff --git a/docs/sphinx/installation/developer.rst b/docs/sphinx/installation/developer.rst index bf7524863..a54709848 100644 --- a/docs/sphinx/installation/developer.rst +++ b/docs/sphinx/installation/developer.rst @@ -33,20 +33,23 @@ the CMake project as follows: .. code-block:: bash - mkdir build - cd build - cmake .. - cmake --build . - cmake --build . --target install + cd scenario/ + cmake -S . -B build + cmake --build build/ + cmake --build build/ --target install .. note:: + The default install prefix of the CMake project is ``/usr/local``. If you want to use a different folder, pass ``-DCMAKE_INSTALL_PREFIX=/new/install/prefix`` to the first ``cmake`` command. .. attention:: - The SWIG bindings are installed in the `site-packages `_ folder of the active Python interpreter. + + The SWIG bindings are installed in the `site-packages `_ + folder of the active Python interpreter. If you have an active virtual environment, it will be automatically detected. - Visit `FindPython3 `_ for more details. + We rely on CMake's logic for detecting Python, + visit `FindPython3 `_ for more details. .. include:: virtual_environment.rst @@ -58,10 +61,18 @@ From the root of the repository: .. code-block:: bash + pip install -e scenario/ pip install -e . The editable installation only symlinks the resources of the repository into the active Python installation. It allows to develop directly operating on the files of the repository and use the updated package without requiring to install it again. +.. note:: + + The ``scenario`` editable installation is just a placeholder. + It is necessary to prevent the editable installation of ``gym-ignition`` to override the resources installed by + the manual CMake execution. + Otherwise, the ``scenario`` package from PyPI would be pulled, resulting with a wrong version. + .. include:: system_configuration.rst diff --git a/docs/sphinx/installation/nightly.rst b/docs/sphinx/installation/nightly.rst index 8991bbcda..ae089e398 100644 --- a/docs/sphinx/installation/nightly.rst +++ b/docs/sphinx/installation/nightly.rst @@ -13,10 +13,23 @@ We publish updated nightly packages after any pull request merged in the ``devel PyPI Package ************ -Install the pre-release `gym-ignition `_ package from PyPI: +We provide two different packages for ScenarIO and gym-ignition. + +If you are interested in the ScenarIO package, +install the `scenario `_ package from PyPI: + +.. code-block:: bash + + pip install --pre scenario + +Instead, if you are interested in gym-ignition, +install the `gym-ignition `_ package from PyPI: .. code-block:: bash - pip install --pre gym-ignition + pip install --pre scenario gym-ignition + +Note that in this case, specifying also the ``scenario`` dependency is necessary, +otherwise ``pip`` will pull the stable package from PyPI. .. include:: system_configuration.rst diff --git a/docs/sphinx/installation/optional_dependencies.rst b/docs/sphinx/installation/optional_dependencies.rst deleted file mode 100644 index b9343c9af..000000000 --- a/docs/sphinx/installation/optional_dependencies.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. _installation_optional_dependencies: - -Optional Dependencies -===================== - -.. _installation_optional_dependencies_idyntree: - -iDynTree -******** - -**gym-ignition** provides helper classes to manipulate the kinematics and the dynamics of rigid-bodies. -Among the many existing solutions, we selected `iDynTree `_. - -Follow the `official installation instructions `__ and make sure that you also enable and install the `Python bindings `__. - -You can verify that the installation succeeded and your system is properly configured if you can ``import iDynTree`` in a Python interpreter. - -.. tip:: - If you want to learn more about ``iDynTree``, the two classes we mainly use are ``iDynTree::KinDynComputations`` (`docs `__) and ``iDynTree::InverseKinematics`` (`docs `__). - - The theory and notation behind the library is summarized in `Multibody dynamics notation `_. \ No newline at end of file diff --git a/docs/sphinx/installation/stable.rst b/docs/sphinx/installation/stable.rst index 08f4ed6a0..a07904b7f 100644 --- a/docs/sphinx/installation/stable.rst +++ b/docs/sphinx/installation/stable.rst @@ -13,8 +13,20 @@ We publish updated stable packages after any tagged release of the ``master`` br PyPI Package ************ -Install the `gym-ignition `_ package from PyPI: +We provide two different packages for ScenarIO and gym-ignition. + +If you are interested in the ScenarIO package, +install the `scenario `_ package from PyPI: + +.. code-block:: bash + + pip install scenario + +Instead, if you are interested in gym-ignition, +install the `gym-ignition `_ package from PyPI: .. code-block:: bash pip install gym-ignition + +It will download and install also ``scenario`` since it depends on it. diff --git a/docs/sphinx/installation/support_policy.rst b/docs/sphinx/installation/support_policy.rst index 37b020370..8651f19d1 100644 --- a/docs/sphinx/installation/support_policy.rst +++ b/docs/sphinx/installation/support_policy.rst @@ -8,19 +8,19 @@ dependencies, depending on the installation type you select. The project mostly supports all the major operating systems. However, we are currently using and testing only GNU/Linux systems. -We do not yet provide direct support to other operating systems. +We do not yet provide official support to other operating systems. The table below recaps the project requirements of the :ref:`Stable ` and :ref:`Nightly ` channels: -+-------------+-----------------+--------+------------------+----------+------------+---------+ -| Channel | C++ | Python | Ignition | Ubuntu | macOS [*]_ | Windows | -+=============+=================+========+==================+==========+============+=========+ -| **Stable** | >= gcc8, clang6 | >= 3.8 | `Dome`_ (binary) | >= 20.04 | No | No | -+-------------+-----------------+--------+------------------+----------+------------+---------+ -| **Nightly** | >= gcc8, clang6 | >= 3.8 | `Dome`_ (source) | >= 20.04 | No | No | -+-------------+-----------------+--------+------------------+----------+------------+---------+ ++-------------+-----------------+--------+----------------------+----------+------------+---------+ +| Channel | C++ | Python | Ignition | Ubuntu | macOS [*]_ | Windows | ++=============+=================+========+======================+==========+============+=========+ +| **Stable** | >= gcc8, clang6 | >= 3.8 | `Fortress`_ (binary) | >= 20.04 | No | No | ++-------------+-----------------+--------+----------------------+----------+------------+---------+ +| **Nightly** | >= gcc8, clang6 | >= 3.8 | `Fortress`_ (source) | >= 20.04 | No | No | ++-------------+-----------------+--------+----------------------+----------+------------+---------+ -.. _`Dome`: https://ignitionrobotics.org/docs/dome/install +.. _`Fortress`: https://ignitionrobotics.org/docs/fortress/install .. [*] Ignition officially supports macOS and also ``gym-ignition`` could be installed on this platform. However, we do not currently test this configuration and we cannot guarantee support. @@ -30,12 +30,12 @@ The table below recaps the project requirements of the :ref:`Stable `_ are no longer accurate. -This section provides a constantly updated description aligned with the most recent development news. - -.. _why_scenario: - -Why ScenarIO -============ - -*SCENe interfAces for Robot Input/Output* is an abstraction layer to interface with robots. -It exposes APIs to interact with a scene, providing a :cpp:class:`~scenario::core::World` that can return -:cpp:class:`~scenario::core::Model` objects, from which you can gather measurements and send commands. -The relevant APIs of ScenarIO are documented in the :ref:`Scenario Core ` section. - -Many simulators already provide an abstraction of different physics engines. -They expose a unified interface that, after selecting the desired back-end, maps its unified methods to those of the -underlying physics engine. The aim of ScenarIO is extending this idea also to real-time robots. - -The simulation backend of ScenarIO communicates with a simulator that itself abstracts physics engines. -This is powerful because, in this way, ScenarIO is independent from the details of the underlying physics engine. -Any current and future physics engine supported by the simulator is compatible with ScenarIO without requiring any -change from our side. - -Regarding real-time robots, the APIs of ScenarIO can be implemented exploiting middlewares like ROS or YARP. -At the time of writing there are no official real-time backends, stay tuned for further updates. - -Once both the simulation and real-time backends are in place, you can then write code to control your robot just once, -it will interact either with the simulated or the real robot depending on the ScenarIO backend you enabled. - -.. note:: - - The ScenarIO interface is flexible and generic. - Let's say you already have built your functionality with the backends we provide, and you are not happy from the performance of the simulator we used. - You can implement your own simulation backend and run it alongside those we provide. - The same applies to the real-time backend, in case your robot uses a custom middleware or SDK. - -.. tip: - - So far, we always referred to the C++ abstraction layer provided by ScenarIO. - The interface / implementation pattern is implemented with classic inheritance and polymorphism. - Having such unified interface simplifies the process to expose it to other languages. - Thanks to SWIG, we officially provide Python bindings of ScenarIO, so that you can prototype your applications even faster! - -.. _why_ignition_gazebo: - -Why Ignition Gazebo -=================== - -In this section we want to go a bit deeper in the motivations that led us to select Ignition Gazebo as target solution for the simulation back-end of ScenarIO. - -To begin, a bit of history. The `Gazebo `_ simulator that the entire robotic community has used for -over a decade is different than Ignition Gazebo. -We will refer to the old simulator as Gazebo Classic. -Ignition Gazebo is the new generation of the simulator. -It takes all the lesson learned by the development of Gazebo Classic and provides a more modular and extensible framework for robotic simulations. -The monolithic structure of Gazebo Classic has been broken in standalone libraries, obtaining a *suite* called `Ignition `_. -Ignition Gazebo is just one of the libraries [*]_ that compose the suite. -When we started the development of gym-ignition, Ignition Gazebo was "stable" enough to start using it. -The clear advantage is support: Gazebo Classic will just receive bug fixing, all the development effort now shifted towards the new Ignition Gazebo. -The price to pay, instead, is that Ignition Gazebo has not yet reached feature parity with Gazebo Classic, even though -the gap is getting filled quickly. - -.. [*] Yes, you read correctly: *library*. Ignition Gazebo is a library. - A `ignition::gazebo::Server` object can be instantiated and it can be stepped programmatically from your code. - This type of architecture became quite popular recently because it gives you full control of the simulation. - Ignition Gazebo, therefore, became a solution similar to the well known alternatives like ``pybullet`` and ``mujoco-py``. - -We have been and currently are Gazebo Classic users, as many other robotics research labs. -Over time, we became familiar with the tools and methods of Gazebo Classic and built a lot of code and applications that depend on it. -Unfortunately, despite someone has shown attempts, Gazebo Classic is not suitable for the large-scale simulations that are -typical in modern reinforcement learning architectures. -Ignition Gazebo offered us a viable solution for this use case that allows us to exploit the know-how we gained with Gazebo Classic. - -The two main features that drove us towards the adoption of Ignition Gazebo are the following: - -1. **Physics engines are loaded as plugin libraries and Ignition Gazebo operates on an API-stable interface.** - This architecture allows everyone to implement a new physics engine backend and run simulations exploiting all the other - components of the Ignition suite (rendering, systems, ...). - While today only `DART `_ is officially supported, we believe this is one of the best - attempts to obtain in the long run a framework that allows to switch physics engines with minimal effort. - For reinforcement learning research, it could bring domain randomization to the next level. -2. **Simulations can be stepped programmatically without relying on network transport, guaranteeing full reproducibility.** - Reproducible simulations are paramount whether you are prototyping a new robot controller or you are running - large-scale simulations for robot learning. - Most of the client-server architectures cannot guarantee reproducibility since asynchronous network transports could - provide different results depending on the load of your system. - An effective solution is using the simulator as a library and stepping it programmatically from your code. - Gazebo ScenarIO provides APIs to perform these kind of simulations with Ignition Gazebo. - -There are a bunch of other nice features we didn't cover in this section. -Not all of them are currently exposed to ScenarIO Gazebo, please open a feature request if you have any suggestion or, -even better, fire up a pull request! - -To summarize, these are the features that motivated us to choose Ignition Gazebo: - -- Simulator developed for robotics -- Simulator-as-a-library structure -- Abstraction of different physics engines and rendering engines -- Modular software architecture -- Powerful and constantly improving SDF model description -- Well maintained, packaged, and widely tested -- Large big database of objects to create worlds: `Ignition Fuel `_ -- Long term vision and support - -.. note:: - - Ignition Gazebo is the target simulator of the new `DARPA Subterranean Challenge `_. - Have a look to their simulation results to understand what you can expect from using Ignition Gazebo. - -.. _why_gym_ignition: - -Why gym-ignition -================ - -In the previous sections we described why we developed ScenarIO and why we used Ignition Gazebo to implement its simulation back-end. -While we mentioned few advantages for the robot learning domain, ScenarIO remains a general C++ library that can be used for generic robotic applications. - -The reinforcement learning community, in the past years, converged towards Python for the development of the environments -containing the decision-making logic. -`OpenAI Gym `_ became the reference interface to provide a clear separation between agents and environments. -A widespread interface is powerful because if you implement an environment that exposes the ``gym.Env`` interface, you can then use -all the countless frameworks provided by the community to train a policy selecting your favourite algorithm. - -The Python package ``gym_ignition`` enables you to create robotic environments compatible with OpenAI Gym. -Being based on ScenarIO, it enables to develop environments that can run not only on different physics engines, -but also on real robots. - -You can think of ``gym_ignition`` as a way to help you structuring your environment. -If you know how `pytorch-lightning `_ relates to PyTorch, -the same applies to the interaction between gym-ignition and ScenarIO. -Thanks to the :py:class:`~gym_ignition.base.task.Task` and :py:class:`~gym_ignition.base.runtime.Runtime` interfaces, -``gym_ignition`` abstracts away all the unnecessary boilerplate that otherwise you have to copy and paste between environments. - -For example, :py:class:`~gym_ignition.runtimes.gazebo_runtime.GazeboRuntime` provides all boilerplate code to take -your implementation of a :py:class:`~gym_ignition.base.task.Task` and simulate it with Ignition Gazebo. - -Furthermore, we provide useful classes with functionalities that are commonly required by robotic environments, like -Inverse Kinematic (:py:class:`~gym_ignition.rbd.idyntree.inverse_kinematics_nlp.InverseKinematicsNLP`) -and multibody dynamics algorithms (:py:class:`~gym_ignition.rbd.idyntree.kindyncomputations.KinDynComputations`) -with full support of floating-base systems. - -.. note:: - - Developing environments for robot learning in C++ is a valid choice and the community has shown different examples. - ScenarIO can be used to develop C++ environments as well, however we find more useful using Python since it allows - a faster prototyping. - -.. note:: - - To the best of our knowledge, the first package that implemented a structure that abstracts the task, the robot, and - the simulator is `openai_ros `_. - We have been inspired by its structure in the early stage of development, and the current interfaces implemented in - gym-ignition are an evolution of the original architecture. diff --git a/docs/sphinx/what/what_is_gym_ignition.rst b/docs/sphinx/what/what_is_gym_ignition.rst new file mode 100644 index 000000000..a487cbbd3 --- /dev/null +++ b/docs/sphinx/what/what_is_gym_ignition.rst @@ -0,0 +1,22 @@ +.. _what_is_gym_ignition: + +What is gym-ignition? +===================== + +**gym-ignition** is a framework to create **reproducible robotics environments** for reinforcement learning research. + +It is based on the :ref:`ScenarIO ` project which provides the low-level APIs to interface with the Ignition Gazebo simulator. +By default, RL environments share a lot of boilerplate code, e.g. for initializing the simulator or structuring the classes +to expose the ``gym.Env`` interface. +Gym-ignition provides the :py:class:`~gym_ignition.base.task.Task` and :py:class:`~gym_ignition.base.runtime.Runtime` +abstractions that help you focusing on the development of the decision-making logic rather than engineering. +It includes :py:mod:`~gym_ignition.randomizers` to simplify the implementation of domain randomization +of models, physics, and tasks. +Gym-ignition also provides powerful dynamics algorithms compatible with both fixed-base and floating-based robots by +exploiting `iDynTree `_ and exposing +high-level functionalities (:py:mod:`~gym_ignition.rbd.idyntree`). + +Gym-ignition does not provide out-of-the-box environments ready to be used. +Rather, its aim is simplifying and streamlining their development. +Nonetheless, for illustrative purpose, it includes canonical examples in the +:py:mod:`gym_ignition_environments` package. diff --git a/docs/sphinx/what/what_is_scenario.rst b/docs/sphinx/what/what_is_scenario.rst new file mode 100644 index 000000000..060a581d5 --- /dev/null +++ b/docs/sphinx/what/what_is_scenario.rst @@ -0,0 +1,31 @@ +.. _what_is_scenario: + +What is ScenarIO +================ + +**ScenarIO** is a C++ abstraction layer to interact with simulated and real robots. + +It mainly provides the following +`C++ interfaces `_: + +- :cpp:class:`scenario::core::World` +- :cpp:class:`scenario::core::Model` +- :cpp:class:`scenario::core::Link` +- :cpp:class:`scenario::core::Joint` + +These interfaces can be implemented to operate on different scenarios, +including robots operating on either simulated worlds or in real-time. + +ScenarIO currently fully implements **Gazebo ScenarIO** (APIs), +a simulated back-end that interacts with `Ignition Gazebo `_. +The result allows stepping the simulator programmatically, ensuring a fully reproducible behaviour. +It relates closely to other projects like +`pybullet `_ and `mujoco-py `_. + +A real-time backend that interacts with the `YARP `_ middleware is under development. + +ScenarIO can be used either from C++ (:ref:`Core APIs `, :ref:`Gazebo APIs `) +or from Python (:py:mod:`~scenario.bindings.core`, :py:mod:`~scenario.bindings.gazebo`). + +If you're interested to know the reasons why we started developing ScenarIO and why we selected Ignition Gazebo +for our simulations, visit the :ref:`Motivations ` section. diff --git a/docs/sphinx/why/motivations.rst b/docs/sphinx/why/motivations.rst new file mode 100644 index 000000000..b1e2592bb --- /dev/null +++ b/docs/sphinx/why/motivations.rst @@ -0,0 +1,13 @@ +.. _motivations: + +Intro +===== + +In this section we recap the motivations behind this project. +Choosing the right framework for your research is often challenging and we hope to provide a broader look that could +help deciding whether it meets your needs or not. + +The development of the framework is evolving quickly, and the architecture and motivations described in the +:ref:`reference publication ` are becoming outdated. +While that publication remains the reference in case you use the project for your research, +this section provides a constantly updated description aligned with the most recent development news. diff --git a/docs/sphinx/why/why_gym_ignition.rst b/docs/sphinx/why/why_gym_ignition.rst new file mode 100644 index 000000000..bcb6fb175 --- /dev/null +++ b/docs/sphinx/why/why_gym_ignition.rst @@ -0,0 +1,44 @@ +.. _why_gym_ignition: + +Why gym-ignition +================ + +In the previous sections we described why we developed ScenarIO and why we used Ignition Gazebo to implement its simulation back-end. +While we mentioned few advantages for the robot learning domain, ScenarIO remains a general C++ library that can be used for generic robotic applications. + +The reinforcement learning community, in the past years, converged towards Python for the development of the environments +containing the decision-making logic. +`OpenAI Gym `_ became the reference interface to provide a clear separation between agents and environments. +A widespread interface is powerful because if you implement an environment that exposes the ``gym.Env`` interface, you can then use +all the countless frameworks provided by the community to train a policy selecting your favourite algorithm. + +The Python package ``gym_ignition`` enables you to create robotic environments compatible with OpenAI Gym. +Being based on ScenarIO, it enables to develop environments that can run not only on different physics engines, +but also on real robots. + +You can think of ``gym_ignition`` as a way to help you structuring your environment. +If you know how `pytorch-lightning `_ relates to PyTorch, +the same applies to the interaction between gym-ignition and ScenarIO. +Thanks to the :py:class:`~gym_ignition.base.task.Task` and :py:class:`~gym_ignition.base.runtime.Runtime` interfaces, +``gym_ignition`` abstracts away all the unnecessary boilerplate that otherwise you have to copy and paste between environments. + +For example, :py:class:`~gym_ignition.runtimes.gazebo_runtime.GazeboRuntime` provides all boilerplate code to take +your implementation of a :py:class:`~gym_ignition.base.task.Task` and simulate it with Ignition Gazebo. + +Furthermore, we provide useful classes with functionalities that are commonly required by robotic environments, like +Inverse Kinematic (:py:class:`~gym_ignition.rbd.idyntree.inverse_kinematics_nlp.InverseKinematicsNLP`) +and multibody dynamics algorithms (:py:class:`~gym_ignition.rbd.idyntree.kindyncomputations.KinDynComputations`) +with full support of floating-base systems. + +.. note:: + + Developing environments for robot learning in C++ is a valid choice and the community has shown different examples. + ScenarIO can be used to develop C++ environments as well, however we find more useful using Python since it allows + a faster prototyping. + +.. note:: + + To the best of our knowledge, the first package that implemented a structure that abstracts the task, the robot, and + the simulator is `openai_ros `_. + We have been inspired by its structure in the early stage of development, and the current interfaces implemented in + gym-ignition are an evolution of the original architecture. diff --git a/docs/sphinx/why/why_ignition_gazebo.rst b/docs/sphinx/why/why_ignition_gazebo.rst new file mode 100644 index 000000000..28827c469 --- /dev/null +++ b/docs/sphinx/why/why_ignition_gazebo.rst @@ -0,0 +1,65 @@ +.. _why_ignition_gazebo: + +Why Ignition Gazebo +=================== + +In this section we want to go a bit deeper in the motivations that led us to select Ignition Gazebo as target solution for the simulation back-end of ScenarIO. + +To begin, a bit of history. The `Gazebo `_ simulator that the entire robotic community has used for +over a decade is different than Ignition Gazebo. +We will refer to the old simulator as Gazebo Classic. +Ignition Gazebo is the new generation of the simulator. +It takes all the lesson learned by the development of Gazebo Classic and provides a more modular and extensible framework for robotic simulations. +The monolithic structure of Gazebo Classic has been broken in standalone libraries, obtaining a *suite* called `Ignition `_. +Ignition Gazebo is just one of the libraries [*]_ that compose the suite. +When we started the development of gym-ignition, Ignition Gazebo was "stable" enough to start using it. +The clear advantage is support: Gazebo Classic will just receive bug fixing, all the development effort now shifted towards the new Ignition Gazebo. +The price to pay, instead, is that Ignition Gazebo has not yet reached feature parity with Gazebo Classic, even though +the gap is getting filled quickly. + +.. [*] Yes, you read correctly: *library*. Ignition Gazebo is a library. + A ``ignition::gazebo::Server`` object can be instantiated and it can be stepped programmatically from your code. + This type of architecture became quite popular recently because it gives you full control of the simulation. + Ignition Gazebo, therefore, became a solution similar to the well known alternatives like ``pybullet`` and ``mujoco-py``. + +We have been and currently are Gazebo Classic users, as many other robotics research labs. +Over time, we became familiar with the tools and methods of Gazebo Classic and built a lot of code and applications that depend on it. +Unfortunately, despite someone has shown attempts, Gazebo Classic is not suitable for the large-scale simulations that are +typical in modern reinforcement learning architectures. +Ignition Gazebo offered us a viable solution for this use case that allows us to exploit the know-how we gained with Gazebo Classic. + +The two main features that drove us towards the adoption of Ignition Gazebo are the following: + +1. **Physics engines are loaded as plugin libraries and Ignition Gazebo operates on an API-stable interface.** + This architecture allows everyone to implement a new physics engine backend and run simulations exploiting all the other + components of the Ignition suite (rendering, systems, ...). + While today only `DART `_ is officially supported, we believe this is one of the best + attempts to obtain in the long run a framework that allows to switch physics engines with minimal effort. + For reinforcement learning research, it could bring domain randomization to the next level. +2. **Simulations can be stepped programmatically without relying on network transport, guaranteeing full reproducibility.** + Reproducible simulations are paramount whether you are prototyping a new robot controller or you are running + large-scale simulations for robot learning. + Most of the client-server architectures cannot guarantee reproducibility since asynchronous network transports could + provide different results depending on the load of your system. + An effective solution is using the simulator as a library and stepping it programmatically from your code. + Gazebo ScenarIO provides APIs to perform these kind of simulations with Ignition Gazebo. + +There are a bunch of other nice features we didn't cover in this section. +Not all of them are currently exposed to ScenarIO Gazebo, please open a feature request if you have any suggestion or, +even better, fire up a pull request! + +To summarize, these are the features that motivated us to choose Ignition Gazebo: + +- Simulator developed for robotics +- Simulator-as-a-library structure +- Abstraction of different physics engines and rendering engines +- Modular software architecture +- Powerful and constantly improving SDF model description +- Well maintained, packaged, and widely tested +- Large big database of objects to create worlds: `Ignition Fuel `_ +- Long term vision and support + +.. note:: + + Ignition Gazebo is the target simulator of the new `DARPA Subterranean Challenge `_. + Have a look to their simulation results to understand what you can expect from using Ignition Gazebo. diff --git a/docs/sphinx/why/why_scenario.rst b/docs/sphinx/why/why_scenario.rst new file mode 100644 index 000000000..a7635ef76 --- /dev/null +++ b/docs/sphinx/why/why_scenario.rst @@ -0,0 +1,40 @@ +.. _why_scenario: + +Why ScenarIO +============ + +*SCENe interfAces for Robot Input/Output* is an abstraction layer to interface with robots. +It exposes APIs to interact with a scene, providing a :cpp:class:`~scenario::core::World` that can return +:cpp:class:`~scenario::core::Model` objects, from which you can gather measurements and send commands. +The relevant APIs of ScenarIO are documented in the :ref:`Scenario Core ` section. + +Many simulators already provide an abstraction of different physics engines. +They expose a unified interface that, after selecting the desired back-end, maps its unified methods to those of the +underlying physics engine. The aim of ScenarIO is extending this idea also to real-time robots. + +The simulation backend of ScenarIO communicates with a simulator that itself abstracts physics engines. +This is powerful because, in this way, ScenarIO is independent from the details of the underlying physics engine. +Any current and future physics engine supported by the simulator is compatible with ScenarIO without requiring any +change from our side. + +Regarding real-time robots, the APIs of ScenarIO can be implemented exploiting middlewares like ROS or YARP. +At the time of writing there are no official real-time backends, stay tuned for further updates. + +Once both the simulation and real-time backends are in place, you can then write code to control your robot just once, +it will interact either with the simulated or the real robot depending on the ScenarIO backend you enabled. + +.. note:: + + The ScenarIO interface is flexible and generic. + Let's say you already have built your functionality with the backends we provide, + and you are not happy from the performance of the simulator we used. + You can implement your own simulation backend and run it alongside those we provide. + The same applies to the real-time backend, in case your robot uses a custom middleware or SDK. + +.. tip:: + + So far, we always referred to the C++ abstraction layer provided by ScenarIO. + The interface / implementation pattern is implemented with classic inheritance and polymorphism. + Having such unified interface simplifies the process to expose it to other languages. + Thanks to SWIG, we officially provide Python bindings of ScenarIO, + so that you can prototype your applications even faster! diff --git a/examples/panda_pick_and_place.py b/examples/panda_pick_and_place.py index ac45960fe..841099c2f 100644 --- a/examples/panda_pick_and_place.py +++ b/examples/panda_pick_and_place.py @@ -1,16 +1,18 @@ -import time import enum -import numpy as np -import gym_ignition -from typing import List +import time from functools import partial +from typing import List + +import gym_ignition import gym_ignition_environments +import numpy as np +from gym_ignition.context.gazebo import controllers from gym_ignition.rbd import conversions +from gym_ignition.rbd.idyntree import inverse_kinematics_nlp +from scipy.spatial.transform import Rotation as R + from scenario import core as scenario_core from scenario import gazebo as scenario_gazebo -from scipy.spatial.transform import Rotation as R -from gym_ignition.context.gazebo import controllers -from gym_ignition.rbd.idyntree import inverse_kinematics_nlp # Configure verbosity scenario_gazebo.set_verbosity(scenario_gazebo.Verbosity_error) @@ -19,26 +21,31 @@ np.set_printoptions(precision=4, suppress=True) -def add_panda_controller(panda: gym_ignition_environments.models.panda.Panda, - controller_period: float) -> None: +def add_panda_controller( + panda: gym_ignition_environments.models.panda.Panda, controller_period: float +) -> None: # Set the controller period assert panda.set_controller_period(period=controller_period) # Increase the max effort of the fingers - panda.get_joint(joint_name="panda_finger_joint1").to_gazebo(). \ - set_max_generalized_force(max_force=500.0) - panda.get_joint(joint_name="panda_finger_joint2").to_gazebo(). \ - set_max_generalized_force(max_force=500.0) + panda.get_joint( + joint_name="panda_finger_joint1" + ).to_gazebo().set_max_generalized_force(max_force=500.0) + panda.get_joint( + joint_name="panda_finger_joint2" + ).to_gazebo().set_max_generalized_force(max_force=500.0) # Insert the ComputedTorqueFixedBase controller - assert panda.to_gazebo().insert_model_plugin(*controllers.ComputedTorqueFixedBase( - kp=[100.0] * (panda.dofs() - 2) + [10000.0] * 2, - ki=[0.0] * panda.dofs(), - kd=[17.5] * (panda.dofs() - 2) + [100.0] * 2, - urdf=panda.get_model_file(), - joints=panda.joint_names(), - ).args()) + assert panda.to_gazebo().insert_model_plugin( + *controllers.ComputedTorqueFixedBase( + kp=[100.0] * (panda.dofs() - 2) + [10000.0] * 2, + ki=[0.0] * panda.dofs(), + kd=[17.5] * (panda.dofs() - 2) + [100.0] * 2, + urdf=panda.get_model_file(), + joints=panda.joint_names(), + ).args() + ) # Initialize the controller to the current state assert panda.set_joint_position_targets(panda.joint_positions()) @@ -46,34 +53,40 @@ def add_panda_controller(panda: gym_ignition_environments.models.panda.Panda, assert panda.set_joint_acceleration_targets(panda.joint_accelerations()) -def get_panda_ik(panda: gym_ignition_environments.models.panda.Panda, - optimized_joints: List[str]) -> \ - inverse_kinematics_nlp.InverseKinematicsNLP: +def get_panda_ik( + panda: gym_ignition_environments.models.panda.Panda, optimized_joints: List[str] +) -> inverse_kinematics_nlp.InverseKinematicsNLP: # Create IK ik = inverse_kinematics_nlp.InverseKinematicsNLP( urdf_filename=panda.get_model_file(), considered_joints=optimized_joints, - joint_serialization=panda.joint_names()) + joint_serialization=panda.joint_names(), + ) # Initialize IK - ik.initialize(verbosity=1, - floating_base=False, - cost_tolerance=1E-8, - constraints_tolerance=1E-8, - base_frame=panda.base_frame()) + ik.initialize( + verbosity=1, + floating_base=False, + cost_tolerance=1e-8, + constraints_tolerance=1e-8, + base_frame=panda.base_frame(), + ) # Set the current configuration ik.set_current_robot_configuration( base_position=np.array(panda.base_position()), base_quaternion=np.array(panda.base_orientation()), - joint_configuration=np.array(panda.joint_positions())) + joint_configuration=np.array(panda.joint_positions()), + ) # Add the cartesian target of the end effector end_effector = "end_effector_frame" - ik.add_target(frame_name=end_effector, - target_type=inverse_kinematics_nlp.TargetType.POSE, - as_constraint=False) + ik.add_target( + frame_name=end_effector, + target_type=inverse_kinematics_nlp.TargetType.POSE, + as_constraint=False, + ) return ik @@ -85,17 +98,20 @@ def insert_bucket(world: scenario_gazebo.World) -> scenario_gazebo.Model: # Download the cube SDF file bucket_sdf = scenario_gazebo.get_model_file_from_fuel( - uri=uri(org="GoogleResearch", - name="Threshold_Basket_Natural_Finish_Fabric_Liner_Small"), - use_cache=False) + uri=uri( + org="GoogleResearch", + name="Threshold_Basket_Natural_Finish_Fabric_Liner_Small", + ), + use_cache=False, + ) # Assign a custom name to the model model_name = "bucket" # Insert the model - assert world.insert_model(bucket_sdf, - scenario_core.Pose([0.68, 0, 1.02], [1., 0, 0, 1]), - model_name) + assert world.insert_model( + bucket_sdf, scenario_core.Pose([0.68, 0, 1.02], [1.0, 0, 0, 1]), model_name + ) # Return the model return world.get_model(model_name=model_name) @@ -108,56 +124,61 @@ def insert_table(world: scenario_gazebo.World) -> scenario_gazebo.Model: # Download the cube SDF file bucket_sdf = scenario_gazebo.get_model_file_from_fuel( - uri=uri(org="OpenRobotics", - name="Table"), - use_cache=False) + uri=uri(org="OpenRobotics", name="Table"), use_cache=False + ) # Assign a custom name to the model model_name = "table" # Insert the model - assert world.insert_model(bucket_sdf, - scenario_core.Pose_identity(), - model_name) + assert world.insert_model(bucket_sdf, scenario_core.Pose_identity(), model_name) # Return the model return world.get_model(model_name=model_name) -def insert_cube_in_operating_area(world: scenario_gazebo.World) -> scenario_gazebo.Model: +def insert_cube_in_operating_area( + world: scenario_gazebo.World, +) -> scenario_gazebo.Model: # Insert objects from Fuel uri = lambda org, name: f"https://fuel.ignitionrobotics.org/{org}/models/{name}" # Download the cube SDF file cube_sdf = scenario_gazebo.get_model_file_from_fuel( - uri=uri(org="openrobotics", name="wood cube 5cm"), use_cache=False) + uri=uri(org="openrobotics", name="wood cube 5cm"), use_cache=False + ) # Sample a random position random_position = np.random.uniform(low=[0.2, -0.3, 1.01], high=[0.4, 0.3, 1.01]) # Get a unique name model_name = gym_ignition.utils.scenario.get_unique_model_name( - world=world, model_name="cube") + world=world, model_name="cube" + ) # Insert the model assert world.insert_model( - cube_sdf, scenario_core.Pose(random_position, [1., 0, 0, 0]), model_name) + cube_sdf, scenario_core.Pose(random_position, [1.0, 0, 0, 0]), model_name + ) # Return the model return world.get_model(model_name=model_name) -def solve_ik(target_position: np.ndarray, - target_orientation: np.ndarray, - ik: inverse_kinematics_nlp.InverseKinematicsNLP) -> np.ndarray: +def solve_ik( + target_position: np.ndarray, + target_orientation: np.ndarray, + ik: inverse_kinematics_nlp.InverseKinematicsNLP, +) -> np.ndarray: quat_xyzw = R.from_euler(seq="y", angles=90, degrees=True).as_quat() ik.update_transform_target( target_name=ik.get_active_target_names()[0], position=target_position, - quaternion=conversions.Quaternion.to_wxyz(xyzw=quat_xyzw)) + quaternion=conversions.Quaternion.to_wxyz(xyzw=quat_xyzw), + ) # Run the IK ik.solve() @@ -165,17 +186,21 @@ def solve_ik(target_position: np.ndarray, return ik.get_reduced_solution().joint_configuration -def end_effector_reached(position: np.array, - end_effector_link: scenario_core.Link, - max_error_pos: float = 0.01, - max_error_vel: float = 0.5, - mask: np.ndarray = np.array([1., 1., 1.])) -> bool: +def end_effector_reached( + position: np.array, + end_effector_link: scenario_core.Link, + max_error_pos: float = 0.01, + max_error_vel: float = 0.5, + mask: np.ndarray = np.array([1.0, 1.0, 1.0]), +) -> bool: masked_target = mask * position masked_current = mask * np.array(end_effector_link.position()) - return np.linalg.norm(masked_current - masked_target) < max_error_pos and \ - np.linalg.norm(end_effector_link.world_linear_velocity()) < max_error_vel + return ( + np.linalg.norm(masked_current - masked_target) < max_error_pos + and np.linalg.norm(end_effector_link.world_linear_velocity()) < max_error_vel + ) def get_unload_position(bucket: scenario_core.Model) -> np.ndarray: @@ -189,8 +214,9 @@ class FingersAction(enum.Enum): CLOSE = enum.auto() -def move_fingers(panda: gym_ignition_environments.models.panda.Panda, - action: FingersAction) -> None: +def move_fingers( + panda: gym_ignition_environments.models.panda.Panda, action: FingersAction +) -> None: # Get the joints of the fingers finger1 = panda.get_joint(joint_name="panda_finger_joint1") @@ -211,7 +237,8 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, # Get the simulator and the world gazebo, world = gym_ignition.utils.scenario.init_gazebo_sim( - step_size=0.001, real_time_factor=2.0, steps_per_run=1) + step_size=0.001, real_time_factor=2.0, steps_per_run=1 +) # Open the GUI gazebo.gui() @@ -220,7 +247,11 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, # Insert the Panda manipulator panda = gym_ignition_environments.models.panda.Panda( - world=world, position=[-0.1, 0, 1.0]) + world=world, position=[-0.1, 0, 1.0] +) + +# Disable joint velocity limits +_ = [j.to_gazebo().set_velocity_limit(1_000) for j in panda.joints()] # Enable contacts only for the finger links panda.get_link("panda_leftfinger").to_gazebo().enable_contact_detection(True) @@ -242,7 +273,9 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, gazebo.run(paused=True) # Create and configure IK for the panda -ik_joints = [j.name() for j in panda.joints() if j.type is not scenario_core.JointType_fixed ] +ik_joints = [ + j.name() for j in panda.joints() if j.type is not scenario_core.JointType_fixed +] ik = get_panda_ik(panda=panda, optimized_joints=ik_joints) # Get some manipulator links @@ -269,7 +302,8 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, over_joint_configuration = solve_ik( target_position=position_over_cube, target_orientation=np.array(cube.base_orientation()), - ik=ik) + ik=ik, + ) # Set the joint references assert panda.set_joint_position_targets(over_joint_configuration, ik_joints) @@ -278,10 +312,12 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, panda.open_fingers() # Run the simulation until the EE reached the desired position - while not end_effector_reached(position=position_over_cube, - end_effector_link=end_effector_frame, - max_error_pos=0.05, - max_error_vel=0.5): + while not end_effector_reached( + position=position_over_cube, + end_effector_link=end_effector_frame, + max_error_pos=0.05, + max_error_vel=0.5, + ): gazebo.run() # Wait a bit more @@ -297,7 +333,8 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, over_joint_configuration = solve_ik( target_position=np.array(cube.base_position()) + np.array([0, 0, 0.04]), target_orientation=np.array(cube.base_orientation()), - ik=ik) + ik=ik, + ) # Set the joint references assert panda.set_joint_position_targets(over_joint_configuration, ik_joints) @@ -305,8 +342,9 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, # Run the simulation until the EE reached the desired position while not end_effector_reached( - position=np.array(cube.base_position()) + np.array([0, 0, 0.04]), - end_effector_link=end_effector_frame): + position=np.array(cube.base_position()) + np.array([0, 0, 0.04]), + end_effector_link=end_effector_frame, + ): gazebo.run() @@ -323,8 +361,10 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, panda.close_fingers() # Detect a graps reading the contact wrenches of the finger links - while not (np.linalg.norm(finger_left.contact_wrench()) >= 50.0 and - np.linalg.norm(finger_right.contact_wrench()) >= 50.0): + while not ( + np.linalg.norm(finger_left.contact_wrench()) >= 50.0 + and np.linalg.norm(finger_right.contact_wrench()) >= 50.0 + ): gazebo.run() # ============= @@ -340,16 +380,19 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, over_joint_configuration = solve_ik( target_position=position_over_cube, target_orientation=np.array(cube.base_orientation()), - ik=ik) + ik=ik, + ) # Set the joint references assert panda.set_joint_position_targets(over_joint_configuration, ik_joints) # Run the simulation until the EE reached the desired position - while not end_effector_reached(position=position_over_cube, - end_effector_link=end_effector_frame, - max_error_pos=0.1, - max_error_vel=0.5): + while not end_effector_reached( + position=position_over_cube, + end_effector_link=end_effector_frame, + max_error_pos=0.1, + max_error_vel=0.5, + ): gazebo.run() # Wait a bit more @@ -365,20 +408,21 @@ def move_fingers(panda: gym_ignition_environments.models.panda.Panda, unload_joint_configuration = solve_ik( target_position=get_unload_position(bucket=bucket), target_orientation=np.array([0, 1.0, 0, 0]), - ik=ik) + ik=ik, + ) # Set the joint references - assert panda.set_joint_position_targets(unload_joint_configuration, - ik_joints) + assert panda.set_joint_position_targets(unload_joint_configuration, ik_joints) # Run the simulation until the EE reached the desired position while not end_effector_reached( - position=get_unload_position(bucket=bucket) + - np.random.uniform(low=-0.05, high=0.05, size=3), - end_effector_link=end_effector_frame, - max_error_pos=0.01, - max_error_vel=0.1, - mask=np.array([1, 1, 0])): + position=get_unload_position(bucket=bucket) + + np.random.uniform(low=-0.05, high=0.05, size=3), + end_effector_link=end_effector_frame, + max_error_pos=0.01, + max_error_vel=0.1, + mask=np.array([1, 1, 0]), + ): gazebo.run() diff --git a/examples/python/launch_cartpole.py b/examples/python/launch_cartpole.py index 650a4c690..9ca935882 100755 --- a/examples/python/launch_cartpole.py +++ b/examples/python/launch_cartpole.py @@ -2,9 +2,10 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -import gym -import time import functools +import time + +import gym from gym_ignition.utils import logger from gym_ignition_environments import randomizers @@ -21,6 +22,7 @@ def make_env_from_id(env_id: str, **kwargs) -> gym.Env: import gym import gym_ignition_environments + return gym.make(env_id, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index be4e020fe..229623aac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,19 @@ [build-system] -requires = ["setuptools>=42", "setuptools_scm[toml]>=3.4", "cmake-build-extension","wheel", "idyntree"] build-backend = "setuptools.build_meta" +requires = [ + "wheel", + "setuptools>=45", + "setuptools_scm[toml]>=6.0", + "cmake-build-extension", +] [tool.setuptools_scm] local_scheme = "dirty-tag" + +[tool.black] +line-length = 88 + +[tool.isort] +profile = "black" +multi_line_output = 3 + diff --git a/python/gym_ignition/__init__.py b/python/gym_ignition/__init__.py index f59ddd7ce..8aca5b06a 100644 --- a/python/gym_ignition/__init__.py +++ b/python/gym_ignition/__init__.py @@ -5,10 +5,38 @@ # Workaround for https://github.com/osrf/sdformat/issues/227. # It has to be done before loading the bindings. import gym_ignition_models + gym_ignition_models.setup_environment() # Add IGN_GAZEBO_RESOURCE_PATH to the default search path import os + from gym_ignition.utils import resource_finder + if "IGN_GAZEBO_RESOURCE_PATH" in os.environ: resource_finder.add_path_from_env_var("IGN_GAZEBO_RESOURCE_PATH") + + +def initialize_verbosity() -> None: + + import gym + import gym_ignition.utils.logger + + import scenario + + if scenario.detect_install_mode() is scenario.InstallMode.Developer: + gym_ignition.utils.logger.set_level( + level=gym.logger.INFO, scenario_level=gym.logger.WARN + ) + + elif scenario.detect_install_mode() is scenario.InstallMode.User: + gym_ignition.utils.logger.set_level( + level=gym.logger.WARN, scenario_level=gym.logger.WARN + ) + + else: + raise ValueError(scenario.detect_install_mode()) + + +# Configure default verbosity +initialize_verbosity() diff --git a/python/gym_ignition/base/__init__.py b/python/gym_ignition/base/__init__.py index 0a78c14b3..e6b03e4ba 100644 --- a/python/gym_ignition/base/__init__.py +++ b/python/gym_ignition/base/__init__.py @@ -3,5 +3,4 @@ # GNU Lesser General Public License v2.1 or any later version. # Abstract classes -from . import task -from . import runtime +from . import runtime, task diff --git a/python/gym_ignition/base/runtime.py b/python/gym_ignition/base/runtime.py index f5bd8fe64..1cc1fd72a 100644 --- a/python/gym_ignition/base/runtime.py +++ b/python/gym_ignition/base/runtime.py @@ -3,8 +3,9 @@ # GNU Lesser General Public License v2.1 or any later version. import abc + import gym -from gym_ignition import base +from gym_ignition.base.task import Task class Runtime(gym.Env, abc.ABC): @@ -59,10 +60,10 @@ def close(self): Runtimes can handle only one :py:class:`~gym_ignition.base.task.Task` object. """ - def __init__(self, task: base.task.Task, agent_rate: float): + def __init__(self, task: Task, agent_rate: float): #: Task handled by the runtime. - self.task: base.task.Task = task + self.task: Task = task #: Rate of environment execution. self.agent_rate = agent_rate diff --git a/python/gym_ignition/base/task.py b/python/gym_ignition/base/task.py index 169ac42b9..40aaf2a02 100644 --- a/python/gym_ignition/base/task.py +++ b/python/gym_ignition/base/task.py @@ -3,13 +3,21 @@ # GNU Lesser General Public License v2.1 or any later version. import abc +from typing import Dict, Tuple + import gym import numpy as np -from scenario import core from gym.utils import seeding -from typing import Dict, Tuple -from gym_ignition.utils.typing import ActionSpace, ObservationSpace -from gym_ignition.utils.typing import Action, Observation, Reward, SeedList +from gym_ignition.utils.typing import ( + Action, + ActionSpace, + Observation, + ObservationSpace, + Reward, + SeedList, +) + +from scenario import core class Task(abc.ABC): @@ -224,7 +232,7 @@ def seed_task(self, seed: int = None) -> SeedList: """ # Create the seed if not passed - seed = np.random.randint(2**32 - 1) if seed is None else seed + seed = np.random.randint(2 ** 32 - 1) if seed is None else seed # Get an instance of the random number generator from gym utils. # This is necessary to have an independent rng for each environment. diff --git a/python/gym_ignition/context/gazebo/__init__.py b/python/gym_ignition/context/gazebo/__init__.py index 9b3a446d4..b5c6c19df 100644 --- a/python/gym_ignition/context/gazebo/__init__.py +++ b/python/gym_ignition/context/gazebo/__init__.py @@ -2,5 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import plugin -from . import controllers +from . import controllers, plugin diff --git a/python/gym_ignition/context/gazebo/controllers.py b/python/gym_ignition/context/gazebo/controllers.py index 2e64f4d94..b746130d5 100644 --- a/python/gym_ignition/context/gazebo/controllers.py +++ b/python/gym_ignition/context/gazebo/controllers.py @@ -2,15 +2,16 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from gym_ignition.context import gazebo -from typing import Iterable, List, Tuple from dataclasses import dataclass, field +from typing import Iterable, List, Tuple + +from gym_ignition.context.gazebo import plugin GRAVITY = (0, 0, -9.80665) @dataclass -class ComputedTorqueFixedBase(gazebo.plugin.GazeboPlugin): +class ComputedTorqueFixedBase(plugin.GazeboPlugin): urdf: str kp: List[float] @@ -22,9 +23,9 @@ class ComputedTorqueFixedBase(gazebo.plugin.GazeboPlugin): # Private fields _name: str = field(init=False, repr=False, default="ComputedTorqueFixedBase") _plugin_name: str = field(init=False, repr=False, default="ControllerRunner") - _plugin_class: str = field(init=False, - repr=False, - default="scenario::plugins::gazebo::ControllerRunner") + _plugin_class: str = field( + init=False, repr=False, default="scenario::plugins::gazebo::ControllerRunner" + ) def to_xml(self) -> str: xml = f""" diff --git a/python/gym_ignition/context/gazebo/plugin.py b/python/gym_ignition/context/gazebo/plugin.py index 98d00de05..5bf8a73fa 100644 --- a/python/gym_ignition/context/gazebo/plugin.py +++ b/python/gym_ignition/context/gazebo/plugin.py @@ -3,11 +3,11 @@ # GNU Lesser General Public License v2.1 or any later version. import abc -from typing import Tuple from dataclasses import dataclass, field +from typing import Tuple # Default SDF version used in the serialized XML context -SDF_VERSION=1.7 +SDF_VERSION = 1.7 # Read the following for more information about dataclasses internals: # https://florimond.dev/blog/articles/2018/10/reconciling-dataclasses-and-properties-in-python/ @@ -57,9 +57,11 @@ def args(self) -> Tuple[str, str, str]: Returns: A tuple with the args required to insert the plugin. """ - return str(self._plugin_name), \ - str(self._plugin_class), \ - GazeboPlugin.wrap_in_sdf(self.to_xml()) + return ( + str(self._plugin_name), + str(self._plugin_class), + GazeboPlugin.wrap_in_sdf(self.to_xml()), + ) @staticmethod def wrap_in_sdf(context: str) -> str: diff --git a/python/gym_ignition/randomizers/__init__.py b/python/gym_ignition/randomizers/__init__.py index f5aef5284..d91e26d93 100644 --- a/python/gym_ignition/randomizers/__init__.py +++ b/python/gym_ignition/randomizers/__init__.py @@ -2,8 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import abc -from . import model -from . import physics - -from . import gazebo_env_randomizer +from . import abc, gazebo_env_randomizer, model, physics diff --git a/python/gym_ignition/randomizers/abc.py b/python/gym_ignition/randomizers/abc.py index 6cec4c99c..e0f61eb74 100644 --- a/python/gym_ignition/randomizers/abc.py +++ b/python/gym_ignition/randomizers/abc.py @@ -3,16 +3,15 @@ # GNU Lesser General Public License v2.1 or any later version. import abc + import gym_ignition.base.task + from scenario import core as scenario_core class TaskRandomizer(abc.ABC): - @abc.abstractmethod - def randomize_task(self, - task: gym_ignition.base.task.Task, - **kwargs) -> None: + def randomize_task(self, task: gym_ignition.base.task.Task, **kwargs) -> None: """ Randomize a :py:class:`~gym_ignition.base.task.Task` instance. @@ -101,10 +100,10 @@ def physics_expired(self) -> bool: class ModelRandomizer(abc.ABC): - @abc.abstractmethod - def randomize_model(self, task: gym_ignition.base.task.Task, **kwargs) \ - -> scenario_core.Model: + def randomize_model( + self, task: gym_ignition.base.task.Task, **kwargs + ) -> scenario_core.Model: """ Randomize the model. @@ -118,10 +117,10 @@ def randomize_model(self, task: gym_ignition.base.task.Task, **kwargs) \ class ModelDescriptionRandomizer(abc.ABC): - @abc.abstractmethod - def randomize_model_description(self, task: gym_ignition.base.task.Task, **kwargs) \ - -> str: + def randomize_model_description( + self, task: gym_ignition.base.task.Task, **kwargs + ) -> str: """ Randomize the model description. diff --git a/python/gym_ignition/randomizers/gazebo_env_randomizer.py b/python/gym_ignition/randomizers/gazebo_env_randomizer.py index a6f68fc3c..f1690231b 100644 --- a/python/gym_ignition/randomizers/gazebo_env_randomizer.py +++ b/python/gym_ignition/randomizers/gazebo_env_randomizer.py @@ -3,19 +3,18 @@ # GNU Lesser General Public License v2.1 or any later version. import abc +from typing import Callable, Dict, Optional, Union, cast + import gym -from typing import cast -from gym_ignition import randomizers -from gym_ignition.utils import logger, typing +from gym_ignition.randomizers.abc import PhysicsRandomizer, TaskRandomizer +from gym_ignition.randomizers.physics import dart from gym_ignition.runtimes import gazebo_runtime -from typing import Callable, Dict, Optional, Union +from gym_ignition.utils import typing -MakeEnvCallable = Callable[[Optional[Dict]],gym.Env] +MakeEnvCallable = Callable[[Optional[Dict]], gym.Env] -class GazeboEnvRandomizer(gym.Wrapper, - randomizers.abc.TaskRandomizer, - abc.ABC): +class GazeboEnvRandomizer(gym.Wrapper, TaskRandomizer, abc.ABC): """ Base class to implement an environment randomizer for Ignition Gazebo. @@ -46,11 +45,15 @@ class GazeboEnvRandomizer(gym.Wrapper, This operation is demanding, consider randomizing physics at a low rate. """ - def __init__(self, - env: Union[str, MakeEnvCallable], - physics_randomizer: randomizers.abc.PhysicsRandomizer = - randomizers.physics.dart.DART(), - **kwargs): + def __init__( + self, + env: Union[str, MakeEnvCallable], + physics_randomizer: PhysicsRandomizer = dart.DART(), + **kwargs, + ): + + # Print the extra kwargs + gym.logger.debug(f"GazeboEnvRandomizer: {dict(kwargs=kwargs)}") # Store the options self._env_option = env @@ -106,9 +109,9 @@ def reset(self, **kwargs) -> typing.Observation: # Private methods # =============== - def _create_environment(self, - env: Union[str, MakeEnvCallable], - **kwargs) -> gazebo_runtime.GazeboRuntime: + def _create_environment( + self, env: Union[str, MakeEnvCallable], **kwargs + ) -> gazebo_runtime.GazeboRuntime: if isinstance(env, str): env_to_wrap = self._create_from_id(env_id=env, **kwargs) @@ -125,18 +128,13 @@ def _create_environment(self, return cast(gazebo_runtime.GazeboRuntime, env_to_wrap) @staticmethod - def _create_from_callable(make_env: MakeEnvCallable, - **kwargs) -> gym.Env: - - with logger.gym_verbosity(level=gym.logger.WARN): - env = make_env(**kwargs) + def _create_from_callable(make_env: MakeEnvCallable, **kwargs) -> gym.Env: + env = make_env(**kwargs) return env @staticmethod def _create_from_id(env_id: str, **kwargs) -> gym.Env: - with logger.gym_verbosity(level=gym.logger.WARN): - env = gym.make(env_id, **kwargs) - + env = gym.make(env_id, **kwargs) return env diff --git a/python/gym_ignition/randomizers/model/sdf.py b/python/gym_ignition/randomizers/model/sdf.py index 9d6396f3f..52b274b4f 100644 --- a/python/gym_ignition/randomizers/model/sdf.py +++ b/python/gym_ignition/randomizers/model/sdf.py @@ -2,12 +2,13 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -import numpy as np -from lxml import etree +from enum import Enum, auto from pathlib import Path -from enum import auto, Enum from typing import Dict, List, NamedTuple, Union +import numpy as np +from lxml import etree + class Distribution(Enum): Uniform = auto() @@ -71,9 +72,9 @@ def at_xpath(self, xpath: str) -> "RandomizationDataBuilder": self.storage["xpath"] = xpath return self - def sampled_from(self, - distribution: Distribution, - parameters: DistributionParameters) -> "RandomizationDataBuilder": + def sampled_from( + self, distribution: Distribution, parameters: DistributionParameters + ) -> "RandomizationDataBuilder": """ Set the distribution associated to the randomization. @@ -88,12 +89,14 @@ def sampled_from(self, self.storage["distribution"] = distribution self.storage["parameters"] = parameters - if self.storage["distribution"] is Distribution.Gaussian and \ - not isinstance(parameters, GaussianParams): + if self.storage["distribution"] is Distribution.Gaussian and not isinstance( + parameters, GaussianParams + ): raise ValueError("Wrong parameters type") - if self.storage["distribution"] is Distribution.Uniform and \ - not isinstance(parameters, UniformParams): + if self.storage["distribution"] is Distribution.Uniform and not isinstance( + parameters, UniformParams + ): raise ValueError("Wrong parameters type") return self @@ -254,7 +257,8 @@ def process_data(self) -> None: # Update the data complete_data = data._replace( - xpath=element_xpath, element=element, parameters=params) + xpath=element_xpath, element=element, parameters=params + ) expanded_randomizations.append(complete_data) @@ -280,20 +284,19 @@ def sample(self, pretty_print=False) -> str: if data.distribution is Distribution.Gaussian: - sample = self.rng.normal(loc=data.parameters.mean, - scale=data.parameters.variance) + sample = self.rng.normal( + loc=data.parameters.mean, scale=data.parameters.variance + ) elif data.distribution is Distribution.Uniform: - sample = self.rng.uniform(low=data.parameters.low, - high=data.parameters.high) + sample = self.rng.uniform( + low=data.parameters.low, high=data.parameters.high + ) else: raise ValueError("Distribution not recognized") - if data.force_positive: - sample = max(sample, 0.0) - # Update the value if data.method is Method.Absolute: @@ -312,6 +315,9 @@ def sample(self, pretty_print=False) -> str: else: raise ValueError("Method not recognized") + if data.force_positive: + data.element.text = str(max(float(data.element.text), 0.0)) + return etree.tostring(self._root, pretty_print=pretty_print).decode() def new_randomization(self) -> RandomizationDataBuilder: diff --git a/python/gym_ignition/randomizers/physics/dart.py b/python/gym_ignition/randomizers/physics/dart.py index a170ab572..59f25aae2 100644 --- a/python/gym_ignition/randomizers/physics/dart.py +++ b/python/gym_ignition/randomizers/physics/dart.py @@ -4,6 +4,7 @@ import gym_ignition.base.task from gym_ignition import randomizers + from scenario import gazebo as scenario diff --git a/python/gym_ignition/rbd/__init__.py b/python/gym_ignition/rbd/__init__.py index 3403736e2..ba0a6f330 100644 --- a/python/gym_ignition/rbd/__init__.py +++ b/python/gym_ignition/rbd/__init__.py @@ -2,6 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import utils -from . import idyntree -from . import conversions +from . import conversions, idyntree, utils diff --git a/python/gym_ignition/rbd/conversions.py b/python/gym_ignition/rbd/conversions.py index 107ca0276..398622088 100644 --- a/python/gym_ignition/rbd/conversions.py +++ b/python/gym_ignition/rbd/conversions.py @@ -1,27 +1,30 @@ import abc -import numpy as np from typing import Tuple + +import numpy as np from scipy.spatial.transform import Rotation class Transform(abc.ABC): - @staticmethod - def from_position_and_quaternion(position: np.ndarray, - quaternion: np.ndarray) -> np.ndarray: + def from_position_and_quaternion( + position: np.ndarray, quaternion: np.ndarray + ) -> np.ndarray: if quaternion.size != 4: raise ValueError("Quaternion array must have 4 elements") rotation = Quaternion.to_rotation(quaternion) - transform = Transform.from_position_and_rotation(position=position, - rotation=rotation) + transform = Transform.from_position_and_rotation( + position=position, rotation=rotation + ) return transform @staticmethod - def from_position_and_rotation(position: np.ndarray, - rotation: np.ndarray) -> np.ndarray: + def from_position_and_rotation( + position: np.ndarray, rotation: np.ndarray + ) -> np.ndarray: if position.size != 3: raise ValueError("Position array must have 3 elements") @@ -36,7 +39,9 @@ def from_position_and_rotation(position: np.ndarray, return transform @staticmethod - def to_position_and_rotation(transform: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: + def to_position_and_rotation( + transform: np.ndarray, + ) -> Tuple[np.ndarray, np.ndarray]: if transform.shape != (4, 4): raise ValueError("Transform must be a 4x4 matrix") @@ -47,15 +52,15 @@ def to_position_and_rotation(transform: np.ndarray) -> Tuple[np.ndarray, np.ndar return position, rotation @staticmethod - def to_position_and_quaternion(transform: np.ndarray) \ - -> Tuple[np.ndarray, np.ndarray]: + def to_position_and_quaternion( + transform: np.ndarray, + ) -> Tuple[np.ndarray, np.ndarray]: p, R = Transform.to_position_and_rotation(transform=transform) return p, Quaternion.from_matrix(matrix=R) class Quaternion(abc.ABC): - @staticmethod def to_wxyz(xyzw: np.ndarray) -> np.ndarray: diff --git a/python/gym_ignition/rbd/idyntree/__init__.py b/python/gym_ignition/rbd/idyntree/__init__.py index 54580dfd8..a7fb07ea8 100644 --- a/python/gym_ignition/rbd/idyntree/__init__.py +++ b/python/gym_ignition/rbd/idyntree/__init__.py @@ -2,7 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import numpy -from . import helpers -from . import kindyncomputations -from . import inverse_kinematics_nlp +from . import helpers, inverse_kinematics_nlp, kindyncomputations, numpy diff --git a/python/gym_ignition/rbd/idyntree/helpers.py b/python/gym_ignition/rbd/idyntree/helpers.py index a7a06808b..58e1c1c25 100644 --- a/python/gym_ignition/rbd/idyntree/helpers.py +++ b/python/gym_ignition/rbd/idyntree/helpers.py @@ -2,10 +2,11 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -import os import abc -from typing import List +import os from enum import Enum, auto +from typing import List + import idyntree.bindings as idt from gym_ignition.utils import resource_finder @@ -22,14 +23,16 @@ def to_idyntree(self): return idt.MIXED_REPRESENTATION elif self.value == FrameVelocityRepresentation.BODY_FIXED_REPRESENTATION.value: return idt.BODY_FIXED_REPRESENTATION - elif self.value == FrameVelocityRepresentation.INERTIAL_FIXED_REPRESENTATION.value: + elif ( + self.value + == FrameVelocityRepresentation.INERTIAL_FIXED_REPRESENTATION.value + ): return idt.INERTIAL_FIXED_REPRESENTATION else: raise ValueError(self.value) class iDynTreeHelpers(abc.ABC): - @staticmethod def get_model_loader(model_file: str, considered_joints: List[str] = None): @@ -59,10 +62,10 @@ def get_model_loader(model_file: str, considered_joints: List[str] = None): @staticmethod def get_kindyncomputations( - model_file: str, - considered_joints: List[str] = None, - velocity_representation: FrameVelocityRepresentation = - FrameVelocityRepresentation.MIXED_REPRESENTATION): + model_file: str, + considered_joints: List[str] = None, + velocity_representation: FrameVelocityRepresentation = FrameVelocityRepresentation.MIXED_REPRESENTATION, + ): # Get the model loader model_loader = iDynTreeHelpers.get_model_loader(model_file, considered_joints) @@ -76,7 +79,9 @@ def get_kindyncomputations( # Configure the velocity representation velocity_representation_idyntree = velocity_representation.to_idyntree() - ok_repr = kindyn.setFrameVelocityRepresentation(velocity_representation_idyntree) + ok_repr = kindyn.setFrameVelocityRepresentation( + velocity_representation_idyntree + ) if not ok_repr: raise RuntimeError("Failed to set the velocity representation") diff --git a/python/gym_ignition/rbd/idyntree/inverse_kinematics_nlp.py b/python/gym_ignition/rbd/idyntree/inverse_kinematics_nlp.py index b7fda7c2b..13d90edc5 100644 --- a/python/gym_ignition/rbd/idyntree/inverse_kinematics_nlp.py +++ b/python/gym_ignition/rbd/idyntree/inverse_kinematics_nlp.py @@ -3,12 +3,13 @@ # GNU Lesser General Public License v2.1 or any later version. import os -import numpy as np +from dataclasses import dataclass from enum import Enum, auto -from gym_ignition import rbd +from typing import Dict, List, Optional, Tuple, Union + import idyntree.bindings as idt -from dataclasses import dataclass -from typing import List, Dict, Optional, Union +import numpy as np +from gym_ignition import rbd class TargetType(Enum): @@ -29,7 +30,7 @@ class TransformTargetData: class TargetData: type: TargetType - weight: float + weight: Union[float, Tuple[float, float]] data: Union[np.ndarray, TransformTargetData] @@ -84,11 +85,12 @@ def to_idyntree(self): class InverseKinematicsNLP: - - def __init__(self, - urdf_filename: str, - considered_joints: List[str] = None, - joint_serialization: List[str] = None) -> None: + def __init__( + self, + urdf_filename: str, + considered_joints: List[str] = None, + joint_serialization: List[str] = None, + ) -> None: self._floating_base: bool = False self._base_frame: Optional[str] = None @@ -103,17 +105,17 @@ def __init__(self, # INITIALIZATION METHODS # ====================== - def initialize(self, - rotation_parametrization: RotationParametrization = - RotationParametrization.ROLL_PITCH_YAW, - target_mode: TargetResolutionMode = - TargetResolutionMode.TARGET_AS_CONSTRAINT_NONE, - cost_tolerance: float = 1E-08, - constraints_tolerance: float = 1E-4, - max_iterations: int = 1000, - base_frame: str = None, - floating_base: bool = False, - verbosity: int = 1) -> None: + def initialize( + self, + rotation_parametrization: RotationParametrization = RotationParametrization.ROLL_PITCH_YAW, + target_mode: TargetResolutionMode = TargetResolutionMode.TARGET_AS_CONSTRAINT_NONE, + cost_tolerance: float = 1e-08, + constraints_tolerance: float = 1e-4, + max_iterations: int = 1000, + base_frame: str = None, + floating_base: bool = False, + verbosity: int = 1, + ) -> None: # Create the IK object self._ik = idt.InverseKinematics() @@ -121,8 +123,8 @@ def initialize(self, # Load the URDF model and get the model loader object. # We create the full model with all the joints specified in joint_serialization. model_loader: idt.ModelLoader = self._get_model_loader( - urdf=self._urdf_filename, - joint_serialization=self._joint_serialization) + urdf=self._urdf_filename, joint_serialization=self._joint_serialization + ) # Get the model model: idt.Model = model_loader.model() @@ -169,91 +171,120 @@ def initialize(self, if not self._floating_base: # Add a frame constraint for the base - self.add_frame_transform_constraint(frame_name=self._base_frame, - position=np.array([0.0, 0, 0.0]), - quaternion=np.array([1.0, 0, 0, 0])) - - def set_current_robot_configuration(self, - base_position: np.ndarray, - base_quaternion: np.ndarray, - joint_configuration: np.ndarray) -> None: + self.add_frame_transform_constraint( + frame_name=self._base_frame, + position=np.array([0.0, 0, 0.0]), + quaternion=np.array([1.0, 0, 0, 0]), + ) + + def set_current_robot_configuration( + self, + base_position: np.ndarray, + base_quaternion: np.ndarray, + joint_configuration: np.ndarray, + ) -> None: if joint_configuration.size != len(self._joint_serialization): raise ValueError(joint_configuration) H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform( - position=base_position, - quaternion=base_quaternion) + position=base_position, quaternion=base_quaternion + ) - q = rbd.idyntree.numpy.FromNumPy.to_idyntree_dyn_vector(array=joint_configuration) + q = rbd.idyntree.numpy.FromNumPy.to_idyntree_dyn_vector( + array=joint_configuration + ) if not self._ik.setCurrentRobotConfiguration(H, q): raise RuntimeError("Failed to set the current robot configuration") if not self._floating_base: - self.update_frame_transform_constraint(frame_name=self._base_frame, - position=base_position, - quaternion=base_quaternion) + self.update_frame_transform_constraint( + frame_name=self._base_frame, + position=base_position, + quaternion=base_quaternion, + ) - def set_current_joint_configuration(self, - joint_name: str, - configuration: float) -> None: + def set_current_joint_configuration( + self, joint_name: str, configuration: float + ) -> None: if joint_name not in self._joint_serialization: raise ValueError(joint_name) - if not self._ik.setJointConfiguration(jointName=joint_name, - jointConfiguration=configuration): - raise RuntimeError(f"Failed to set the configuration of joint '{joint_name}'") + if not self._ik.setJointConfiguration( + jointName=joint_name, jointConfiguration=configuration + ): + raise RuntimeError( + f"Failed to set the configuration of joint '{joint_name}'" + ) # ============== # TARGET METHODS # ============== - def add_target(self, - frame_name: str, - target_type: TargetType, - weight: float = 1.0, - as_constraint: bool = False) -> None: + def add_target( + self, + frame_name: str, + target_type: TargetType, + weight: Union[float, Tuple[float, float]] = None, + as_constraint: bool = False, + ) -> None: + + # Check the type of the 'weight' argument + float_target_types = {TargetType.ROTATION, TargetType.POSITION} + weight_type = float if target_type in float_target_types else tuple + + # Backward compatibility: if the target type is POSE and the weight is a float, + # we apply the same weight to both target components + if target_type is TargetType.POSE and isinstance(weight, float): + weight = (weight, weight) + + # Set the default weight if not specified + default_weight = 1.0 if target_type in float_target_types else (1.0, 1.0) + weight = weight if weight is not None else default_weight + + if not isinstance(weight, weight_type): + raise ValueError(f"The weight must be {weight_type} for this target") if target_type == TargetType.ROTATION: # Add the target - ok_target = self._ik.addRotationTarget(frame_name, - idt.Rotation.Identity(), - weight) + ok_target = self._ik.addRotationTarget( + frame_name, idt.Rotation.Identity(), weight + ) # Initialize the target data buffers - self._targets_data[frame_name] = TargetData(type=TargetType.ROTATION, - weight=weight, - data=np.array([1.0, 0, 0, 0])) + self._targets_data[frame_name] = TargetData( + type=TargetType.ROTATION, weight=weight, data=np.array([1.0, 0, 0, 0]) + ) elif target_type == TargetType.POSITION: # Add the target - ok_target = self._ik.addPositionTarget(frame_name, - idt.Position_Zero(), - weight) + ok_target = self._ik.addPositionTarget( + frame_name, idt.Position_Zero(), weight + ) # Initialize the target data buffers - self._targets_data[frame_name] = TargetData(type=TargetType.POSITION, - weight=weight, - data=np.array([0.0, 0, 0])) + self._targets_data[frame_name] = TargetData( + type=TargetType.POSITION, weight=weight, data=np.array([0.0, 0, 0]) + ) elif target_type == TargetType.POSE: # Add the target - ok_target = self._ik.addTarget(frame_name, - idt.Transform.Identity(), - weight, - weight) + ok_target = self._ik.addTarget( + frame_name, idt.Transform.Identity(), weight[0], weight[1] + ) # Create the transform target data - target_data = TransformTargetData(position=np.array([0.0, 0, 0]), - quaternion=np.array([1., 0, 0, 0])) + target_data = TransformTargetData( + position=np.array([0.0, 0, 0]), quaternion=np.array([1.0, 0, 0, 0]) + ) # Initialize the target data buffers - self._targets_data[frame_name] = TargetData(type=TargetType.POSE, - weight=weight, - data=target_data) + self._targets_data[frame_name] = TargetData( + type=TargetType.POSE, weight=weight, data=target_data + ) else: raise ValueError(target_type) @@ -273,10 +304,12 @@ def add_target(self, if not self._ik.setTargetResolutionMode(frame_name, constraint): raise RuntimeError(f"Failed to set target '{frame_name}' as constraint") - def add_com_target(self, - weight: float = 1.0, - as_constraint: bool = False, - constraint_tolerance: float = 1E-8) -> None: + def add_com_target( + self, + weight: float = 1.0, + as_constraint: bool = False, + constraint_tolerance: float = 1e-8, + ) -> None: # Add the target self._ik.setCOMTarget(idt.Position_Zero(), weight) @@ -287,14 +320,15 @@ def add_com_target(self, # Initialize the target data buffers assert "com" not in self._targets_data.keys() - self._targets_data["com"] = TargetData(type=TargetType.POSITION, - weight=weight, - data=np.array([0.0, 0, 0])) + self._targets_data["com"] = TargetData( + type=TargetType.POSITION, weight=weight, data=np.array([0.0, 0, 0]) + ) def update_position_target(self, target_name: str, position: np.ndarray) -> None: if target_name not in self.get_active_target_names( - target_type=TargetType.POSITION): + target_type=TargetType.POSITION + ): raise ValueError(f"Failed to find a position target '{target_name}'") @@ -309,14 +343,15 @@ def update_position_target(self, target_name: str, position: np.ndarray) -> None weight = self._targets_data[target_name].weight # Update the target data - self._targets_data[target_name] = TargetData(type=TargetType.POSITION, - weight=weight, - data=position) + self._targets_data[target_name] = TargetData( + type=TargetType.POSITION, weight=weight, data=position + ) def update_rotation_target(self, target_name: str, quaternion: np.ndarray) -> None: if target_name not in self.get_active_target_names( - target_type=TargetType.ROTATION): + target_type=TargetType.ROTATION + ): raise ValueError(f"Failed to find a rotation target '{target_name}'") @@ -331,23 +366,22 @@ def update_rotation_target(self, target_name: str, quaternion: np.ndarray) -> No weight = self._targets_data[target_name].weight # Update the target data - self._targets_data[target_name] = TargetData(type=TargetType.ROTATION, - weight=weight, - data=quaternion) + self._targets_data[target_name] = TargetData( + type=TargetType.ROTATION, weight=weight, data=quaternion + ) - def update_transform_target(self, - target_name: str, - position: np.ndarray, - quaternion: np.ndarray) -> None: + def update_transform_target( + self, target_name: str, position: np.ndarray, quaternion: np.ndarray + ) -> None: - if target_name not in self.get_active_target_names( - target_type=TargetType.POSE): + if target_name not in self.get_active_target_names(target_type=TargetType.POSE): raise ValueError(f"Failed to find a transform target '{target_name}'") # Create the iDynTree transform - H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform(position=position, - quaternion=quaternion) + H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform( + position=position, quaternion=quaternion + ) # Update the target inside IK if not self._ik.updateTarget(target_name, H): @@ -360,12 +394,11 @@ def update_transform_target(self, transform_data = TransformTargetData(position=position, quaternion=quaternion) # Update the target data - self._targets_data[target_name] = TargetData(type=TargetType.POSE, - weight=weight, - data=transform_data) + self._targets_data[target_name] = TargetData( + type=TargetType.POSE, weight=weight, data=transform_data + ) - def update_com_target(self, - position: np.ndarray) -> None: + def update_com_target(self, position: np.ndarray) -> None: if not self._ik.isCOMTargetActive(): raise RuntimeError("Constraint on CoM not active") @@ -377,9 +410,11 @@ def update_com_target(self, self._ik.setCOMTarget(p, self._targets_data["com"].weight) # Update the target data - self._targets_data["com"] = TargetData(type=TargetType.POSITION, - weight=self._targets_data["com"].weight, - data=position) + self._targets_data["com"] = TargetData( + type=TargetType.POSITION, + weight=self._targets_data["com"].weight, + data=position, + ) def deactivate_com_target(self) -> None: @@ -392,22 +427,22 @@ def deactivate_com_target(self) -> None: # FRAME METHODS # ============= - def add_frame_transform_constraint(self, - frame_name: str, - position: np.ndarray, - quaternion: np.ndarray) -> None: + def add_frame_transform_constraint( + self, frame_name: str, position: np.ndarray, quaternion: np.ndarray + ) -> None: # Create the transform - H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform(position=position, - quaternion=quaternion) + H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform( + position=position, quaternion=quaternion + ) # Add the target if not self._ik.addFrameConstraint(frame_name, H): raise RuntimeError(f"Failed to add constraint on frame '{frame_name}'") - def add_frame_position_constraint(self, - frame_name: str, - position: np.ndarray) -> None: + def add_frame_position_constraint( + self, frame_name: str, position: np.ndarray + ) -> None: # Create the position p = rbd.idyntree.numpy.FromNumPy.to_idyntree_position(position=position) @@ -416,9 +451,9 @@ def add_frame_position_constraint(self, if not self._ik.addFramePositionConstraint(frame_name, p): raise RuntimeError(f"Failed to add constraint on frame '{frame_name}'") - def add_frame_rotation_constraint(self, - frame_name: str, - quaternion: np.ndarray) -> None: + def add_frame_rotation_constraint( + self, frame_name: str, quaternion: np.ndarray + ) -> None: # Create the position R = rbd.idyntree.numpy.FromNumPy.to_idyntree_rotation(quaternion=quaternion) @@ -427,17 +462,17 @@ def add_frame_rotation_constraint(self, if not self._ik.addFrameRotationConstraint(frame_name, R): raise RuntimeError(f"Failed to add constraint on frame '{frame_name}'") - def update_frame_transform_constraint(self, - frame_name: str, - position: np.ndarray, - quaternion: np.ndarray) -> None: + def update_frame_transform_constraint( + self, frame_name: str, position: np.ndarray, quaternion: np.ndarray + ) -> None: if not self._ik.isFrameConstraintActive(frame_name): raise RuntimeError(f"Constraint on frame '{frame_name}' not active") # Create the transform - H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform(position=position, - quaternion=quaternion) + H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform( + position=position, quaternion=quaternion + ) if not self._ik.activateFrameConstraint(frame_name, H): raise RuntimeError(f"Failed to update constraint on frame '{frame_name}'") @@ -448,7 +483,9 @@ def deactivate_frame_constraint(self, frame_name: str) -> None: raise RuntimeError(f"Constraint on frame '{frame_name}' not active") if not self._ik.deactivateFrameConstraint(frame_name): - raise RuntimeError(f"Failed to deactivate constraint on frame '{frame_name}'") + raise RuntimeError( + f"Failed to deactivate constraint on frame '{frame_name}'" + ) # =================== # IK SOLUTION METHODS @@ -462,32 +499,43 @@ def solve(self) -> None: # Initialize next solver call self._warm_start_with_last_solution() - def warm_start_from(self, - full_solution: IKSolution = None, - reduced_solution: IKSolution = None) -> None: + def warm_start_from( + self, full_solution: IKSolution = None, reduced_solution: IKSolution = None + ) -> None: - if full_solution is None and reduced_solution is None or \ - full_solution is not None and reduced_solution is not None: + if ( + full_solution is None + and reduced_solution is None + or full_solution is not None + and reduced_solution is not None + ): raise ValueError("You have to specify either a full or a reduced solution") - if reduced_solution is not None and\ - reduced_solution.joint_configuration.size != len(self._considered_joints): + if ( + reduced_solution is not None + and reduced_solution.joint_configuration.size + != len(self._considered_joints) + ): raise RuntimeError( - "The joint configuration does not match the number of considered joints") + "The joint configuration does not match the number of considered joints" + ) - if full_solution is not None and \ - full_solution.joint_configuration.size != len(self._joint_serialization): + if full_solution is not None and full_solution.joint_configuration.size != len( + self._joint_serialization + ): raise RuntimeError( - "The joint configuration does not match the number of model joints") + "The joint configuration does not match the number of model joints" + ) solution = reduced_solution if reduced_solution is not None else full_solution H = rbd.idyntree.numpy.FromNumPy.to_idyntree_transform( - position=solution.base_position, - quaternion=solution.base_quaternion) + position=solution.base_position, quaternion=solution.base_quaternion + ) q = rbd.idyntree.numpy.FromNumPy.to_idyntree_dyn_vector( - array=solution.joint_configuration) + array=solution.joint_configuration + ) # Warm start the solver if not self._ik.setFullJointsInitialCondition(H, q): @@ -520,8 +568,11 @@ def get_active_target_names(self, target_type: TargetType = None) -> List[str]: return list(self._targets_data.keys()) else: - return [name for name, value in self._targets_data.items() - if value.type == target_type ] + return [ + name + for name, value in self._targets_data.items() + if value.type == target_type + ] def get_target_data(self, target_name: str) -> TargetData: @@ -542,9 +593,11 @@ def get_full_solution(self) -> IKSolution: base_position = base_transform.getPosition().toNumPy() base_quaternion = base_transform.getRotation().asQuaternion().toNumPy() - return IKSolution(base_position=base_position, - base_quaternion=base_quaternion, - joint_configuration=joint_positions) + return IKSolution( + base_position=base_position, + base_quaternion=base_quaternion, + joint_configuration=joint_positions, + ) def get_reduced_solution(self) -> IKSolution: @@ -561,18 +614,20 @@ def get_reduced_solution(self) -> IKSolution: base_position = base_transform.getPosition().toNumPy() base_quaternion = base_transform.getRotation().asQuaternion().toNumPy() - return IKSolution(base_position=base_position, - base_quaternion=base_quaternion, - joint_configuration=joint_positions) + return IKSolution( + base_position=base_position, + base_quaternion=base_quaternion, + joint_configuration=joint_positions, + ) # =============== # PRIVATE METHODS # =============== @staticmethod - def _get_model_loader(urdf: str, - joint_serialization: List[str] = None) \ - -> idt.ModelLoader: + def _get_model_loader( + urdf: str, joint_serialization: List[str] = None + ) -> idt.ModelLoader: # Get the model loader model_loader = idt.ModelLoader() diff --git a/python/gym_ignition/rbd/idyntree/kindyncomputations.py b/python/gym_ignition/rbd/idyntree/kindyncomputations.py index 1f04fd6e6..a20a0d1e2 100644 --- a/python/gym_ignition/rbd/idyntree/kindyncomputations.py +++ b/python/gym_ignition/rbd/idyntree/kindyncomputations.py @@ -2,29 +2,32 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -import numpy as np from typing import List, Tuple + import idyntree.bindings as idt -from scenario import core as scenario_core +import numpy as np from gym_ignition.rbd import conversions from gym_ignition.rbd.idyntree import numpy + +from scenario import core as scenario_core + from .helpers import FrameVelocityRepresentation, iDynTreeHelpers class KinDynComputations: - - def __init__(self, - model_file: str, - considered_joints: List[str] = None, - world_gravity: np.ndarray = np.array([0, 0, -9.806]), - velocity_representation: FrameVelocityRepresentation = - FrameVelocityRepresentation.MIXED_REPRESENTATION) -> None: + def __init__( + self, + model_file: str, + considered_joints: List[str] = None, + world_gravity: np.ndarray = np.array([0, 0, -9.806]), + velocity_representation: FrameVelocityRepresentation = FrameVelocityRepresentation.MIXED_REPRESENTATION, + ) -> None: self.velocity_representation = velocity_representation - self.kindyn = iDynTreeHelpers.get_kindyncomputations(model_file, - considered_joints, - velocity_representation) + self.kindyn = iDynTreeHelpers.get_kindyncomputations( + model_file, considered_joints, velocity_representation + ) self.world_gravity = np.array(world_gravity) self.dofs = self.kindyn.getNrOfDegreesOfFreedom() @@ -42,12 +45,14 @@ def joint_serialization(self) -> List[str]: return self._considered_joints - def set_robot_state(self, - s: np.ndarray, - ds: np.ndarray, - world_H_base: np.ndarray = np.eye(4), - base_velocity: np.ndarray = np.zeros(6), - world_gravity: np.ndarray = None) -> None: + def set_robot_state( + self, + s: np.ndarray, + ds: np.ndarray, + world_H_base: np.ndarray = np.eye(4), + base_velocity: np.ndarray = np.zeros(6), + world_gravity: np.ndarray = None, + ) -> None: gravity = world_gravity if world_gravity is not None else self.world_gravity @@ -69,28 +74,30 @@ def set_robot_state(self, s_idyntree = numpy.FromNumPy.to_idyntree_dyn_vector(array=s) ds_idyntree = numpy.FromNumPy.to_idyntree_dyn_vector(array=ds) - world_gravity_idyntree = \ - numpy.FromNumPy.to_idyntree_fixed_vector(array=gravity) + world_gravity_idyntree = numpy.FromNumPy.to_idyntree_fixed_vector(array=gravity) world_H_base_idyntree = numpy.FromNumPy.to_idyntree_transform( - position=world_H_base[0:3, 3], rotation=world_H_base[0:3, 0:3]) + position=world_H_base[0:3, 3], rotation=world_H_base[0:3, 0:3] + ) base_velocity_idyntree = numpy.FromNumPy.to_idyntree_twist( - linear_velocity=base_velocity[0:3], angular_velocity=base_velocity[3:6]) + linear_velocity=base_velocity[0:3], angular_velocity=base_velocity[3:6] + ) - ok_state = self.kindyn.setRobotState(world_H_base_idyntree, - s_idyntree, - base_velocity_idyntree, - ds_idyntree, - world_gravity_idyntree) + ok_state = self.kindyn.setRobotState( + world_H_base_idyntree, + s_idyntree, + base_velocity_idyntree, + ds_idyntree, + world_gravity_idyntree, + ) if not ok_state: raise RuntimeError("Failed to set the robot state") - def set_robot_state_from_model(self, - model: scenario_core.Model, - world_gravity: np.ndarray = None) \ - -> None: + def set_robot_state_from_model( + self, model: scenario_core.Model, world_gravity: np.ndarray = None + ) -> None: s = np.array(model.joint_positions(self.joint_serialization())) ds = np.array(model.joint_velocities(self.joint_serialization())) @@ -117,14 +124,17 @@ def set_robot_state_from_model(self, # Pack the data structures world_H_base = conversions.Transform.from_position_and_quaternion( - position=world_o_base, quaternion=world_quat_base) + position=world_o_base, quaternion=world_quat_base + ) base_velocity_6d = np.concatenate((base_linear_velocity, base_angular_velocity)) - self.set_robot_state(s=s, - ds=ds, - world_H_base=world_H_base, - base_velocity=base_velocity_6d, - world_gravity=world_gravity) + self.set_robot_state( + s=s, + ds=ds, + world_H_base=world_H_base, + base_velocity=base_velocity_6d, + world_gravity=world_gravity, + ) def get_floating_base(self) -> str: @@ -175,9 +185,9 @@ def get_world_transform(self, frame_name: str) -> np.ndarray: return numpy.ToNumPy.from_idyntree_transform(transform=H) - def get_relative_transform(self, - ref_frame_name: str, - frame_name: str) -> np.ndarray: + def get_relative_transform( + self, ref_frame_name: str, frame_name: str + ) -> np.ndarray: if self.kindyn.getFrameIndex(ref_frame_name) < 0: raise ValueError(f"Frame '{ref_frame_name}' does not exist") @@ -185,8 +195,9 @@ def get_relative_transform(self, if self.kindyn.getFrameIndex(frame_name) < 0: raise ValueError(f"Frame '{frame_name}' does not exist") - ref_H_other: idt.Transform = self.kindyn.getRelativeTransform(ref_frame_name, - frame_name) + ref_H_other: idt.Transform = self.kindyn.getRelativeTransform( + ref_frame_name, frame_name + ) return ref_H_other.asHomogeneousTransform().toNumPy() @@ -195,16 +206,20 @@ def get_world_base_transform(self) -> np.ndarray: W_H_B: idt.Transform = self.kindyn.getWorldBaseTransform() return W_H_B.asHomogeneousTransform().toNumPy() - def get_relative_transform_explicit(self, - ref_frame_origin: str, - ref_frame_orientation: str, - frame_origin: str, - frame_orientation: str) -> np.ndarray: - - for frame in {ref_frame_origin, - ref_frame_orientation, - frame_origin, - frame_orientation}: + def get_relative_transform_explicit( + self, + ref_frame_origin: str, + ref_frame_orientation: str, + frame_origin: str, + frame_orientation: str, + ) -> np.ndarray: + + for frame in { + ref_frame_origin, + ref_frame_orientation, + frame_origin, + frame_orientation, + }: if frame != "world" and self.kindyn.getFrameIndex(frame) < 0: raise ValueError(f"Frame '{frame}' does not exist") @@ -227,10 +242,8 @@ def get_relative_transform_explicit(self, frameD_index = self.kindyn.getFrameIndex(frameName=frameD) ref_H_other: idt.Transform = self.kindyn.getRelativeTransformExplicit( - frameA_index, - frameB_index, - frameC_index, - frameD_index) + frameA_index, frameB_index, frameC_index, frameD_index + ) AB_H_CD = ref_H_other @@ -250,22 +263,28 @@ def get_relative_transform_explicit(self, return AB_H_CD.asHomogeneousTransform().toNumPy() - def get_relative_adjoint_wrench_transform_explicit(self, - ref_frame_origin: str, - ref_frame_orientation: str, - frame_origin: str, - frame_orientation: str) \ - -> np.ndarray: + def get_relative_adjoint_wrench_transform_explicit( + self, + ref_frame_origin: str, + ref_frame_orientation: str, + frame_origin: str, + frame_orientation: str, + ) -> np.ndarray: AB_H_CD = self.get_relative_transform_explicit( ref_frame_origin=ref_frame_origin, ref_frame_orientation=ref_frame_orientation, frame_origin=frame_origin, - frame_orientation=frame_orientation) + frame_orientation=frame_orientation, + ) - return numpy.FromNumPy.to_idyntree_transform( - position=AB_H_CD[0:3, 3], rotation=AB_H_CD[0:3, 0:3]) \ - .asAdjointTransformWrench().toNumPy() + return ( + numpy.FromNumPy.to_idyntree_transform( + position=AB_H_CD[0:3, 3], rotation=AB_H_CD[0:3, 0:3] + ) + .asAdjointTransformWrench() + .toNumPy() + ) def get_mass_matrix(self) -> np.ndarray: @@ -286,8 +305,9 @@ def get_generalized_gravity_forces(self) -> np.ndarray: base_wrench: idt.Wrench = g.baseWrench() joint_torques: idt.JointDOFsDoubleArray = g.jointTorques() - return np.concatenate([base_wrench.toNumPy().flatten(), - joint_torques.toNumPy().flatten()]) + return np.concatenate( + [base_wrench.toNumPy().flatten(), joint_torques.toNumPy().flatten()] + ) def get_bias_forces(self) -> np.ndarray: @@ -299,8 +319,9 @@ def get_bias_forces(self) -> np.ndarray: base_wrench: idt.Wrench = h.baseWrench() joint_torques: idt.JointDOFsDoubleArray = h.jointTorques() - return np.concatenate([base_wrench.toNumPy().flatten(), - joint_torques.toNumPy().flatten()]) + return np.concatenate( + [base_wrench.toNumPy().flatten(), joint_torques.toNumPy().flatten()] + ) def get_momentum(self) -> Tuple[np.ndarray, np.ndarray]: @@ -340,7 +361,9 @@ def get_com_velocity(self) -> np.ndarray: # Get the transform of the base frame W_H_B = self.kindyn.getWorldBaseTransform() - _, W_R_B = numpy.ToNumPy.from_idyntree_transform(transform=W_H_B, split=True) + _, W_R_B = numpy.ToNumPy.from_idyntree_transform( + transform=W_H_B, split=True + ) # Get the rotation between world and base frame B_R_W = np.linalg.inv(W_R_B) diff --git a/python/gym_ignition/rbd/idyntree/numpy.py b/python/gym_ignition/rbd/idyntree/numpy.py index 5b80d93e8..8a7268997 100644 --- a/python/gym_ignition/rbd/idyntree/numpy.py +++ b/python/gym_ignition/rbd/idyntree/numpy.py @@ -1,12 +1,12 @@ import abc -import numpy as np -from gym_ignition import rbd from typing import Tuple, Union + import idyntree.bindings as idt +import numpy as np +from gym_ignition import rbd class FromNumPy(abc.ABC): - @staticmethod def to_idyntree_dyn_vector(array: np.ndarray) -> idt.VectorDynSize: @@ -54,9 +54,9 @@ def to_idyntree_rotation(quaternion: np.ndarray) -> idt.Rotation: return R @staticmethod - def to_idyntree_transform(position: np.ndarray, - quaternion: np.ndarray = None, - rotation: np.ndarray = None) -> idt.Transform: + def to_idyntree_transform( + position: np.ndarray, quaternion: np.ndarray = None, rotation: np.ndarray = None + ) -> idt.Transform: if quaternion is None and rotation is None: raise ValueError("You must pass either a quaternion or a rotation") @@ -77,8 +77,9 @@ def to_idyntree_transform(position: np.ndarray, return H @staticmethod - def to_idyntree_twist(linear_velocity: np.ndarray, - angular_velocity: np.ndarray) -> idt.Twist: + def to_idyntree_twist( + linear_velocity: np.ndarray, angular_velocity: np.ndarray + ) -> idt.Twist: if linear_velocity.size != 3: raise ValueError("The linear velocity must have 3 elements") @@ -94,7 +95,6 @@ def to_idyntree_twist(linear_velocity: np.ndarray, class ToNumPy(abc.ABC): - @staticmethod def from_idyntree_vector(vector) -> np.ndarray: @@ -115,8 +115,9 @@ def from_idyntree_vector(vector) -> np.ndarray: return np.array(vector.toNumPy()) @staticmethod - def from_idyntree_transform(transform: idt.Transform, split: bool = False) \ - -> Union[Tuple[np.ndarray, np.ndarray], np.ndarray]: + def from_idyntree_transform( + transform: idt.Transform, split: bool = False + ) -> Union[Tuple[np.ndarray, np.ndarray], np.ndarray]: if not isinstance(transform, idt.Transform): raise ValueError(transform) diff --git a/python/gym_ignition/rbd/utils.py b/python/gym_ignition/rbd/utils.py index e58a379be..88b9559da 100644 --- a/python/gym_ignition/rbd/utils.py +++ b/python/gym_ignition/rbd/utils.py @@ -53,9 +53,7 @@ def vee(matrix3x3: np.ndarray) -> np.ndarray: skew_symmetric = extract_skew(matrix3x3) - return np.array([skew_symmetric[2, 1], - skew_symmetric[0, 2], - skew_symmetric[1, 0]]) + return np.array([skew_symmetric[2, 1], skew_symmetric[0, 2], skew_symmetric[1, 0]]) def extract_skew(matrix: np.ndarray) -> np.ndarray: diff --git a/python/gym_ignition/runtimes/__init__.py b/python/gym_ignition/runtimes/__init__.py index 7c1c57004..05e6a8150 100644 --- a/python/gym_ignition/runtimes/__init__.py +++ b/python/gym_ignition/runtimes/__init__.py @@ -2,5 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import gazebo_runtime -from . import realtime_runtime +from . import gazebo_runtime, realtime_runtime diff --git a/python/gym_ignition/runtimes/gazebo_runtime.py b/python/gym_ignition/runtimes/gazebo_runtime.py index c460d908d..25335876b 100644 --- a/python/gym_ignition/runtimes/gazebo_runtime.py +++ b/python/gym_ignition/runtimes/gazebo_runtime.py @@ -2,11 +2,14 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. +from typing import Optional + import gym_ignition_models -from gym_ignition import base, utils +from gym_ignition import base from gym_ignition.base import runtime from gym_ignition.utils import logger from gym_ignition.utils.typing import * + from scenario import gazebo as scenario @@ -30,19 +33,28 @@ class GazeboRuntime(runtime.Runtime): physics, a new simulator should be created. """ - metadata = {'render.modes': ['human']} + metadata = {"render.modes": ["human"]} + + def __init__( + self, + task_cls: type, + agent_rate: float, + physics_rate: float, + real_time_factor: float, + physics_engine=scenario.PhysicsEngine_dart, + world: str = None, + **kwargs, + ): - def __init__(self, - task_cls: type, - agent_rate: float, - physics_rate: float, - real_time_factor: float, - physics_engine = scenario.PhysicsEngine_dart, - world: str = None, - **kwargs): + if gym.logger.MIN_LEVEL <= gym.logger.DEBUG: + import inspect + + frame = inspect.currentframe() + args, _, _, values = inspect.getargvalues(frame) + gym.logger.debug(f"{dict({arg: values[arg] for arg in args})}") # Gazebo attributes - self._gazebo = None + self._gazebo: Optional[scenario.GazeboSimulator] = None self._physics_rate = physics_rate self._real_time_factor = real_time_factor @@ -139,9 +151,9 @@ def reset(self) -> Observation: return Observation(observation) - def render(self, mode: str = 'human', **kwargs) -> None: + def render(self, mode: str = "human", **kwargs) -> None: - if mode != 'human': + if mode != "human": raise ValueError(f"Render mode '{mode}' not supported") gui_ok = self.gazebo.gui() @@ -185,13 +197,16 @@ def gazebo(self) -> scenario.GazeboSimulator: num_of_steps_per_run = self._physics_rate / self.agent_rate if num_of_steps_per_run != int(num_of_steps_per_run): - logger.warn("Rounding the number of iterations to {} from the nominal {}" - .format(int(num_of_steps_per_run), num_of_steps_per_run)) + logger.warn( + "Rounding the number of iterations to {} from the nominal {}".format( + int(num_of_steps_per_run), num_of_steps_per_run + ) + ) # Create the simulator - gazebo = scenario.GazeboSimulator(1.0 / self._physics_rate, - self._real_time_factor, - int(num_of_steps_per_run)) + gazebo = scenario.GazeboSimulator( + 1.0 / self._physics_rate, self._real_time_factor, int(num_of_steps_per_run) + ) # Store the simulator self._gazebo = gazebo @@ -212,18 +227,14 @@ def world(self) -> scenario.World: if self._gazebo is None: raise RuntimeError("Gazebo has not yet been created") - # Help type hinting - self._gazebo: scenario.GazeboSimulator - if self._gazebo.initialized(): raise RuntimeError("Gazebo was already initialized, cannot insert world") if self._world_sdf is None: self._world_sdf = "" - self._world_name = utils.scenario.get_unique_world_name("default") + self._world_name = "default" else: - sdf_world_name = scenario.get_world_name_from_sdf(self._world_sdf) - self._world_name = utils.scenario.get_unique_world_name(sdf_world_name) + self._world_name = scenario.get_world_name_from_sdf(self._world_sdf) # Load the world ok_world = self._gazebo.insert_world_from_sdf(self._world_sdf, self._world_name) @@ -250,7 +261,8 @@ def world(self) -> scenario.World: # Insert the ground plane ok_ground = world.insert_model( - gym_ignition_models.get_model_file("ground_plane")) + gym_ignition_models.get_model_file("ground_plane") + ) if not ok_ground: raise RuntimeError("Failed to insert the ground plane") diff --git a/python/gym_ignition/runtimes/realtime_runtime.py b/python/gym_ignition/runtimes/realtime_runtime.py index 82a04a2ac..5a6189ad4 100644 --- a/python/gym_ignition/runtimes/realtime_runtime.py +++ b/python/gym_ignition/runtimes/realtime_runtime.py @@ -3,29 +3,26 @@ # GNU Lesser General Public License v2.1 or any later version. from gym_ignition.base import runtime, task -from gym_ignition.utils.typing import State, Action, Observation, Done, Info +from gym_ignition.utils.typing import Action, Done, Info, Observation, State class RealTimeRuntime(runtime.Runtime): """ - Implementation of :py:class:`~gym_ignition.base.runtime.Runtime` for real-time - execution. + Implementation of :py:class:`~gym_ignition.base.runtime.Runtime` for real-time + execution. - Warning: - This class is not yet complete. + Warning: + This class is not yet complete. """ - def __init__(self, - task_cls: type, - robot_cls: type, - agent_rate: float, - **kwargs): + def __init__(self, task_cls: type, robot_cls: type, agent_rate: float, **kwargs): # Build the environment task_object = task_cls(**kwargs) - assert isinstance(task_object, task.Task), \ - "'task_cls' object must inherit from Task" + assert isinstance( + task_object, task.Task + ), "'task_cls' object must inherit from Task" super().__init__(task=task_object, agent_rate=agent_rate) @@ -46,8 +43,10 @@ def timestamp(self) -> float: def step(self, action: Action) -> State: # Validate action and robot - assert self.action_space.contains(action), \ - "%r (%s) invalid" % (action, type(action)) + assert self.action_space.contains(action), "%r (%s) invalid" % ( + action, + type(action), + ) # Set the action ok_action = self.task.set_action(action) @@ -57,8 +56,10 @@ def step(self, action: Action) -> State: # Get the observation observation = self.task.get_observation() - assert self.observation_space.contains(observation), \ - "%r (%s) invalid" % (observation, type(observation)) + assert self.observation_space.contains(observation), "%r (%s) invalid" % ( + observation, + type(observation), + ) # Get the reward reward = self.task.get_reward() @@ -76,7 +77,7 @@ def reset(self) -> Observation: return Observation(observation) - def render(self, mode: str = 'human', **kwargs) -> None: + def render(self, mode: str = "human", **kwargs) -> None: raise NotImplementedError def close(self) -> None: diff --git a/python/gym_ignition/scenario/__init__.py b/python/gym_ignition/scenario/__init__.py index 05c190634..d7e9b69cf 100644 --- a/python/gym_ignition/scenario/__init__.py +++ b/python/gym_ignition/scenario/__init__.py @@ -2,5 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import model_wrapper -from . import model_with_file +from . import model_with_file, model_wrapper diff --git a/python/gym_ignition/scenario/model_with_file.py b/python/gym_ignition/scenario/model_with_file.py index d64df1e48..24c47174e 100644 --- a/python/gym_ignition/scenario/model_with_file.py +++ b/python/gym_ignition/scenario/model_with_file.py @@ -6,7 +6,6 @@ class ModelWithFile(abc.ABC): - def __init__(self): super().__init__() diff --git a/python/gym_ignition/scenario/model_wrapper.py b/python/gym_ignition/scenario/model_wrapper.py index c2fe5fdf5..8adb8d151 100644 --- a/python/gym_ignition/scenario/model_wrapper.py +++ b/python/gym_ignition/scenario/model_wrapper.py @@ -3,11 +3,11 @@ # GNU Lesser General Public License v2.1 or any later version. import abc + from scenario import core as scenario class ModelWrapper(scenario.Model, abc.ABC): - def __init__(self, model: scenario.Model): # No need to call scenario.Model.__init__()! diff --git a/python/gym_ignition/utils/__init__.py b/python/gym_ignition/utils/__init__.py index f77751855..685a9010a 100644 --- a/python/gym_ignition/utils/__init__.py +++ b/python/gym_ignition/utils/__init__.py @@ -2,9 +2,5 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import math -from . import misc +from . import logger, math, misc, resource_finder, scenario from .typing import * -from . import logger -from . import scenario -from . import resource_finder diff --git a/python/gym_ignition/utils/logger.py b/python/gym_ignition/utils/logger.py index e23406e2b..ca20a4e18 100644 --- a/python/gym_ignition/utils/logger.py +++ b/python/gym_ignition/utils/logger.py @@ -2,12 +2,13 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -import gym -import warnings import contextlib +import warnings + +import gym from gym import logger +from gym.logger import debug, error, info from gym.utils import colorize -from gym.logger import debug, info, error def custom_formatwarning(msg, *args, **kwargs): @@ -29,14 +30,14 @@ def warn(msg: str, *args) -> None: """ if logger.MIN_LEVEL <= logger.WARN: - warnings.warn(colorize('%s: %s' % ('WARN', msg % args), 'yellow'), stacklevel=2) + warnings.warn(colorize("%s: %s" % ("WARN", msg % args), "yellow"), stacklevel=2) # Monkey patch warning formatting warnings.formatwarning = custom_formatwarning -def set_level(level: int) -> None: +def set_level(level: int, scenario_level: int = None) -> None: """ Set the verbosity level of both :py:mod:`gym` and :py:mod:`gym_ignition`. @@ -50,6 +51,7 @@ def set_level(level: int) -> None: Args: level: The desired verbosity level. + scenario_level: The desired ScenarIO verbosity level (defaults to ``level``). """ # Set the gym verbosity @@ -60,14 +62,17 @@ def set_level(level: int) -> None: except ImportError: return + if scenario_level is None: + scenario_level = level + # Set the ScenarI/O verbosity - if logger.MIN_LEVEL <= logger.DEBUG: + if scenario_level <= logger.DEBUG: scenario.set_verbosity(scenario.Verbosity_debug) - elif logger.MIN_LEVEL <= logger.INFO: + elif scenario_level <= logger.INFO: scenario.set_verbosity(scenario.Verbosity_info) - elif logger.MIN_LEVEL <= logger.WARN: + elif scenario_level <= logger.WARN: scenario.set_verbosity(scenario.Verbosity_warning) - elif logger.MIN_LEVEL <= logger.ERROR: + elif scenario_level <= logger.ERROR: scenario.set_verbosity(scenario.Verbosity_error) else: scenario.set_verbosity(scenario.Verbosity_suppress_all) diff --git a/python/gym_ignition/utils/math.py b/python/gym_ignition/utils/math.py index 625e1ec5c..8bd018f0c 100644 --- a/python/gym_ignition/utils/math.py +++ b/python/gym_ignition/utils/math.py @@ -2,15 +2,19 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -import numpy as np from numbers import Number -from typing import Union, List +from typing import List, Union + +import numpy as np + from scenario import gazebo as scenario -def normalize(input: Union[Number, List[Number], np.ndarray], - low: Union[Number, List[Number], np.ndarray], - high: Union[Number, List[Number], np.ndarray]) -> Union[Number, np.ndarray]: +def normalize( + input: Union[Number, List[Number], np.ndarray], + low: Union[Number, List[Number], np.ndarray], + high: Union[Number, List[Number], np.ndarray], +) -> Union[Number, np.ndarray]: if low is None or high is None: return input @@ -32,10 +36,11 @@ def normalize(input: Union[Number, List[Number], np.ndarray], return np.array(output) -def denormalize(input: Union[Number, List[Number], np.ndarray], - low: Union[Number, List[Number], np.ndarray], - high: Union[Number, List[Number], np.ndarray]) \ - -> Union[Number, np.ndarray]: +def denormalize( + input: Union[Number, List[Number], np.ndarray], + low: Union[Number, List[Number], np.ndarray], + high: Union[Number, List[Number], np.ndarray], +) -> Union[Number, np.ndarray]: if low is None or high is None: return input diff --git a/python/gym_ignition/utils/misc.py b/python/gym_ignition/utils/misc.py index 1a53dbd1a..c5d361106 100644 --- a/python/gym_ignition/utils/misc.py +++ b/python/gym_ignition/utils/misc.py @@ -9,7 +9,7 @@ def string_to_file(string: str) -> str: handle, tmpfile = tempfile.mkstemp() - with open(handle, 'w') as f: + with open(handle, "w") as f: f.write(string) return tmpfile diff --git a/python/gym_ignition/utils/resource_finder.py b/python/gym_ignition/utils/resource_finder.py index 88ef5a1ba..5d507a844 100644 --- a/python/gym_ignition/utils/resource_finder.py +++ b/python/gym_ignition/utils/resource_finder.py @@ -3,8 +3,9 @@ # GNU Lesser General Public License v2.1 or any later version. import os -from typing import List from os.path import exists, isfile +from typing import List + from gym_ignition.utils import logger GYM_IGNITION_DATA_PATH = [] @@ -17,7 +18,9 @@ def get_search_paths() -> List[str]: def add_path(data_path: str) -> None: if not exists(data_path): - logger.warn(f"The path '{data_path}' does not exist. Not added to the data path.") + logger.warn( + f"The path '{data_path}' does not exist. Not added to the data path." + ) return global GYM_IGNITION_DATA_PATH @@ -40,7 +43,7 @@ def add_path_from_env_var(env_variable: str) -> None: env_var_content = os.environ[env_variable] # Remove leading ':' characters - if env_var_content[0] == ':': + if env_var_content[0] == ":": env_var_content = env_var_content[1:] # Split multiple value @@ -67,7 +70,7 @@ def find_resource(file_name: str) -> str: # Handle if the path is relative for path in GYM_IGNITION_DATA_PATH: logger.debug(f" Exploring folder '{path}'") - path_with_slash = path if path[-1] == '/' else path + "/" + path_with_slash = path if path[-1] == "/" else path + "/" candidate_abs_path = path_with_slash + file_name if isfile(candidate_abs_path): diff --git a/python/gym_ignition/utils/scenario.py b/python/gym_ignition/utils/scenario.py index dc0a820ae..3c6797762 100644 --- a/python/gym_ignition/utils/scenario.py +++ b/python/gym_ignition/utils/scenario.py @@ -2,11 +2,13 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. +from typing import List, Tuple, Union + import gym.spaces +import gym_ignition_models import numpy as np + from scenario import core -import gym_ignition_models -from typing import List, Tuple, Union from scenario import gazebo as scenario @@ -44,23 +46,9 @@ def get_unique_model_name(world: scenario.World, model_name: str) -> str: return model_name_tentative -def get_unique_world_name(world_name: str) -> str: - - postfix = 0 - world_name_tentative = f"{world_name}" - ecm_singleton = scenario.ECMSingleton_instance() - - while world_name_tentative in ecm_singleton.world_names(): - postfix += 1 - world_name_tentative = f"{world_name}{postfix}" - - return world_name_tentative - - -def init_gazebo_sim(step_size: float = 0.001, - real_time_factor: float = 1.0, - steps_per_run: int = 1) -> Tuple[scenario.GazeboSimulator, - Union[scenario.World, core.World]]: +def init_gazebo_sim( + step_size: float = 0.001, real_time_factor: float = 1.0, steps_per_run: int = 1 +) -> Tuple[scenario.GazeboSimulator, Union[scenario.World, core.World]]: """ Initialize a Gazebo simulation with an empty world and default physics. @@ -103,8 +91,9 @@ def init_gazebo_sim(step_size: float = 0.001, return gazebo, world -def get_joint_positions_space(model: scenario.Model, - considered_joints: List[str] = None) -> gym.spaces.Box: +def get_joint_positions_space( + model: scenario.Model, considered_joints: List[str] = None +) -> gym.spaces.Box: """ Build a Box space from the joint position limits. @@ -124,7 +113,8 @@ def get_joint_positions_space(model: scenario.Model, joint_limits = model.joint_limits(considered_joints) # Build the space - space = gym.spaces.Box(low=np.array(joint_limits.min), - high=np.array(joint_limits.max)) + space = gym.spaces.Box( + low=np.array(joint_limits.min), high=np.array(joint_limits.max) + ) return space diff --git a/python/gym_ignition/utils/typing.py b/python/gym_ignition/utils/typing.py index ad6a34661..5b25627da 100644 --- a/python/gym_ignition/utils/typing.py +++ b/python/gym_ignition/utils/typing.py @@ -2,19 +2,20 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. +from typing import Dict, List, NewType, Tuple, Union + import gym.spaces import numpy as np -from typing import List, Tuple, Dict, Union, NewType -Done = NewType('Done', bool) -Info = NewType('Info', Dict) -Reward = NewType('Reward', float) -Observation = NewType('Observation', np.ndarray) -Action = NewType('Action', Union[np.ndarray, np.number]) +Done = NewType("Done", bool) +Info = NewType("Info", Dict) +Reward = NewType("Reward", float) +Observation = NewType("Observation", np.ndarray) +Action = NewType("Action", Union[np.ndarray, np.number]) -SeedList = NewType('SeedList', List[int]) +SeedList = NewType("SeedList", List[int]) -State = NewType('State', Tuple[Observation, Reward, Done, Info]) +State = NewType("State", Tuple[Observation, Reward, Done, Info]) -ActionSpace = NewType('ActionSpace', gym.spaces.Space) -ObservationSpace = NewType('ObservationSpace', gym.spaces.Space) +ActionSpace = NewType("ActionSpace", gym.spaces.Space) +ObservationSpace = NewType("ObservationSpace", gym.spaces.Space) diff --git a/python/gym_ignition_environments/__init__.py b/python/gym_ignition_environments/__init__.py index 0fd3eb264..35228f42b 100644 --- a/python/gym_ignition_environments/__init__.py +++ b/python/gym_ignition_environments/__init__.py @@ -3,50 +3,56 @@ # GNU Lesser General Public License v2.1 or any later version. import numpy -from . import tasks -from . import models -from . import randomizers from gym.envs.registration import register +from . import models, randomizers, tasks max_float = float(numpy.finfo(numpy.float32).max) register( - id='Pendulum-Gazebo-v0', - entry_point='gym_ignition.runtimes.gazebo_runtime:GazeboRuntime', + id="Pendulum-Gazebo-v0", + entry_point="gym_ignition.runtimes.gazebo_runtime:GazeboRuntime", max_episode_steps=5000, - kwargs={'task_cls': tasks.pendulum_swingup.PendulumSwingUp, - 'agent_rate': 1000, - 'physics_rate': 1000, - 'real_time_factor': max_float, - }) + kwargs={ + "task_cls": tasks.pendulum_swingup.PendulumSwingUp, + "agent_rate": 1000, + "physics_rate": 1000, + "real_time_factor": max_float, + }, +) register( - id='CartPoleDiscreteBalancing-Gazebo-v0', - entry_point='gym_ignition.runtimes.gazebo_runtime:GazeboRuntime', + id="CartPoleDiscreteBalancing-Gazebo-v0", + entry_point="gym_ignition.runtimes.gazebo_runtime:GazeboRuntime", max_episode_steps=5000, - kwargs={'task_cls': tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing, - 'agent_rate': 1000, - 'physics_rate': 1000, - 'real_time_factor': max_float, - }) + kwargs={ + "task_cls": tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing, + "agent_rate": 1000, + "physics_rate": 1000, + "real_time_factor": max_float, + }, +) register( - id='CartPoleContinuousBalancing-Gazebo-v0', - entry_point='gym_ignition.runtimes.gazebo_runtime:GazeboRuntime', + id="CartPoleContinuousBalancing-Gazebo-v0", + entry_point="gym_ignition.runtimes.gazebo_runtime:GazeboRuntime", max_episode_steps=5000, - kwargs={'task_cls': tasks.cartpole_continuous_balancing.CartPoleContinuousBalancing, - 'agent_rate': 1000, - 'physics_rate': 1000, - 'real_time_factor': max_float, - }) + kwargs={ + "task_cls": tasks.cartpole_continuous_balancing.CartPoleContinuousBalancing, + "agent_rate": 1000, + "physics_rate": 1000, + "real_time_factor": max_float, + }, +) register( - id='CartPoleContinuousSwingup-Gazebo-v0', - entry_point='gym_ignition.runtimes.gazebo_runtime:GazeboRuntime', + id="CartPoleContinuousSwingup-Gazebo-v0", + entry_point="gym_ignition.runtimes.gazebo_runtime:GazeboRuntime", max_episode_steps=5000, - kwargs={'task_cls': tasks.cartpole_continuous_swingup.CartPoleContinuousSwingup, - 'agent_rate': 1000, - 'physics_rate': 1000, - 'real_time_factor': max_float, - }) + kwargs={ + "task_cls": tasks.cartpole_continuous_swingup.CartPoleContinuousSwingup, + "agent_rate": 1000, + "physics_rate": 1000, + "real_time_factor": max_float, + }, +) diff --git a/python/gym_ignition_environments/models/__init__.py b/python/gym_ignition_environments/models/__init__.py index 45560d4f6..0fb08e192 100644 --- a/python/gym_ignition_environments/models/__init__.py +++ b/python/gym_ignition_environments/models/__init__.py @@ -2,7 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import icub -from . import panda -from . import cartpole -from . import pendulum +from . import cartpole, icub, panda, pendulum diff --git a/python/gym_ignition_environments/models/cartpole.py b/python/gym_ignition_environments/models/cartpole.py index da59aa457..9c1746d4b 100644 --- a/python/gym_ignition_environments/models/cartpole.py +++ b/python/gym_ignition_environments/models/cartpole.py @@ -3,19 +3,21 @@ # GNU Lesser General Public License v2.1 or any later version. from typing import List -from scenario import core as scenario + +from gym_ignition.scenario import model_with_file, model_wrapper from gym_ignition.utils.scenario import get_unique_model_name -from gym_ignition.scenario import model_wrapper, model_with_file +from scenario import core as scenario -class CartPole(model_wrapper.ModelWrapper, - model_with_file.ModelWithFile): - def __init__(self, - world: scenario.World, - position: List[float] = (0.0, 0.0, 0.0), - orientation: List[float] = (1.0, 0, 0, 0), - model_file: str = None): +class CartPole(model_wrapper.ModelWrapper, model_with_file.ModelWithFile): + def __init__( + self, + world: scenario.World, + position: List[float] = (0.0, 0.0, 0.0), + orientation: List[float] = (1.0, 0, 0, 0), + model_file: str = None, + ): # Get a unique model name model_name = get_unique_model_name(world, "cartpole") @@ -28,9 +30,7 @@ def __init__(self, model_file = CartPole.get_model_file() # Insert the model - ok_model = world.to_gazebo().insert_model(model_file, - initial_pose, - model_name) + ok_model = world.to_gazebo().insert_model(model_file, initial_pose, model_name) if not ok_model: raise RuntimeError("Failed to insert model") @@ -45,4 +45,5 @@ def __init__(self, def get_model_file(cls) -> str: import gym_ignition_models + return gym_ignition_models.get_model_file("cartpole") diff --git a/python/gym_ignition_environments/models/icub.py b/python/gym_ignition_environments/models/icub.py index a48e58c1e..b59284fbd 100644 --- a/python/gym_ignition_environments/models/icub.py +++ b/python/gym_ignition_environments/models/icub.py @@ -4,13 +4,16 @@ import abc from typing import List + from gym_ignition import scenario -from scenario import core as scenario_core from gym_ignition.utils.scenario import get_unique_model_name +from scenario import core as scenario_core -class ICubGazeboABC(scenario.model_wrapper.ModelWrapper, - abc.ABC): + +class ICubGazeboABC( + scenario.model_wrapper.ModelWrapper, scenario.model_with_file.ModelWithFile, abc.ABC +): DOFS = 32 NUM_LINKS = 39 @@ -18,32 +21,52 @@ class ICubGazeboABC(scenario.model_wrapper.ModelWrapper, initial_positions = { # Left leg - 'l_knee': -1.05, - 'l_ankle_pitch': -0.57, 'l_ankle_roll': -0.024, - 'l_hip_pitch': 0.48, 'l_hip_roll': 0.023, 'l_hip_yaw': -0.005, + "l_knee": -1.05, + "l_ankle_pitch": -0.57, + "l_ankle_roll": -0.024, + "l_hip_pitch": 0.48, + "l_hip_roll": 0.023, + "l_hip_yaw": -0.005, # Left arm - 'l_elbow': 0.54, - 'l_wrist_pitch': 0.0, 'l_wrist_prosup': 0.0, 'l_wrist_yaw': 0.0, - 'l_shoulder_pitch': -0.159, 'l_shoulder_roll': 0.435, 'l_shoulder_yaw': 0.183, + "l_elbow": 0.54, + "l_wrist_pitch": 0.0, + "l_wrist_prosup": 0.0, + "l_wrist_yaw": 0.0, + "l_shoulder_pitch": -0.159, + "l_shoulder_roll": 0.435, + "l_shoulder_yaw": 0.183, # Head - 'neck_pitch': 0.0, 'neck_roll': 0.0, 'neck_yaw': 0.0, + "neck_pitch": 0.0, + "neck_roll": 0.0, + "neck_yaw": 0.0, # Right leg - 'r_knee': -1.05, - 'r_ankle_pitch': -0.57, 'r_ankle_roll': -0.024, - 'r_hip_pitch': 0.48, 'r_hip_roll': 0.023, 'r_hip_yaw': -0.005, + "r_knee": -1.05, + "r_ankle_pitch": -0.57, + "r_ankle_roll": -0.024, + "r_hip_pitch": 0.48, + "r_hip_roll": 0.023, + "r_hip_yaw": -0.005, # Right arm - 'r_elbow': 0.54, - 'r_wrist_pitch': 0.0, 'r_wrist_prosup': 0.0, 'r_wrist_yaw': 0.0, - 'r_shoulder_pitch': -0.159, 'r_shoulder_roll': 0.435, 'r_shoulder_yaw': 0.183, + "r_elbow": 0.54, + "r_wrist_pitch": 0.0, + "r_wrist_prosup": 0.0, + "r_wrist_yaw": 0.0, + "r_shoulder_pitch": -0.159, + "r_shoulder_roll": 0.435, + "r_shoulder_yaw": 0.183, # Torso - 'torso_pitch': 0.1, 'torso_roll': 0.0, 'torso_yaw': 0.0, + "torso_pitch": 0.1, + "torso_roll": 0.0, + "torso_yaw": 0.0, } - def __init__(self, - world: scenario_core.World, - position: List[float], - orientation: List[float], - model_file: str = None): + def __init__( + self, + world: scenario_core.World, + position: List[float], + orientation: List[float], + model_file: str = None, + ): # Get a unique model name model_name = get_unique_model_name(world, "icub") @@ -56,9 +79,7 @@ def __init__(self, model_file = self.get_model_file() # Insert the model - ok_model = world.to_gazebo().insert_model(model_file, - initial_pose, - model_name) + ok_model = world.to_gazebo().insert_model(model_file, initial_pose, model_name) if not ok_model: raise RuntimeError("Failed to insert model") @@ -78,42 +99,49 @@ def __init__(self, assert ok_q0, "Failed to set initial position" -class ICubGazebo(ICubGazeboABC, - scenario.model_with_file.ModelWithFile): - - def __init__(self, - world: scenario_core.World, - position: List[float] = (0., 0., 0.572), - orientation: List[float] = (0, 0, 0, 1.0), - model_file: str = None): +class ICubGazebo(ICubGazeboABC): + def __init__( + self, + world: scenario_core.World, + position: List[float] = (0.0, 0.0, 0.572), + orientation: List[float] = (0, 0, 0, 1.0), + model_file: str = None, + ): - super().__init__(world=world, - position=position, - orientation=orientation, - model_file=model_file) + super().__init__( + world=world, + position=position, + orientation=orientation, + model_file=model_file, + ) @classmethod def get_model_file(cls) -> str: import gym_ignition_models + return gym_ignition_models.get_model_file("iCubGazeboV2_5") class ICubGazeboSimpleCollisions(ICubGazeboABC): - - def __init__(self, - world: scenario_core.World, - position: List[float] = (0., 0., 0.572), - orientation: List[float] = (0, 0, 0, 1.0), - model_file: str = None): - - super().__init__(world=world, - position=position, - orientation=orientation, - model_file=model_file) + def __init__( + self, + world: scenario_core.World, + position: List[float] = (0.0, 0.0, 0.572), + orientation: List[float] = (0, 0, 0, 1.0), + model_file: str = None, + ): + + super().__init__( + world=world, + position=position, + orientation=orientation, + model_file=model_file, + ) @classmethod def get_model_file(cls) -> str: import gym_ignition_models + return gym_ignition_models.get_model_file("iCubGazeboSimpleCollisionsV2_5") diff --git a/python/gym_ignition_environments/models/panda.py b/python/gym_ignition_environments/models/panda.py index b5dd03df8..4765b227f 100644 --- a/python/gym_ignition_environments/models/panda.py +++ b/python/gym_ignition_environments/models/panda.py @@ -3,19 +3,21 @@ # GNU Lesser General Public License v2.1 or any later version. from typing import List -from scenario import core as scenario + +from gym_ignition.scenario import model_with_file, model_wrapper from gym_ignition.utils.scenario import get_unique_model_name -from gym_ignition.scenario import model_wrapper, model_with_file +from scenario import core as scenario -class Panda(model_wrapper.ModelWrapper, - model_with_file.ModelWithFile): - def __init__(self, - world: scenario.World, - position: List[float] = (0.0, 0.0, 0.0), - orientation: List[float] = (1.0, 0, 0, 0), - model_file: str = None): +class Panda(model_wrapper.ModelWrapper, model_with_file.ModelWithFile): + def __init__( + self, + world: scenario.World, + position: List[float] = (0.0, 0.0, 0.0), + orientation: List[float] = (1.0, 0, 0, 0), + model_file: str = None, + ): # Get a unique model name model_name = get_unique_model_name(world, "panda") @@ -28,9 +30,7 @@ def __init__(self, model_file = Panda.get_model_file() # Insert the model - ok_model = world.to_gazebo().insert_model(model_file, - initial_pose, - model_name) + ok_model = world.to_gazebo().insert_model(model_file, initial_pose, model_name) if not ok_model: raise RuntimeError("Failed to insert model") @@ -40,21 +40,22 @@ def __init__(self, # Initial joint configuration model.to_gazebo().reset_joint_positions( - [0, -0.785,0, -2.356, 0, 1.571, 0.785], - [name for name in model.joint_names() if "panda_joint" in name]) + [0, -0.785, 0, -2.356, 0, 1.571, 0.785], + [name for name in model.joint_names() if "panda_joint" in name], + ) # From: # https://github.com/mkrizmancic/franka_gazebo/blob/master/config/default.yaml pid_gains_1000hz = { - 'panda_joint1': scenario.PID(50, 0, 20), - 'panda_joint2': scenario.PID(10000, 0, 500), - 'panda_joint3': scenario.PID(100, 0, 10), - 'panda_joint4': scenario.PID(1000, 0, 50), - 'panda_joint5': scenario.PID(100, 0, 10), - 'panda_joint6': scenario.PID(100, 0, 10), - 'panda_joint7': scenario.PID(10, 0.5, 0.1), - 'panda_finger_joint1': scenario.PID(100, 0, 50), - 'panda_finger_joint2': scenario.PID(100, 0, 50), + "panda_joint1": scenario.PID(50, 0, 20), + "panda_joint2": scenario.PID(10000, 0, 500), + "panda_joint3": scenario.PID(100, 0, 10), + "panda_joint4": scenario.PID(1000, 0, 50), + "panda_joint5": scenario.PID(100, 0, 10), + "panda_joint6": scenario.PID(100, 0, 10), + "panda_joint7": scenario.PID(10, 0.5, 0.1), + "panda_finger_joint1": scenario.PID(100, 0, 50), + "panda_finger_joint2": scenario.PID(100, 0, 50), } # Check that all joints have gains @@ -77,4 +78,5 @@ def __init__(self, def get_model_file(cls) -> str: import gym_ignition_models + return gym_ignition_models.get_model_file("panda") diff --git a/python/gym_ignition_environments/models/pendulum.py b/python/gym_ignition_environments/models/pendulum.py index 343701c9d..d5218529e 100644 --- a/python/gym_ignition_environments/models/pendulum.py +++ b/python/gym_ignition_environments/models/pendulum.py @@ -3,19 +3,21 @@ # GNU Lesser General Public License v2.1 or any later version. from typing import List -from scenario import core as scenario + +from gym_ignition.scenario import model_with_file, model_wrapper from gym_ignition.utils.scenario import get_unique_model_name -from gym_ignition.scenario import model_wrapper, model_with_file +from scenario import core as scenario -class Pendulum(model_wrapper.ModelWrapper, - model_with_file.ModelWithFile): - def __init__(self, - world: scenario.World, - position: List[float] = (0.0, 0.0, 0.0), - orientation: List[float] = (1.0, 0, 0, 0), - model_file: str = None): +class Pendulum(model_wrapper.ModelWrapper, model_with_file.ModelWithFile): + def __init__( + self, + world: scenario.World, + position: List[float] = (0.0, 0.0, 0.0), + orientation: List[float] = (1.0, 0, 0, 0), + model_file: str = None, + ): # Get a unique model name model_name = get_unique_model_name(world, "pendulum") @@ -28,9 +30,7 @@ def __init__(self, model_file = Pendulum.get_model_file() # Insert the model - ok_model = world.to_gazebo().insert_model(model_file, - initial_pose, - model_name) + ok_model = world.to_gazebo().insert_model(model_file, initial_pose, model_name) if not ok_model: raise RuntimeError("Failed to insert model") @@ -45,4 +45,5 @@ def __init__(self, def get_model_file(cls) -> str: import gym_ignition_models + return gym_ignition_models.get_model_file("pendulum") diff --git a/python/gym_ignition_environments/randomizers/__init__.py b/python/gym_ignition_environments/randomizers/__init__.py index c96f81343..b3485616f 100644 --- a/python/gym_ignition_environments/randomizers/__init__.py +++ b/python/gym_ignition_environments/randomizers/__init__.py @@ -2,5 +2,4 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -from . import cartpole -from . import cartpole_no_rand +from . import cartpole, cartpole_no_rand diff --git a/python/gym_ignition_environments/randomizers/cartpole.py b/python/gym_ignition_environments/randomizers/cartpole.py index a1fca6d5e..4e5a01a09 100644 --- a/python/gym_ignition_environments/randomizers/cartpole.py +++ b/python/gym_ignition_environments/randomizers/cartpole.py @@ -4,26 +4,31 @@ import abc from typing import Union -from gym_ignition import utils + +from gym_ignition import randomizers, utils +from gym_ignition.randomizers import gazebo_env_randomizer +from gym_ignition.randomizers.gazebo_env_randomizer import MakeEnvCallable +from gym_ignition.randomizers.model.sdf import Distribution, Method, UniformParams from gym_ignition.utils import misc -from gym_ignition import randomizers -from scenario import gazebo as scenario from gym_ignition_environments import tasks from gym_ignition_environments.models import cartpole -from gym_ignition.randomizers import gazebo_env_randomizer -from gym_ignition.randomizers.gazebo_env_randomizer import MakeEnvCallable -from gym_ignition.randomizers.model.sdf import Method, Distribution, UniformParams - -# Tasks that are supported by this randomizer. Used for type hinting. -SupportedTasks = Union[tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing, - tasks.cartpole_continuous_swingup.CartPoleContinuousSwingup, - tasks.cartpole_continuous_balancing.CartPoleContinuousBalancing] +from scenario import gazebo as scenario -class CartpoleRandomizersMixin(randomizers.abc.TaskRandomizer, - randomizers.abc.PhysicsRandomizer, - randomizers.abc.ModelDescriptionRandomizer, - abc.ABC): +# Tasks that are supported by this randomizer. Used for type hinting. +SupportedTasks = Union[ + tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing, + tasks.cartpole_continuous_swingup.CartPoleContinuousSwingup, + tasks.cartpole_continuous_balancing.CartPoleContinuousBalancing, +] + + +class CartpoleRandomizersMixin( + randomizers.abc.TaskRandomizer, + randomizers.abc.PhysicsRandomizer, + randomizers.abc.ModelDescriptionRandomizer, + abc.ABC, +): """ Mixin that collects the implementation of task, model and physics randomizations for cartpole environments. @@ -34,7 +39,8 @@ def __init__(self, randomize_physics_after_rollouts: int = 0): # Initialize base classes randomizers.abc.TaskRandomizer.__init__(self) randomizers.abc.PhysicsRandomizer.__init__( - self, randomize_after_rollouts_num=randomize_physics_after_rollouts) + self, randomize_after_rollouts_num=randomize_physics_after_rollouts + ) randomizers.abc.ModelDescriptionRandomizer.__init__(self) # SDF randomizer @@ -97,8 +103,9 @@ def randomize_model_description(self, task: SupportedTasks, **kwargs) -> str: # Private Methods # =============== - def _get_sdf_randomizer(self, task: SupportedTasks) -> \ - randomizers.model.sdf.SDFRandomizer: + def _get_sdf_randomizer( + self, task: SupportedTasks + ) -> randomizers.model.sdf.SDFRandomizer: if self._sdf_randomizer is not None: return self._sdf_randomizer @@ -119,12 +126,11 @@ def _get_sdf_randomizer(self, task: SupportedTasks) -> \ sdf_randomizer.rng = task.np_random # Randomize the mass of all links - sdf_randomizer.new_randomization() \ - .at_xpath("*/link/inertial/mass") \ - .method(Method.Additive) \ - .sampled_from(Distribution.Uniform, UniformParams(low=-0.2, high=0.2)) \ - .force_positive() \ - .add() + sdf_randomizer.new_randomization().at_xpath("*/link/inertial/mass").method( + Method.Additive + ).sampled_from( + Distribution.Uniform, UniformParams(low=-0.2, high=0.2) + ).force_positive().add() # Process the randomization sdf_randomizer.process_data() @@ -148,27 +154,27 @@ def _populate_world(task: SupportedTasks, cartpole_model: str = None) -> None: # Insert a new cartpole. # It will create a unique name if there are clashing. - model = cartpole.CartPole(world=task.world, - model_file=cartpole_model) + model = cartpole.CartPole(world=task.world, model_file=cartpole_model) # Store the model name in the task task.model_name = model.name() -class CartpoleEnvRandomizer(gazebo_env_randomizer.GazeboEnvRandomizer, - CartpoleRandomizersMixin): +class CartpoleEnvRandomizer( + gazebo_env_randomizer.GazeboEnvRandomizer, CartpoleRandomizersMixin +): """ Concrete implementation of cartpole environments randomization. """ - def __init__(self, - env: MakeEnvCallable, - num_physics_rollouts: int = 0): + def __init__(self, env: MakeEnvCallable, num_physics_rollouts: int = 0): # Initialize the mixin CartpoleRandomizersMixin.__init__( - self, randomize_physics_after_rollouts=num_physics_rollouts) + self, randomize_physics_after_rollouts=num_physics_rollouts + ) # Initialize the environment randomizer gazebo_env_randomizer.GazeboEnvRandomizer.__init__( - self, env=env, physics_randomizer=self) + self, env=env, physics_randomizer=self + ) diff --git a/python/gym_ignition_environments/randomizers/cartpole_no_rand.py b/python/gym_ignition_environments/randomizers/cartpole_no_rand.py index e8c5843e3..d5ae4076f 100644 --- a/python/gym_ignition_environments/randomizers/cartpole_no_rand.py +++ b/python/gym_ignition_environments/randomizers/cartpole_no_rand.py @@ -3,15 +3,18 @@ # GNU Lesser General Public License v2.1 or any later version. from typing import Union -from gym_ignition_environments import tasks -from gym_ignition_environments.models import cartpole + from gym_ignition.randomizers import gazebo_env_randomizer from gym_ignition.randomizers.gazebo_env_randomizer import MakeEnvCallable +from gym_ignition_environments import tasks +from gym_ignition_environments.models import cartpole # Tasks that are supported by this randomizer. Used for type hinting. -SupportedTasks = Union[tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing, - tasks.cartpole_continuous_swingup.CartPoleContinuousSwingup, - tasks.cartpole_continuous_balancing.CartPoleContinuousBalancing] +SupportedTasks = Union[ + tasks.cartpole_discrete_balancing.CartPoleDiscreteBalancing, + tasks.cartpole_continuous_swingup.CartPoleContinuousSwingup, + tasks.cartpole_continuous_balancing.CartPoleContinuousBalancing, +] class CartpoleEnvNoRandomizations(gazebo_env_randomizer.GazeboEnvRandomizer): diff --git a/python/gym_ignition_environments/tasks/__init__.py b/python/gym_ignition_environments/tasks/__init__.py index e33fc6c82..ae1ac74ef 100644 --- a/python/gym_ignition_environments/tasks/__init__.py +++ b/python/gym_ignition_environments/tasks/__init__.py @@ -2,12 +2,9 @@ # This software may be modified and distributed under the terms of the # GNU Lesser General Public License v2.1 or any later version. -# Pendulum -from . import pendulum_swingup - -# Cartpole discrete -from . import cartpole_discrete_balancing - -# Cartpole continuous -from . import cartpole_continuous_swingup -from . import cartpole_continuous_balancing +from . import ( + cartpole_continuous_balancing, + cartpole_continuous_swingup, + cartpole_discrete_balancing, + pendulum_swingup, +) diff --git a/python/gym_ignition_environments/tasks/cartpole_continuous_balancing.py b/python/gym_ignition_environments/tasks/cartpole_continuous_balancing.py index 0f489c0c3..7c131ba3e 100644 --- a/python/gym_ignition_environments/tasks/cartpole_continuous_balancing.py +++ b/python/gym_ignition_environments/tasks/cartpole_continuous_balancing.py @@ -3,21 +3,24 @@ # GNU Lesser General Public License v2.1 or any later version. import abc +from typing import Tuple + import gym import numpy as np -from typing import Tuple from gym_ignition.base import task +from gym_ignition.utils.typing import ( + Action, + ActionSpace, + Observation, + ObservationSpace, + Reward, +) + from scenario import core as scenario -from gym_ignition.utils.typing import Action, Reward, Observation -from gym_ignition.utils.typing import ActionSpace, ObservationSpace class CartPoleContinuousBalancing(task.Task, abc.ABC): - - def __init__(self, - agent_rate: float, - reward_cart_at_center: bool = True, - **kwargs): + def __init__(self, agent_rate: float, reward_cart_at_center: bool = True, **kwargs): # Initialize the Task base class task.Task.__init__(self, agent_rate=agent_rate) @@ -41,28 +44,28 @@ def create_spaces(self) -> Tuple[ActionSpace, ObservationSpace]: # Create the action space max_force = 50.0 # Nm - action_space = gym.spaces.Box(low=np.array([-max_force]), - high=np.array([max_force]), - dtype=np.float32) + action_space = gym.spaces.Box( + low=np.array([-max_force]), high=np.array([max_force]), dtype=np.float32 + ) # Configure reset limits - high = np.array([ - self._x_threshold, # x - self._dx_threshold, # dx - self._q_threshold, # q - self._dq_threshold # dq - ]) + high = np.array( + [ + self._x_threshold, # x + self._dx_threshold, # dx + self._q_threshold, # q + self._dq_threshold, # dq + ] + ) # Configure the reset space - self.reset_space = gym.spaces.Box(low=-high, - high=high, - dtype=np.float32) + self.reset_space = gym.spaces.Box(low=-high, high=high, dtype=np.float32) # Configure the observation space obs_high = high.copy() * 1.2 - observation_space = gym.spaces.Box(low=-obs_high, - high=obs_high, - dtype=np.float32) + observation_space = gym.spaces.Box( + low=-obs_high, high=obs_high, dtype=np.float32 + ) return action_space, observation_space @@ -103,10 +106,12 @@ def get_reward(self) -> Reward: # Get the observation x, dx, _, _ = self.get_observation() - reward = reward \ - - 0.10 * np.abs(x) \ - - 0.10 * np.abs(dx) \ + reward = ( + reward + - 0.10 * np.abs(x) + - 0.10 * np.abs(dx) - 10.0 * (x >= self._x_threshold) + ) return reward @@ -139,8 +144,12 @@ def reset_task(self) -> None: x, dx, q, dq = self.np_random.uniform(low=-0.05, high=0.05, size=(4,)) # Reset the cartpole state - ok_reset_pos = model.to_gazebo().reset_joint_positions([x, q], ["linear", "pivot"]) - ok_reset_vel = model.to_gazebo().reset_joint_velocities([dx, dq], ["linear", "pivot"]) + ok_reset_pos = model.to_gazebo().reset_joint_positions( + [x, q], ["linear", "pivot"] + ) + ok_reset_vel = model.to_gazebo().reset_joint_velocities( + [dx, dq], ["linear", "pivot"] + ) if not (ok_reset_pos and ok_reset_vel): raise RuntimeError("Failed to reset the cartpole state") diff --git a/python/gym_ignition_environments/tasks/cartpole_continuous_swingup.py b/python/gym_ignition_environments/tasks/cartpole_continuous_swingup.py index 98017febe..12878ff87 100644 --- a/python/gym_ignition_environments/tasks/cartpole_continuous_swingup.py +++ b/python/gym_ignition_environments/tasks/cartpole_continuous_swingup.py @@ -3,21 +3,24 @@ # GNU Lesser General Public License v2.1 or any later version. import abc +from typing import Tuple + import gym import numpy as np -from typing import Tuple from gym_ignition.base import task +from gym_ignition.utils.typing import ( + Action, + ActionSpace, + Observation, + ObservationSpace, + Reward, +) + from scenario import core as scenario -from gym_ignition.utils.typing import Action, Reward, Observation -from gym_ignition.utils.typing import ActionSpace, ObservationSpace class CartPoleContinuousSwingup(task.Task, abc.ABC): - - def __init__(self, - agent_rate: float, - reward_cart_at_center: bool = True, - **kwargs): + def __init__(self, agent_rate: float, reward_cart_at_center: bool = True, **kwargs): # Initialize the Task base class task.Task.__init__(self, agent_rate=agent_rate) @@ -41,28 +44,28 @@ def create_spaces(self) -> Tuple[ActionSpace, ObservationSpace]: # Create the action space max_force = 200.0 # Nm - action_space = gym.spaces.Box(low=np.array([-max_force]), - high=np.array([max_force]), - dtype=np.float32) + action_space = gym.spaces.Box( + low=np.array([-max_force]), high=np.array([max_force]), dtype=np.float32 + ) # Configure reset limits - high = np.array([ - self._x_threshold, # x - self._dx_threshold, # dx - self._q_threshold, # q - self._dq_threshold # dq - ]) + high = np.array( + [ + self._x_threshold, # x + self._dx_threshold, # dx + self._q_threshold, # q + self._dq_threshold, # dq + ] + ) # Configure the reset space - self.reset_space = gym.spaces.Box(low=-high, - high=high, - dtype=np.float32) + self.reset_space = gym.spaces.Box(low=-high, high=high, dtype=np.float32) # Configure the observation space obs_high = high.copy() * 1.2 - observation_space = gym.spaces.Box(low=-obs_high, - high=obs_high, - dtype=np.float32) + observation_space = gym.spaces.Box( + low=-obs_high, high=obs_high, dtype=np.float32 + ) return action_space, observation_space @@ -146,8 +149,12 @@ def reset_task(self) -> None: x, dx, dq = self.np_random.uniform(low=-0.05, high=0.05, size=(3,)) # Reset the cartpole state - ok_reset_pos = model.to_gazebo().reset_joint_positions([x, q], ["linear", "pivot"]) - ok_reset_vel = model.to_gazebo().reset_joint_velocities([dx, dq], ["linear", "pivot"]) + ok_reset_pos = model.to_gazebo().reset_joint_positions( + [x, q], ["linear", "pivot"] + ) + ok_reset_vel = model.to_gazebo().reset_joint_velocities( + [dx, dq], ["linear", "pivot"] + ) if not (ok_reset_pos and ok_reset_vel): raise RuntimeError("Failed to reset the cartpole state") diff --git a/python/gym_ignition_environments/tasks/cartpole_discrete_balancing.py b/python/gym_ignition_environments/tasks/cartpole_discrete_balancing.py index f63e6ced1..743a9b784 100644 --- a/python/gym_ignition_environments/tasks/cartpole_discrete_balancing.py +++ b/python/gym_ignition_environments/tasks/cartpole_discrete_balancing.py @@ -3,21 +3,26 @@ # GNU Lesser General Public License v2.1 or any later version. import abc +from typing import Tuple + import gym import numpy as np -from typing import Tuple from gym_ignition.base import task +from gym_ignition.utils.typing import ( + Action, + ActionSpace, + Observation, + ObservationSpace, + Reward, +) + from scenario import core as scenario -from gym_ignition.utils.typing import Action, Reward, Observation -from gym_ignition.utils.typing import ActionSpace, ObservationSpace class CartPoleDiscreteBalancing(task.Task, abc.ABC): - - def __init__(self, - agent_rate: float, - reward_cart_at_center: bool = True, - **kwargs) -> None: + def __init__( + self, agent_rate: float, reward_cart_at_center: bool = True, **kwargs + ) -> None: # Initialize the Task base class task.Task.__init__(self, agent_rate=agent_rate) @@ -44,23 +49,23 @@ def create_spaces(self) -> Tuple[ActionSpace, ObservationSpace]: action_space = gym.spaces.Discrete(2) # Configure reset limits - high = np.array([ - self._x_threshold, # x - self._dx_threshold, # dx - self._q_threshold, # q - self._dq_threshold # dq - ]) + high = np.array( + [ + self._x_threshold, # x + self._dx_threshold, # dx + self._q_threshold, # q + self._dq_threshold, # dq + ] + ) # Configure the reset space - self.reset_space = gym.spaces.Box(low=-high, - high=high, - dtype=np.float32) + self.reset_space = gym.spaces.Box(low=-high, high=high, dtype=np.float32) # Configure the observation space obs_high = high.copy() * 1.2 - observation_space = gym.spaces.Box(low=-obs_high, - high=obs_high, - dtype=np.float32) + observation_space = gym.spaces.Box( + low=-obs_high, high=obs_high, dtype=np.float32 + ) return action_space, observation_space @@ -101,10 +106,12 @@ def get_reward(self) -> Reward: # Get the observation x, dx, _, _ = self.get_observation() - reward = reward \ - - 0.10 * np.abs(x) \ - - 0.10 * np.abs(dx) \ + reward = ( + reward + - 0.10 * np.abs(x) + - 0.10 * np.abs(dx) - 10.0 * (x >= 0.9 * self._x_threshold) + ) return reward @@ -137,8 +144,12 @@ def reset_task(self) -> None: x, dx, q, dq = self.np_random.uniform(low=-0.05, high=0.05, size=(4,)) # Reset the cartpole state - ok_reset_pos = model.to_gazebo().reset_joint_positions([x, q], ["linear", "pivot"]) - ok_reset_vel = model.to_gazebo().reset_joint_velocities([dx, dq], ["linear", "pivot"]) + ok_reset_pos = model.to_gazebo().reset_joint_positions( + [x, q], ["linear", "pivot"] + ) + ok_reset_vel = model.to_gazebo().reset_joint_velocities( + [dx, dq], ["linear", "pivot"] + ) if not (ok_reset_pos and ok_reset_vel): raise RuntimeError("Failed to reset the cartpole state") diff --git a/python/gym_ignition_environments/tasks/pendulum_swingup.py b/python/gym_ignition_environments/tasks/pendulum_swingup.py index 883e1a070..4019dd708 100644 --- a/python/gym_ignition_environments/tasks/pendulum_swingup.py +++ b/python/gym_ignition_environments/tasks/pendulum_swingup.py @@ -3,17 +3,23 @@ # GNU Lesser General Public License v2.1 or any later version. import abc +from typing import Tuple + import gym import numpy as np -from typing import Tuple from gym_ignition.base import task +from gym_ignition.utils.typing import ( + Action, + ActionSpace, + Observation, + ObservationSpace, + Reward, +) + from scenario import core as scenario -from gym_ignition.utils.typing import Action, Observation, Reward -from gym_ignition.utils.typing import ActionSpace, ObservationSpace class PendulumSwingUp(task.Task, abc.ABC): - def __init__(self, agent_rate: float, **kwargs): # Initialize the Task base class @@ -28,16 +34,11 @@ def __init__(self, agent_rate: float, **kwargs): def create_spaces(self) -> Tuple[ActionSpace, ObservationSpace]: - action_space = gym.spaces.Box(low=-self._max_torque, - high=self._max_torque, - shape=(1,), - dtype=np.float32) + action_space = gym.spaces.Box( + low=-self._max_torque, high=self._max_torque, shape=(1,), dtype=np.float32 + ) - high = np.array([ - 1.0, # cos(theta) - 1.0, # sin(theta) - self._max_speed - ]) + high = np.array([1.0, 1.0, self._max_speed]) # cos(theta) # sin(theta) observation_space = gym.spaces.Box(low=-high, high=high, dtype=np.float32) diff --git a/scenario/CMakeLists.txt b/scenario/CMakeLists.txt new file mode 100644 index 000000000..5c1425e40 --- /dev/null +++ b/scenario/CMakeLists.txt @@ -0,0 +1,220 @@ +# Copyright (C) 2019 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. + +cmake_minimum_required(VERSION 3.16) +project(scenario VERSION 1.2.2) + +# Add custom functions / macros +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +# C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Include useful features +include(GNUInstallDirs) + +# Build type +if(NOT CMAKE_CONFIGURATION_TYPES) + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Choose the type of build, recommended options are: Debug or Release" FORCE) + endif() + set(SCENARIO_BUILD_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${SCENARIO_BUILD_TYPES}) +endif() + +# This new build mode configures the CMake project to be compatible with the pipeline to +# create the PyPI linux wheel +include(AddNewBuildMode) +add_new_build_mode(NAME "PyPI" TEMPLATE "Release") + +# Expose shared or static compilation +set(SCENARIO_BUILD_SHARED_LIBRARY TRUE + CACHE BOOL "Compile libraries as shared libraries") + +if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "PyPI") + # Apply the user choice + set(BUILD_SHARED_LIBS ${SCENARIO_BUILD_SHARED_LIBRARY}) +else() + # Check that is Linux + if(NOT (UNIX AND NOT APPLE)) + message(FATAL_ERROR "PyPI packages can be only created for Linux at the moment") + endif() + + if(SCENARIO_BUILD_SHARED_LIBRARY) + message(WARNING "Enabling static compilation, required by the PyPI build mode") + endif() + + # Force static compilation + set(BUILD_SHARED_LIBS FALSE) +endif() + +# Use -fPIC even if statically compiled +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Tweak linker flags in Linux +if(UNIX AND NOT APPLE) + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + get_filename_component(LINKER_BIN ${CMAKE_LINKER} NAME) + if("${LINKER_BIN}" STREQUAL "ld") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--unresolved-symbols=report-all") + endif() + endif() +endif() + +# Control where binaries and libraries are placed in the build folder. +# This simplifies tests running in Windows. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +# Get include-what-you-use information when compiling +option(SCENARIO_USE_IWYU "Get the output of include-what-you-use" OFF) +mark_as_advanced(SCENARIO_USE_IWYU) +if(SCENARIO_USE_IWYU) + find_program(IWYU_PATH NAMES include-what-you-use iwyu) + if(IWYU_PATH) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_PATH}) + endif() +endif() + +# Settings for RPATH +if(NOT MSVC) + option(ENABLE_RPATH "Enable RPATH installation" TRUE) + mark_as_advanced(ENABLE_RPATH) +endif() + +# Dependencies +add_subdirectory(deps) + +if(${CMAKE_VERSION} VERSION_GREATER 3.15) + cmake_policy(SET CMP0094 NEW) +endif() + +# Find virtualenv's before system's interpreters +set(Python3_FIND_VIRTUALENV "FIRST" CACHE STRING + "Configure the detection of virtual environments") +set(Python3_FIND_VIRTUALENV_TYPES "FIRST" "ONLY" "STANDARD") +mark_as_advanced(Python3_FIND_VIRTUALENV) +set_property(CACHE Python3_FIND_VIRTUALENV PROPERTY STRINGS ${Python3_FIND_VIRTUALENV_TYPES}) + +# Find Python3 +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +message(STATUS "Using Python: ${Python3_EXECUTABLE}") + +# Select the appropriate install prefix used throughout the project. +set(SCENARIO_INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR}) +set(SCENARIO_INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR}) +set(SCENARIO_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) + +if(NOT CMAKE_BUILD_TYPE STREQUAL "PyPI") + # Add the libraries installed in the Python site-package folder + set(EXTRA_RPATH_DIRS + "${Python3_SITELIB}" + "${Python3_SITELIB}/scenario/bindings") +else() + # Add the libraries installed in the Python site-package folder + # (that in this case is CMAKE_INSTALL_PREFIX) + set(EXTRA_RPATH_DIRS + "${CMAKE_INSTALL_PREFIX}" + "${CMAKE_INSTALL_PREFIX}/scenario/bindings") +endif() + +# Configure RPATH +include(AddInstallRPATHSupport) +add_install_rpath_support( + BIN_DIRS + "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_BINDIR}" + LIB_DIRS + "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_LIBDIR}" + "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_LIBDIR}/scenario/plugins" + "${EXTRA_RPATH_DIRS}" + INSTALL_NAME_DIR + "${CMAKE_INSTALL_PREFIX}/${SCENARIO_INSTALL_LIBDIR}" + DEPENDS ENABLE_RPATH + USE_LINK_PATH) + +# Find a supported Ignition distribution +if(NOT IGNITION_DISTRIBUTION) + + include(FindIgnitionDistribution) + set(SUPPORTED_IGNITION_DISTRIBUTIONS + "Fortress" + "Edifice" + "Dome" + "Citadel") + + foreach(distribution IN LISTS SUPPORTED_IGNITION_DISTRIBUTIONS) + + find_ignition_distribution( + CODENAME ${distribution} + PACKAGES + ignition-gazebo + REQUIRED FALSE) + + if(${${distribution}_FOUND}) + message(STATUS "Found Ignition ${distribution}") + + # Select Ignition distribution + set(IGNITION_DISTRIBUTION "${distribution}" CACHE + STRING "The Ignition distribution found in the system") + set_property(CACHE IGNITION_DISTRIBUTION PROPERTY + STRINGS ${SUPPORTED_IGNITION_DISTRIBUTIONS}) + + break() + endif() + + endforeach() + +endif() + +if(NOT IGNITION_DISTRIBUTION OR "${IGNITION_DISTRIBUTION}" STREQUAL "") + set(USE_IGNITION FALSE) +else() + set(USE_IGNITION TRUE) +endif() + +option(SCENARIO_USE_IGNITION + "Build C++ code depending on Ignition" + ${USE_IGNITION}) + +# Fail if Ignition is enabled but no compatible distribution was found +if(SCENARIO_USE_IGNITION AND "${IGNITION_DISTRIBUTION}" STREQUAL "") + message(FATAL_ERROR "Failed to find a compatible Ignition Gazebo distribution") +endif() + +# Alias the targets +if(SCENARIO_USE_IGNITION) + include(ImportTargets${IGNITION_DISTRIBUTION}) +endif() + +# Helper for exporting targets +include(InstallBasicPackageFiles) + +# ========= +# SCENARI/O +# ========= + +add_subdirectory(src) + +# ======== +# BINDINGS +# ======== + +# Require to find Ignition libraries when packaging for PyPI +if(CMAKE_BUILD_TYPE STREQUAL "PyPI" AND NOT USE_IGNITION) + message(FATAL_ERROR "Found no Ignition distribution for PyPI package") +endif() + +find_package(SWIG 4.0 QUIET) +option(SCENARIO_ENABLE_BINDINGS "Enable SWIG bindings" ${SWIG_FOUND}) + +if(SCENARIO_ENABLE_BINDINGS) + add_subdirectory(bindings) +endif() + +# Add unistall target +include(AddUninstallTarget) diff --git a/scenario/README.md b/scenario/README.md new file mode 100644 index 000000000..93671a52a --- /dev/null +++ b/scenario/README.md @@ -0,0 +1,133 @@ +# ScenarIO + +[![C++ standard](https://img.shields.io/badge/standard-C++17-blue.svg?style=flat&logo=c%2B%2B)](https://isocpp.org) +[![Size](https://img.shields.io/github/languages/code-size/robotology/gym-ignition.svg)][scenario] +[![Lines](https://img.shields.io/tokei/lines/github/robotology/gym-ignition)][gym-ignition] +[![Python CI/CD](https://github.com/robotology/gym-ignition/workflows/CI/CD/badge.svg)](https://github.com/robotology/gym-ignition/actions) + +[![Version](https://img.shields.io/pypi/v/scenario.svg)][pypi] +[![Python versions](https://img.shields.io/pypi/pyversions/scenario.svg)][pypi] +[![Status](https://img.shields.io/pypi/status/scenario.svg)][pypi] +[![Format](https://img.shields.io/pypi/format/scenario.svg)][pypi] +[![License](https://img.shields.io/pypi/l/scenario.svg)][pypi] + +[pypi]: https://pypi.org/project/scenario/ +[gym-ignition]: https://github.com/robotology/gym-ignition +[scenario]: https://github.com/robotology/gym-ignition/tree/master/scenario + +**SCEN**e interf**A**ces for **R**obot **I**nput / **O**utput. + +|||| +|:---:|:---:|:---:| +| ![][pendulum] | ![][panda] | ![][icub] | + +[icub]: https://user-images.githubusercontent.com/469199/99262746-9e021a80-281e-11eb-9df1-d70134b0801a.png +[panda]: https://user-images.githubusercontent.com/469199/99263111-0cdf7380-281f-11eb-9cfe-338b2aae0503.png +[pendulum]: https://user-images.githubusercontent.com/469199/99262383-321fb200-281e-11eb-89cc-cc31f590daa3.png + +## Description + +**ScenarIO** is a C++ abstraction layer to interact with simulated and real robots. + +It mainly provides the following +[C++ interfaces](https://github.com/robotology/gym-ignition/tree/master/scenario/core/include/scenario/core): + +- `scenario::core::World` +- `scenario::core::Model` +- `scenario::core::Link` +- `scenario::core::Joint` + +These interfaces can be implemented to operate on different scenarios, +including robots operating on either simulated worlds or in real-time. + +ScenarIO currently fully implements **Gazebo ScenarIO**, +a simulated back-end that interacts with [Ignition Gazebo](https://ignitionrobotics.org). +The result allows stepping the simulator programmatically, ensuring a fully reproducible behaviour. +It relates closely to other projects like +[pybullet](https://github.com/bulletphysics/bullet3) and [mujoco-py](https://github.com/openai/mujoco-py). + +A real-time backend that interacts with the [YARP](https://github.com/robotology/yarp) middleware is under development. + +ScenarIO can be used either from C++ ([APIs](https://robotology.github.io/gym-ignition/master/breathe/core.html)) +or from Python ([APIs](https://robotology.github.io/gym-ignition/master/apidoc/scenario/scenario.bindings.html)). + +If you're interested to know the reasons why we started developing ScenarIO and why we selected Ignition Gazebo +for our simulations, visit the _Motivations_ section of the +[website][website]. + +## Installation + +ScenarIO only supports a single distribution of the Ignition suite. +Visit our [Support Policy](https://robotology.github.io/gym-ignition/master/installation/support_policy.html) +to check the distribution currently supported. + +Then, install the supported Ignition suite following the +[official instructions](https://ignitionrobotics.org/docs/edifice). + +### Python + +Execute, preferably in a [virtual environment](https://docs.python.org/3.8/tutorial/venv.html): + +```bash +pip install scenario +``` + +### C++ + +You can either clone and install the standalone project: + +```cmake +git clone https://github.com/robotology/gym-ignition +cd gym-ignition/scenario +cmake -S . -B build/ +cmake --build build/ --target install +``` + +or include it in your CMake project with +[`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html). + +## Usage + +You can find some examples that show the usage of ScenarIO in the _Getting Started_ section of the +[website][website]. + +## Contributing + +Please visit the _Limitations_ section of the [website][website] and check the +[`good first issue`](https://github.com/robotology/gym-ignition/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) +and +[`help wanted`](https://github.com/robotology/gym-ignition/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) +issues. + +You can visit our community forum hosted in [GitHub Discussions](https://github.com/robotology/gym-ignition/discussions). +Even without coding skills, replying user's questions is a great way of contributing. +If you use ScenarIO in your application and want to show it off, visit the +[Show and tell](https://github.com/robotology/gym-ignition/discussions/categories/show-and-tell) section! + +Pull requests are welcome. + +For major changes, please open a [discussion](https://github.com/robotology/gym-ignition/discussions) +first to propose what you would like to change. + +## Citation + +```bibtex +@INPROCEEDINGS{ferigo2020gymignition, + title={Gym-Ignition: Reproducible Robotic Simulations for Reinforcement Learning}, + author={D. {Ferigo} and S. {Traversaro} and G. {Metta} and D. {Pucci}}, + booktitle={2020 IEEE/SICE International Symposium on System Integration (SII)}, + year={2020}, + pages={885-890}, + doi={10.1109/SII46433.2020.9025951} +} +``` + +## License + +[LGPL v2.1](https://choosealicense.com/licenses/lgpl-2.1/) or any later version. + +We vendor some resources from the Ignition code base. +For this reason, Gazebo ScenarIO is double-licensed with the +[Apache License](https://choosealicense.com/licenses/apache-2.0/). + +[website]: https://robotology.github.io/gym-ignition diff --git a/bindings/CMakeLists.txt b/scenario/bindings/CMakeLists.txt similarity index 100% rename from bindings/CMakeLists.txt rename to scenario/bindings/CMakeLists.txt diff --git a/bindings/__init__.py b/scenario/bindings/__init__.py similarity index 65% rename from bindings/__init__.py rename to scenario/bindings/__init__.py index 812f6fadf..da41fc360 100644 --- a/bindings/__init__.py +++ b/scenario/bindings/__init__.py @@ -4,8 +4,26 @@ import os import sys +from enum import Enum, auto from pathlib import Path -from enum import auto, Enum + +import packaging.specifiers +import packaging.version + + +def supported_versions_specifier_set() -> packaging.specifiers.SpecifierSet: + + # If 6 is the Ignition distribution major version, the following specifier enables + # the compatibility with all the following versions: + # + # 6.Y.Z.devK + # 6.Y.Z.alphaK + # 6.Y.Z.betaK + # 6.Y.Z.rcK + # 6.Y.Z.preK + # 6.Y.Z.postK + # + return packaging.specifiers.SpecifierSet("~=6.0.0.dev") class InstallMode(Enum): @@ -16,6 +34,7 @@ class InstallMode(Enum): def detect_install_mode() -> InstallMode: import scenario.bindings.core + install_prefix = scenario.bindings.core.get_install_prefix() return InstallMode.User if install_prefix == "" else InstallMode.Developer @@ -46,6 +65,7 @@ def preload_tensorflow_shared_libraries() -> None: # Check if tensorflow is installed import importlib.util + spec = importlib.util.find_spec("tensorflow") if spec is None: @@ -53,6 +73,7 @@ def preload_tensorflow_shared_libraries() -> None: # Get the tensorflow __init__ location import pathlib + init = pathlib.Path(spec.origin) # Get the tensorflow top-level folder @@ -66,11 +87,13 @@ def preload_tensorflow_shared_libraries() -> None: # Load the main shared library for lib in tensorflow_dir.glob("*tensorflow*.so*"): import ctypes + ctypes.CDLL(str(lib)) # Load all the shared libraries inside tensorflow/python for lib in tensorflow_python_dir.glob("_*.so"): import ctypes + ctypes.CDLL(str(lib)) @@ -78,6 +101,7 @@ def pre_import_gym() -> None: # Check if gym is installed import importlib.util + spec = importlib.util.find_spec("gym") if spec is None: @@ -86,6 +110,42 @@ def pre_import_gym() -> None: import gym +def check_gazebo_installation() -> None: + + import subprocess + + try: + command = ["ign", "gazebo", "--versions"] + result = subprocess.run(command, capture_output=True, text=True, check=True) + except FileNotFoundError: + msg = "Failed to find the 'ign' command in your PATH. " + msg += "Make sure that Ignition is installed " + msg += "and your environment is properly configured." + raise RuntimeError(msg) + except subprocess.CalledProcessError: + raise RuntimeError(f"Failed to execute command: {' '.join(command)}") # noqa + + gazebo_version_string = result.stdout.strip() + + # Get the gazebo version from the command line. + # Since the releases could be in the "6.0.0~preK" form, we replace '~' with '.' to + # be compatible with the 'packaging' package. + gazebo_version_string_normalized = gazebo_version_string.replace("~", ".") + + try: + # Parse the gazebo version + gazebo_version_parsed = packaging.version.Version( + gazebo_version_string_normalized + ) + except: + raise RuntimeError(f"Failed to parse the output of: {' '.join(command)}") + + if not gazebo_version_parsed in supported_versions_specifier_set(): + msg = f"Failed to find Ignition Gazebo {supported_versions_specifier_set()} " + msg += f"(found incompatible {gazebo_version_parsed})" + raise RuntimeError(msg) + + def import_gazebo() -> None: # Check the the module was never loaded by someone else @@ -105,7 +165,7 @@ def import_gazebo() -> None: # Import SWIG bindings # See https://github.com/robotology/gym-ignition/issues/7 # https://stackoverflow.com/a/45473441/12150968 - if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): + if sys.platform.startswith("linux") or sys.platform.startswith("darwin"): # Update the dlopen flags dlopen_flags = sys.getdlopenflags() @@ -123,9 +183,10 @@ def import_gazebo() -> None: def create_home_dot_folder() -> None: # Make sure that the dot folder in the user's home exists - Path("~/.ignition/gazebo").expanduser().mkdir(mode=0o755, - parents=True, - exist_ok=True) + Path("~/.ignition/gazebo").expanduser().mkdir( + mode=0o755, parents=True, exist_ok=True + ) + # =================== # Import the bindings @@ -133,6 +194,7 @@ def create_home_dot_folder() -> None: try: import_gazebo() + check_gazebo_installation() create_home_dot_folder() setup_gazebo_environment() from .bindings import gazebo diff --git a/bindings/core/CMakeLists.txt b/scenario/bindings/core/CMakeLists.txt similarity index 100% rename from bindings/core/CMakeLists.txt rename to scenario/bindings/core/CMakeLists.txt diff --git a/bindings/core/core.i b/scenario/bindings/core/core.i similarity index 86% rename from bindings/core/core.i rename to scenario/bindings/core/core.i index c06ad3c93..f618ce6d3 100644 --- a/bindings/core/core.i +++ b/scenario/bindings/core/core.i @@ -51,18 +51,18 @@ %template(VectorOfContactPoints) std::vector; // Doxygen typemaps -%typemap(doctype) std::array "Iterable[float, float, float]"; -%typemap(doctype) std::array "Iterable[float, float, float, float]"; -%typemap(doctype) std::array "Iterable[float, float, float, float, float, float]"; -%typemap(doctype) std::vector "Iterable[float]"; -%typemap(doctype) std::vector "Iterable[string]"; +%typemap(doctype) std::array "Tuple[float, float, float]"; +%typemap(doctype) std::array "Tuple[float, float, float, float]"; +%typemap(doctype) std::array "Tuple[float, float, float, float, float, float]"; +%typemap(doctype) std::vector "Tuple[float]"; +%typemap(doctype) std::vector "Tuple[string]"; %typemap(doctype) std::vector "Tuple[Link]"; %typemap(doctype) std::vector "Tuple[Joint]"; %typemap(doctype) std::vector "Tuple[Contact]"; %typemap(doctype) std::vector "Tuple[ContactPoint]"; %pythonbegin %{ -from typing import Iterable, Tuple +from typing import Tuple %} // NOTE: Keep all template instantiations above. diff --git a/bindings/gazebo/CMakeLists.txt b/scenario/bindings/gazebo/CMakeLists.txt similarity index 99% rename from bindings/gazebo/CMakeLists.txt rename to scenario/bindings/gazebo/CMakeLists.txt index c314b6851..18fad353c 100644 --- a/bindings/gazebo/CMakeLists.txt +++ b/scenario/bindings/gazebo/CMakeLists.txt @@ -17,7 +17,6 @@ swig_add_library(${scenario_swig_name} target_link_libraries(${scenario_swig_name} PUBLIC - ECMSingleton ScenarioGazebo::ScenarioGazebo ScenarioGazebo::GazeboSimulator Python3::Python) diff --git a/bindings/gazebo/gazebo.i b/scenario/bindings/gazebo/gazebo.i similarity index 89% rename from bindings/gazebo/gazebo.i rename to scenario/bindings/gazebo/gazebo.i index 3d630697f..eeb56c285 100644 --- a/bindings/gazebo/gazebo.i +++ b/scenario/bindings/gazebo/gazebo.i @@ -9,7 +9,6 @@ #include "scenario/gazebo/Model.h" #include "scenario/gazebo/utils.h" #include "scenario/gazebo/World.h" -#include "scenario/plugins/gazebo/ECMSingleton.h" #include %} @@ -59,15 +58,11 @@ namespace scenario::gazebo::utils { %rename("") Verbosity; %rename("") JointLimit; %rename("") ContactPoint; -%rename("") ECMSingleton; %rename("") GazeboEntity; %rename("") PhysicsEngine; %rename("") GazeboSimulator; %rename("") JointControlMode; -// Public helpers -%include "scenario/gazebo/utils.h" - // Other templates for ScenarI/O APIs %shared_ptr(scenario::gazebo::Joint) %shared_ptr(scenario::gazebo::Link) @@ -97,6 +92,9 @@ namespace scenario::gazebo::utils { // Interface of Gazebo entities %include "scenario/gazebo/GazeboEntity.h" +// Public helpers +%include "scenario/gazebo/utils.h" + // ScenarI/O headers %include "scenario/gazebo/Joint.h" %include "scenario/gazebo/Link.h" @@ -105,10 +103,3 @@ namespace scenario::gazebo::utils { // GazeboSimulator %include "scenario/gazebo/GazeboSimulator.h" - -// ECMSingleton -%ignore scenario::plugins::gazebo::ECMSingleton::clean; -%ignore scenario::plugins::gazebo::ECMSingleton::getECM; -%ignore scenario::plugins::gazebo::ECMSingleton::getEventManager; -%ignore scenario::plugins::gazebo::ECMSingleton::storePtrs; -%include "scenario/plugins/gazebo/ECMSingleton.h" diff --git a/bindings/gazebo/to_gazebo.i b/scenario/bindings/gazebo/to_gazebo.i similarity index 100% rename from bindings/gazebo/to_gazebo.i rename to scenario/bindings/gazebo/to_gazebo.i diff --git a/cmake/AddNewBuildMode.cmake b/scenario/cmake/AddNewBuildMode.cmake similarity index 100% rename from cmake/AddNewBuildMode.cmake rename to scenario/cmake/AddNewBuildMode.cmake diff --git a/scenario/cmake/AddUninstallTarget.cmake b/scenario/cmake/AddUninstallTarget.cmake new file mode 100644 index 000000000..4012104f2 --- /dev/null +++ b/scenario/cmake/AddUninstallTarget.cmake @@ -0,0 +1,102 @@ +#.rst: +# AddUninstallTarget +# ------------------ +# +# Add the "uninstall" target for your project:: +# +# include(AddUninstallTarget) +# +# +# will create a file ``cmake_uninstall.cmake`` in the build directory and add a +# custom target ``uninstall`` (or ``UNINSTALL`` on Visual Studio and Xcode) that +# will remove the files installed by your package (using +# ``install_manifest.txt``). +# See also +# https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake +# +# The :module:`AddUninstallTarget` module must be included in your main +# ``CMakeLists.txt``. If included in a subdirectory it does nothing. +# This allows you to use it safely in your main ``CMakeLists.txt`` and include +# your project using ``add_subdirectory`` (for example when using it with +# :cmake:module:`FetchContent`). +# +# If the ``uninstall`` target already exists, the module does nothing. + +#============================================================================= +# Copyright 2008-2013 Kitware, Inc. +# Copyright 2013 Istituto Italiano di Tecnologia (IIT) +# Authors: Daniele E. Domenichelli +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + + +# AddUninstallTarget works only when included in the main CMakeLists.txt +if(NOT "${CMAKE_CURRENT_BINARY_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + return() +endif() + +# The name of the target is uppercase in MSVC and Xcode (for coherence with the +# other standard targets) +if("${CMAKE_GENERATOR}" MATCHES "^(Visual Studio|Xcode)") + set(_uninstall "UNINSTALL") +else() + set(_uninstall "uninstall") +endif() + +# If target is already defined don't do anything +if(TARGET ${_uninstall}) + return() +endif() + + +set(_filename cmake_uninstall.cmake) + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${_filename}" +"if(NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\") + message(WARNING \"Cannot find install manifest: \\\"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\\\"\") + return() +endif() +file(READ \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\" files) +string(STRIP \"\${files}\" files) +string(REGEX REPLACE \"\\n\" \";\" files \"\${files}\") +list(REVERSE files) +foreach(file \${files}) + if(IS_SYMLINK \"\$ENV{DESTDIR}\${file}\" OR EXISTS \"\$ENV{DESTDIR}\${file}\") + message(STATUS \"Uninstalling: \$ENV{DESTDIR}\${file}\") + execute_process( + COMMAND \${CMAKE_COMMAND} -E remove \"\$ENV{DESTDIR}\${file}\" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval) + if(NOT \"\${rm_retval}\" EQUAL 0) + message(FATAL_ERROR \"Problem when removing \\\"\$ENV{DESTDIR}\${file}\\\"\") + endif() + else() + message(STATUS \"Not-found: \$ENV{DESTDIR}\${file}\") + endif() +endforeach(file) +") + +set(_desc "Uninstall the project...") +if(CMAKE_GENERATOR STREQUAL "Unix Makefiles") + set(_comment COMMAND \$\(CMAKE_COMMAND\) -E cmake_echo_color --switch=$\(COLOR\) --cyan "${_desc}") +else() + set(_comment COMMENT "${_desc}") +endif() +add_custom_target(${_uninstall} + ${_comment} + COMMAND ${CMAKE_COMMAND} -P ${_filename} + USES_TERMINAL + BYPRODUCTS uninstall_byproduct + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") +set_property(SOURCE uninstall_byproduct PROPERTY SYMBOLIC 1) + +set_property(TARGET ${_uninstall} PROPERTY FOLDER "CMakePredefinedTargets") + diff --git a/cmake/AliasImportedTarget.cmake b/scenario/cmake/AliasImportedTarget.cmake similarity index 100% rename from cmake/AliasImportedTarget.cmake rename to scenario/cmake/AliasImportedTarget.cmake diff --git a/cmake/FindIgnitionDistribution.cmake b/scenario/cmake/FindIgnitionDistribution.cmake similarity index 97% rename from cmake/FindIgnitionDistribution.cmake rename to scenario/cmake/FindIgnitionDistribution.cmake index d625ee11d..94152bc0b 100644 --- a/cmake/FindIgnitionDistribution.cmake +++ b/scenario/cmake/FindIgnitionDistribution.cmake @@ -7,6 +7,8 @@ # In the future we could pull and parse the tags.yaml file. set(IGNITION-GAZEBO_CITADEL_VER 3) set(IGNITION-GAZEBO_DOME_VER 4) +set(IGNITION-GAZEBO_EDIFICE_VER 5) +set(IGNITION-GAZEBO_FORTRESS_VER 6) macro(find_ignition_distribution) diff --git a/scenario/cmake/FindPackageHandleStandardArgs.cmake b/scenario/cmake/FindPackageHandleStandardArgs.cmake new file mode 100644 index 000000000..fbcf7cd88 --- /dev/null +++ b/scenario/cmake/FindPackageHandleStandardArgs.cmake @@ -0,0 +1,605 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPackageHandleStandardArgs +----------------------------- + +This module provides functions intended to be used in :ref:`Find Modules` +implementing :command:`find_package()` calls. + +.. command:: find_package_handle_standard_args + + This command handles the ``REQUIRED``, ``QUIET`` and version-related + arguments of :command:`find_package`. It also sets the + ``_FOUND`` variable. The package is considered found if all + variables listed contain valid results, e.g. valid filepaths. + + There are two signatures: + + .. code-block:: cmake + + find_package_handle_standard_args( + (DEFAULT_MSG|) + ... + ) + + find_package_handle_standard_args( + [FOUND_VAR ] + [REQUIRED_VARS ...] + [VERSION_VAR ] + [HANDLE_VERSION_RANGE] + [HANDLE_COMPONENTS] + [CONFIG_MODE] + [NAME_MISMATCHED] + [REASON_FAILURE_MESSAGE ] + [FAIL_MESSAGE ] + ) + + The ``_FOUND`` variable will be set to ``TRUE`` if all + the variables ``...`` are valid and any optional + constraints are satisfied, and ``FALSE`` otherwise. A success or + failure message may be displayed based on the results and on + whether the ``REQUIRED`` and/or ``QUIET`` option was given to + the :command:`find_package` call. + + The options are: + + ``(DEFAULT_MSG|)`` + In the simple signature this specifies the failure message. + Use ``DEFAULT_MSG`` to ask for a default message to be computed + (recommended). Not valid in the full signature. + + ``FOUND_VAR `` + .. deprecated:: 3.3 + + Specifies either ``_FOUND`` or + ``_FOUND`` as the result variable. This exists only + for compatibility with older versions of CMake and is now ignored. + Result variables of both names are always set for compatibility. + + ``REQUIRED_VARS ...`` + Specify the variables which are required for this package. + These may be named in the generated failure message asking the + user to set the missing variable values. Therefore these should + typically be cache entries such as ``FOO_LIBRARY`` and not output + variables like ``FOO_LIBRARIES``. + + .. versionchanged:: 3.18 + If ``HANDLE_COMPONENTS`` is specified, this option can be omitted. + + ``VERSION_VAR `` + Specify the name of a variable that holds the version of the package + that has been found. This version will be checked against the + (potentially) specified required version given to the + :command:`find_package` call, including its ``EXACT`` option. + The default messages include information about the required + version and the version which has been actually found, both + if the version is ok or not. + + ``HANDLE_VERSION_RANGE`` + .. versionadded:: 3.19 + + Enable handling of a version range, if one is specified. Without this + option, a developer warning will be displayed if a version range is + specified. + + ``HANDLE_COMPONENTS`` + Enable handling of package components. In this case, the command + will report which components have been found and which are missing, + and the ``_FOUND`` variable will be set to ``FALSE`` + if any of the required components (i.e. not the ones listed after + the ``OPTIONAL_COMPONENTS`` option of :command:`find_package`) are + missing. + + ``CONFIG_MODE`` + Specify that the calling find module is a wrapper around a + call to ``find_package( NO_MODULE)``. This implies + a ``VERSION_VAR`` value of ``_VERSION``. The command + will automatically check whether the package configuration file + was found. + + ``REASON_FAILURE_MESSAGE `` + .. versionadded:: 3.16 + + Specify a custom message of the reason for the failure which will be + appended to the default generated message. + + ``FAIL_MESSAGE `` + Specify a custom failure message instead of using the default + generated message. Not recommended. + + ``NAME_MISMATCHED`` + .. versionadded:: 3.17 + + Indicate that the ```` does not match + ``${CMAKE_FIND_PACKAGE_NAME}``. This is usually a mistake and raises a + warning, but it may be intentional for usage of the command for components + of a larger package. + +Example for the simple signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibXml2 DEFAULT_MSG + LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) + +The ``LibXml2`` package is considered to be found if both +``LIBXML2_LIBRARY`` and ``LIBXML2_INCLUDE_DIR`` are valid. +Then also ``LibXml2_FOUND`` is set to ``TRUE``. If it is not found +and ``REQUIRED`` was used, it fails with a +:command:`message(FATAL_ERROR)`, independent whether ``QUIET`` was +used or not. If it is found, success will be reported, including +the content of the first ````. On repeated CMake runs, +the same message will not be printed again. + +.. note:: + + If ```` does not match ``CMAKE_FIND_PACKAGE_NAME`` for the + calling module, a warning that there is a mismatch is given. The + ``FPHSA_NAME_MISMATCHED`` variable may be set to bypass the warning if using + the old signature and the ``NAME_MISMATCHED`` argument using the new + signature. To avoid forcing the caller to require newer versions of CMake for + usage, the variable's value will be used if defined when the + ``NAME_MISMATCHED`` argument is not passed for the new signature (but using + both is an error).. + +Example for the full signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibArchive + REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR + VERSION_VAR LibArchive_VERSION) + +In this case, the ``LibArchive`` package is considered to be found if +both ``LibArchive_LIBRARY`` and ``LibArchive_INCLUDE_DIR`` are valid. +Also the version of ``LibArchive`` will be checked by using the version +contained in ``LibArchive_VERSION``. Since no ``FAIL_MESSAGE`` is given, +the default messages will be printed. + +Another example for the full signature: + +.. code-block:: cmake + + find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4) + find_package_handle_standard_args(Automoc4 CONFIG_MODE) + +In this case, a ``FindAutmoc4.cmake`` module wraps a call to +``find_package(Automoc4 NO_MODULE)`` and adds an additional search +directory for ``automoc4``. Then the call to +``find_package_handle_standard_args`` produces a proper success/failure +message. + +.. command:: find_package_check_version + + .. versionadded:: 3.19 + + Helper function which can be used to check if a ```` is valid + against version-related arguments of :command:`find_package`. + + .. code-block:: cmake + + find_package_check_version( + [HANDLE_VERSION_RANGE] + [RESULT_MESSAGE_VARIABLE ] + ) + + The ```` will hold a boolean value giving the result of the check. + + The options are: + + ``HANDLE_VERSION_RANGE`` + Enable handling of a version range, if one is specified. Without this + option, a developer warning will be displayed if a version range is + specified. + + ``RESULT_MESSAGE_VARIABLE `` + Specify a variable to get back a message describing the result of the check. + +Example for the usage: + +.. code-block:: cmake + + find_package_check_version(1.2.3 result HANDLE_VERSION_RANGE + RESULT_MESSAGE_VARIABLE reason) + if (result) + message (STATUS "${reason}") + else() + message (FATAL_ERROR "${reason}") + endif() +#]=======================================================================] + +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageMessage.cmake) + + +cmake_policy(PUSH) +# numbers and boolean constants +cmake_policy (SET CMP0012 NEW) +# IN_LIST operator +cmake_policy (SET CMP0057 NEW) + + +# internal helper macro +macro(_FPHSA_FAILURE_MESSAGE _msg) + set (__msg "${_msg}") + if (FPHSA_REASON_FAILURE_MESSAGE) + string(APPEND __msg "\n Reason given by package: ${FPHSA_REASON_FAILURE_MESSAGE}\n") + endif() + if (${_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${__msg}") + else () + if (NOT ${_NAME}_FIND_QUIETLY) + message(STATUS "${__msg}") + endif () + endif () +endmacro() + + +# internal helper macro to generate the failure message when used in CONFIG_MODE: +macro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE) + # _CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found: + if(${_NAME}_CONFIG) + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: missing:${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})") + else() + # If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version. + # List them all in the error message: + if(${_NAME}_CONSIDERED_CONFIGS) + set(configsText "") + list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount) + math(EXPR configsCount "${configsCount} - 1") + foreach(currentConfigIndex RANGE ${configsCount}) + list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename) + list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version) + string(APPEND configsText "\n ${filename} (version ${version})") + endforeach() + if (${_NAME}_NOT_FOUND_MESSAGE) + if (FPHSA_REASON_FAILURE_MESSAGE) + string(PREPEND FPHSA_REASON_FAILURE_MESSAGE "${${_NAME}_NOT_FOUND_MESSAGE}\n ") + else() + set(FPHSA_REASON_FAILURE_MESSAGE "${${_NAME}_NOT_FOUND_MESSAGE}") + endif() + else() + string(APPEND configsText "\n") + endif() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:${configsText}") + + else() + # Simple case: No Config-file was found at all: + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}") + endif() + endif() +endmacro() + + +function(FIND_PACKAGE_CHECK_VERSION version result) + cmake_parse_arguments (PARSE_ARGV 2 FPCV "HANDLE_VERSION_RANGE;NO_AUTHOR_WARNING_VERSION_RANGE" "RESULT_MESSAGE_VARIABLE" "") + + if (FPCV_UNPARSED_ARGUMENTS) + message (FATAL_ERROR "find_package_check_version(): ${FPCV_UNPARSED_ARGUMENTS}: unexpected arguments") + endif() + if ("RESULT_MESSAGE_VARIABLE" IN_LIST FPCV_KEYWORDS_MISSING_VALUES) + message (FATAL_ERROR "find_package_check_version(): RESULT_MESSAGE_VARIABLE expects an argument") + endif() + + set (${result} FALSE PARENT_SCOPE) + if (FPCV_RESULT_MESSAGE_VARIABLE) + unset (${FPCV_RESULT_MESSAGE_VARIABLE} PARENT_SCOPE) + endif() + + if (_CMAKE_FPHSA_PACKAGE_NAME) + set (package "${_CMAKE_FPHSA_PACKAGE_NAME}") + elseif (CMAKE_FIND_PACKAGE_NAME) + set (package "${CMAKE_FIND_PACKAGE_NAME}") + else() + message (FATAL_ERROR "find_package_check_version(): Cannot be used outside a 'Find Module'") + endif() + + if (NOT FPCV_NO_AUTHOR_WARNING_VERSION_RANGE + AND ${package}_FIND_VERSION_RANGE AND NOT FPCV_HANDLE_VERSION_RANGE) + message(AUTHOR_WARNING + "`find_package()` specify a version range but the option " + "HANDLE_VERSION_RANGE` is not passed to `find_package_check_version()`. " + "Only the lower endpoint of the range will be used.") + endif() + + + set (version_ok FALSE) + unset (version_msg) + + if (FPCV_HANDLE_VERSION_RANGE AND ${package}_FIND_VERSION_RANGE) + if ((${package}_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" + AND version VERSION_GREATER_EQUAL ${package}_FIND_VERSION_MIN) + AND ((${package}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" + AND version VERSION_LESS_EQUAL ${package}_FIND_VERSION_MAX) + OR (${package}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" + AND version VERSION_LESS ${package}_FIND_VERSION_MAX))) + set (version_ok TRUE) + set(version_msg "(found suitable version \"${version}\", required range is \"${${package}_FIND_VERSION_RANGE}\")") + else() + set(version_msg "Found unsuitable version \"${version}\", required range is \"${${package}_FIND_VERSION_RANGE}\"") + endif() + elseif (DEFINED ${package}_FIND_VERSION) + if(${package}_FIND_VERSION_EXACT) # exact version required + # count the dots in the version string + string(REGEX REPLACE "[^.]" "" version_dots "${version}") + # add one dot because there is one dot more than there are components + string(LENGTH "${version_dots}." version_dots) + if (version_dots GREATER ${package}_FIND_VERSION_COUNT) + # Because of the C++ implementation of find_package() ${package}_FIND_VERSION_COUNT + # is at most 4 here. Therefore a simple lookup table is used. + if (${package}_FIND_VERSION_COUNT EQUAL 1) + set(version_regex "[^.]*") + elseif (${package}_FIND_VERSION_COUNT EQUAL 2) + set(version_regex "[^.]*\\.[^.]*") + elseif (${package}_FIND_VERSION_COUNT EQUAL 3) + set(version_regex "[^.]*\\.[^.]*\\.[^.]*") + else() + set(version_regex "[^.]*\\.[^.]*\\.[^.]*\\.[^.]*") + endif() + string(REGEX REPLACE "^(${version_regex})\\..*" "\\1" version_head "${version}") + if (NOT ${package}_FIND_VERSION VERSION_EQUAL version_head) + set(version_msg "Found unsuitable version \"${version}\", but required is exact version \"${${package}_FIND_VERSION}\"") + else () + set(version_ok TRUE) + set(version_msg "(found suitable exact version \"${_FOUND_VERSION}\")") + endif () + else () + if (NOT ${package}_FIND_VERSION VERSION_EQUAL version) + set(version_msg "Found unsuitable version \"${version}\", but required is exact version \"${${package}_FIND_VERSION}\"") + else () + set(version_ok TRUE) + set(version_msg "(found suitable exact version \"${version}\")") + endif () + endif () + else() # minimum version + if (${package}_FIND_VERSION VERSION_GREATER version) + set(version_msg "Found unsuitable version \"${version}\", but required is at least \"${${package}_FIND_VERSION}\"") + else() + set(version_ok TRUE) + set(version_msg "(found suitable version \"${version}\", minimum required is \"${${package}_FIND_VERSION}\")") + endif() + endif() + else () + set(version_ok TRUE) + set(version_msg "(found version \"${version}\")") + endif() + + set (${result} ${version_ok} PARENT_SCOPE) + if (FPCV_RESULT_MESSAGE_VARIABLE) + set (${FPCV_RESULT_MESSAGE_VARIABLE} "${version_msg}" PARENT_SCOPE) + endif() +endfunction() + + +function(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG) + + # Set up the arguments for `cmake_parse_arguments`. + set(options CONFIG_MODE HANDLE_COMPONENTS NAME_MISMATCHED HANDLE_VERSION_RANGE) + set(oneValueArgs FAIL_MESSAGE REASON_FAILURE_MESSAGE VERSION_VAR FOUND_VAR) + set(multiValueArgs REQUIRED_VARS) + + # Check whether we are in 'simple' or 'extended' mode: + set(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) + list(FIND _KEYWORDS_FOR_EXTENDED_MODE "${_FIRST_ARG}" INDEX) + + unset(FPHSA_NAME_MISMATCHED_override) + if (DEFINED FPHSA_NAME_MISMATCHED) + # If the variable NAME_MISMATCHED variable is set, error if it is passed as + # an argument. The former is for old signatures, the latter is for new + # signatures. + list(FIND ARGN "NAME_MISMATCHED" name_mismatched_idx) + if (NOT name_mismatched_idx EQUAL "-1") + message(FATAL_ERROR + "The `NAME_MISMATCHED` argument may only be specified by the argument or " + "the variable, not both.") + endif () + + # But use the variable if it is not an argument to avoid forcing minimum + # CMake version bumps for calling modules. + set(FPHSA_NAME_MISMATCHED_override "${FPHSA_NAME_MISMATCHED}") + endif () + + if(${INDEX} EQUAL -1) + set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG}) + set(FPHSA_REQUIRED_VARS ${ARGN}) + set(FPHSA_VERSION_VAR) + else() + cmake_parse_arguments(FPHSA "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) + + if(FPHSA_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \"${FPHSA_UNPARSED_ARGUMENTS}\"") + endif() + + if(NOT FPHSA_FAIL_MESSAGE) + set(FPHSA_FAIL_MESSAGE "DEFAULT_MSG") + endif() + + # In config-mode, we rely on the variable _CONFIG, which is set by find_package() + # when it successfully found the config-file, including version checking: + if(FPHSA_CONFIG_MODE) + list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG) + list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS) + set(FPHSA_VERSION_VAR ${_NAME}_VERSION) + endif() + + if(NOT FPHSA_REQUIRED_VARS AND NOT FPHSA_HANDLE_COMPONENTS) + message(FATAL_ERROR "No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()") + endif() + endif() + + if (DEFINED FPHSA_NAME_MISMATCHED_override) + set(FPHSA_NAME_MISMATCHED "${FPHSA_NAME_MISMATCHED_override}") + endif () + + if (DEFINED CMAKE_FIND_PACKAGE_NAME + AND NOT FPHSA_NAME_MISMATCHED + AND NOT _NAME STREQUAL CMAKE_FIND_PACKAGE_NAME) + message(AUTHOR_WARNING + "The package name passed to `find_package_handle_standard_args` " + "(${_NAME}) does not match the name of the calling package " + "(${CMAKE_FIND_PACKAGE_NAME}). This can lead to problems in calling " + "code that expects `find_package` result variables (e.g., `_FOUND`) " + "to follow a certain pattern.") + endif () + + if (${_NAME}_FIND_VERSION_RANGE AND NOT FPHSA_HANDLE_VERSION_RANGE) + message(AUTHOR_WARNING + "`find_package()` specify a version range but the module ${_NAME} does " + "not support this capability. Only the lower endpoint of the range " + "will be used.") + endif() + + # to propagate package name to FIND_PACKAGE_CHECK_VERSION + set(_CMAKE_FPHSA_PACKAGE_NAME "${_NAME}") + + # now that we collected all arguments, process them + + if("x${FPHSA_FAIL_MESSAGE}" STREQUAL "xDEFAULT_MSG") + set(FPHSA_FAIL_MESSAGE "Could NOT find ${_NAME}") + endif() + + if (FPHSA_REQUIRED_VARS) + list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR) + endif() + + string(TOUPPER ${_NAME} _NAME_UPPER) + string(TOLOWER ${_NAME} _NAME_LOWER) + + if(FPHSA_FOUND_VAR) + set(_FOUND_VAR_UPPER ${_NAME_UPPER}_FOUND) + set(_FOUND_VAR_MIXED ${_NAME}_FOUND) + if(FPHSA_FOUND_VAR STREQUAL _FOUND_VAR_MIXED OR FPHSA_FOUND_VAR STREQUAL _FOUND_VAR_UPPER) + set(_FOUND_VAR ${FPHSA_FOUND_VAR}) + else() + message(FATAL_ERROR "The argument for FOUND_VAR is \"${FPHSA_FOUND_VAR}\", but only \"${_FOUND_VAR_MIXED}\" and \"${_FOUND_VAR_UPPER}\" are valid names.") + endif() + else() + set(_FOUND_VAR ${_NAME_UPPER}_FOUND) + endif() + + # collect all variables which were not found, so they can be printed, so the + # user knows better what went wrong (#6375) + set(MISSING_VARS "") + set(DETAILS "") + # check if all passed variables are valid + set(FPHSA_FOUND_${_NAME} TRUE) + foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS}) + if(NOT ${_CURRENT_VAR}) + set(FPHSA_FOUND_${_NAME} FALSE) + string(APPEND MISSING_VARS " ${_CURRENT_VAR}") + else() + string(APPEND DETAILS "[${${_CURRENT_VAR}}]") + endif() + endforeach() + if(FPHSA_FOUND_${_NAME}) + set(${_NAME}_FOUND TRUE) + set(${_NAME_UPPER}_FOUND TRUE) + else() + set(${_NAME}_FOUND FALSE) + set(${_NAME_UPPER}_FOUND FALSE) + endif() + + # component handling + unset(FOUND_COMPONENTS_MSG) + unset(MISSING_COMPONENTS_MSG) + + if(FPHSA_HANDLE_COMPONENTS) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(${_NAME}_${comp}_FOUND) + + if(NOT DEFINED FOUND_COMPONENTS_MSG) + set(FOUND_COMPONENTS_MSG "found components:") + endif() + string(APPEND FOUND_COMPONENTS_MSG " ${comp}") + + else() + + if(NOT DEFINED MISSING_COMPONENTS_MSG) + set(MISSING_COMPONENTS_MSG "missing components:") + endif() + string(APPEND MISSING_COMPONENTS_MSG " ${comp}") + + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + string(APPEND MISSING_VARS " ${comp}") + endif() + + endif() + endforeach() + set(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}") + string(APPEND DETAILS "[c${COMPONENT_MSG}]") + endif() + + # version handling: + set(VERSION_MSG "") + set(VERSION_OK TRUE) + + # check with DEFINED here as the requested or found version may be "0" + if (DEFINED ${_NAME}_FIND_VERSION) + if(DEFINED ${FPHSA_VERSION_VAR}) + set(_FOUND_VERSION ${${FPHSA_VERSION_VAR}}) + if (FPHSA_HANDLE_VERSION_RANGE) + set (FPCV_HANDLE_VERSION_RANGE HANDLE_VERSION_RANGE) + else() + set(FPCV_HANDLE_VERSION_RANGE NO_AUTHOR_WARNING_VERSION_RANGE) + endif() + find_package_check_version ("${_FOUND_VERSION}" VERSION_OK RESULT_MESSAGE_VARIABLE VERSION_MSG + ${FPCV_HANDLE_VERSION_RANGE}) + else() + # if the package was not found, but a version was given, add that to the output: + if(${_NAME}_FIND_VERSION_EXACT) + set(VERSION_MSG "(Required is exact version \"${${_NAME}_FIND_VERSION}\")") + elseif (FPHSA_HANDLE_VERSION_RANGE AND ${_NAME}_FIND_VERSION_RANGE) + set(VERSION_MSG "(Required is version range \"${${_NAME}_FIND_VERSION_RANGE}\")") + else() + set(VERSION_MSG "(Required is at least version \"${${_NAME}_FIND_VERSION}\")") + endif() + endif() + else () + # Check with DEFINED as the found version may be 0. + if(DEFINED ${FPHSA_VERSION_VAR}) + set(VERSION_MSG "(found version \"${${FPHSA_VERSION_VAR}}\")") + endif() + endif () + + if(VERSION_OK) + string(APPEND DETAILS "[v${${FPHSA_VERSION_VAR}}(${${_NAME}_FIND_VERSION})]") + else() + set(${_NAME}_FOUND FALSE) + endif() + + + # print the result: + if (${_NAME}_FOUND) + FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}") + else () + + if(FPHSA_CONFIG_MODE) + _FPHSA_HANDLE_FAILURE_CONFIG_MODE() + else() + if(NOT VERSION_OK) + set(RESULT_MSG) + if (_FIRST_REQUIRED_VAR) + string (APPEND RESULT_MSG "found ${${_FIRST_REQUIRED_VAR}}") + endif() + if (COMPONENT_MSG) + if (RESULT_MSG) + string (APPEND RESULT_MSG ", ") + endif() + string (APPEND RESULT_MSG "${FOUND_COMPONENTS_MSG}") + endif() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (${RESULT_MSG})") + else() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} (missing:${MISSING_VARS}) ${VERSION_MSG}") + endif() + endif() + + endif () + + set(${_NAME}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) + set(${_NAME_UPPER}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) +endfunction() + + +cmake_policy(POP) diff --git a/scenario/cmake/FindPackageMessage.cmake b/scenario/cmake/FindPackageMessage.cmake new file mode 100644 index 000000000..0628b9816 --- /dev/null +++ b/scenario/cmake/FindPackageMessage.cmake @@ -0,0 +1,48 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPackageMessage +------------------ + +.. code-block:: cmake + + find_package_message( "message for user" "find result details") + +This function is intended to be used in FindXXX.cmake modules files. +It will print a message once for each unique find result. This is +useful for telling the user where a package was found. The first +argument specifies the name (XXX) of the package. The second argument +specifies the message to display. The third argument lists details +about the find result so that if they change the message will be +displayed again. The macro also obeys the QUIET argument to the +find_package command. + +Example: + +.. code-block:: cmake + + if(X11_FOUND) + find_package_message(X11 "Found X11: ${X11_X11_LIB}" + "[${X11_X11_LIB}][${X11_INCLUDE_DIR}]") + else() + ... + endif() +#]=======================================================================] + +function(find_package_message pkg msg details) + # Avoid printing a message repeatedly for the same find result. + if(NOT ${pkg}_FIND_QUIETLY) + string(REPLACE "\n" "" details "${details}") + set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg}) + if(NOT "${details}" STREQUAL "${${DETAILS_VAR}}") + # The message has not yet been printed. + message(STATUS "${msg}") + + # Save the find details in the cache to avoid printing the same + # message again. + set("${DETAILS_VAR}" "${details}" + CACHE INTERNAL "Details about finding ${pkg}") + endif() + endif() +endfunction() diff --git a/scenario/cmake/FindPython/Support.cmake b/scenario/cmake/FindPython/Support.cmake new file mode 100644 index 000000000..8e70e111b --- /dev/null +++ b/scenario/cmake/FindPython/Support.cmake @@ -0,0 +1,3369 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +# +# This file is a "template" file used by various FindPython modules. +# + +# +# Initial configuration +# + +cmake_policy(PUSH) +# numbers and boolean constants +cmake_policy (SET CMP0012 NEW) +# IN_LIST operator +cmake_policy (SET CMP0057 NEW) + +if (NOT DEFINED _PYTHON_PREFIX) + message (FATAL_ERROR "FindPython: INTERNAL ERROR") +endif() +if (NOT DEFINED _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + message (FATAL_ERROR "FindPython: INTERNAL ERROR") +endif() +if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL "3") + set(_${_PYTHON_PREFIX}_VERSIONS 3.10 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) +elseif (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL "2") + set(_${_PYTHON_PREFIX}_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) +else() + message (FATAL_ERROR "FindPython: INTERNAL ERROR") +endif() + +get_property(_${_PYTHON_PREFIX}_CMAKE_ROLE GLOBAL PROPERTY CMAKE_ROLE) + +include (${CMAKE_CURRENT_LIST_DIR}/../FindPackageHandleStandardArgs.cmake) + +# +# helper commands +# +macro (_PYTHON_DISPLAY_FAILURE _PYTHON_MSG) + if (${_PYTHON_PREFIX}_FIND_REQUIRED) + message (FATAL_ERROR "${_PYTHON_MSG}") + else() + if (NOT ${_PYTHON_PREFIX}_FIND_QUIETLY) + message(STATUS "${_PYTHON_MSG}") + endif () + endif() + + set (${_PYTHON_PREFIX}_FOUND FALSE) + string (TOUPPER "${_PYTHON_PREFIX}" _${_PYTHON_PREFIX}_UPPER_PREFIX) + set (${_PYTHON_UPPER_PREFIX}_FOUND FALSE) +endmacro() + + +function (_PYTHON_MARK_AS_INTERNAL) + foreach (var IN LISTS ARGV) + if (DEFINED CACHE{${var}}) + set_property (CACHE ${var} PROPERTY TYPE INTERNAL) + endif() + endforeach() +endfunction() + + +macro (_PYTHON_SELECT_LIBRARY_CONFIGURATIONS _PYTHON_BASENAME) + if(NOT DEFINED ${_PYTHON_BASENAME}_LIBRARY_RELEASE) + set(${_PYTHON_BASENAME}_LIBRARY_RELEASE "${_PYTHON_BASENAME}_LIBRARY_RELEASE-NOTFOUND") + endif() + if(NOT DEFINED ${_PYTHON_BASENAME}_LIBRARY_DEBUG) + set(${_PYTHON_BASENAME}_LIBRARY_DEBUG "${_PYTHON_BASENAME}_LIBRARY_DEBUG-NOTFOUND") + endif() + + get_property(_PYTHON_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if (${_PYTHON_BASENAME}_LIBRARY_DEBUG AND ${_PYTHON_BASENAME}_LIBRARY_RELEASE AND + NOT ${_PYTHON_BASENAME}_LIBRARY_DEBUG STREQUAL ${_PYTHON_BASENAME}_LIBRARY_RELEASE AND + (_PYTHON_isMultiConfig OR CMAKE_BUILD_TYPE)) + # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for + # single-config generators, set optimized and debug libraries + set (${_PYTHON_BASENAME}_LIBRARIES "") + foreach (_PYTHON_libname IN LISTS ${_PYTHON_BASENAME}_LIBRARY_RELEASE) + list( APPEND ${_PYTHON_BASENAME}_LIBRARIES optimized "${_PYTHON_libname}") + endforeach() + foreach (_PYTHON_libname IN LISTS ${_PYTHON_BASENAME}_LIBRARY_DEBUG) + list( APPEND ${_PYTHON_BASENAME}_LIBRARIES debug "${_PYTHON_libname}") + endforeach() + elseif (${_PYTHON_BASENAME}_LIBRARY_RELEASE) + set (${_PYTHON_BASENAME}_LIBRARIES "${${_PYTHON_BASENAME}_LIBRARY_RELEASE}") + elseif (${_PYTHON_BASENAME}_LIBRARY_DEBUG) + set (${_PYTHON_BASENAME}_LIBRARIES "${${_PYTHON_BASENAME}_LIBRARY_DEBUG}") + else() + set (${_PYTHON_BASENAME}_LIBRARIES "${_PYTHON_BASENAME}_LIBRARY-NOTFOUND") + endif() +endmacro() + + +macro (_PYTHON_FIND_FRAMEWORKS) + if (CMAKE_HOST_APPLE OR APPLE) + file(TO_CMAKE_PATH "$ENV{CMAKE_FRAMEWORK_PATH}" _pff_CMAKE_FRAMEWORK_PATH) + set (_pff_frameworks ${CMAKE_FRAMEWORK_PATH} + ${_pff_CMAKE_FRAMEWORK_PATH} + ~/Library/Frameworks + /usr/local/Frameworks + ${CMAKE_SYSTEM_FRAMEWORK_PATH}) + list (REMOVE_DUPLICATES _pff_frameworks) + foreach (_pff_implementation IN LISTS _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + unset (_${_PYTHON_PREFIX}_${_pff_implementation}_FRAMEWORKS) + if (_pff_implementation STREQUAL "CPython") + foreach (_pff_framework IN LISTS _pff_frameworks) + if (EXISTS ${_pff_framework}/Python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}.framework) + list (APPEND _${_PYTHON_PREFIX}_${_pff_implementation}_FRAMEWORKS ${_pff_framework}/Python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}.framework) + endif() + if (EXISTS ${_pff_framework}/Python.framework) + list (APPEND _${_PYTHON_PREFIX}_${_pff_implementation}_FRAMEWORKS ${_pff_framework}/Python.framework) + endif() + endforeach() + elseif (_pff_implementation STREQUAL "IronPython") + foreach (_pff_framework IN LISTS _pff_frameworks) + if (EXISTS ${_pff_framework}/IronPython.framework) + list (APPEND _${_PYTHON_PREFIX}_${_pff_implementation}_FRAMEWORKS ${_pff_framework}/IronPython.framework) + endif() + endforeach() + endif() + endforeach() + unset (_pff_implementation) + unset (_pff_frameworks) + unset (_pff_framework) + endif() +endmacro() + +function (_PYTHON_GET_FRAMEWORKS _PYTHON_PGF_FRAMEWORK_PATHS) + cmake_parse_arguments (PARSE_ARGV 1 _PGF "" "" "IMPLEMENTATIONS;VERSION") + + if (NOT _PGF_IMPLEMENTATIONS) + set (_PGF_IMPLEMENTATIONS ${_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS}) + endif() + + set (framework_paths) + + foreach (implementation IN LISTS _PGF_IMPLEMENTATIONS) + if (implementation STREQUAL "CPython") + foreach (version IN LISTS _PGF_VERSION) + foreach (framework IN LISTS _${_PYTHON_PREFIX}_${implementation}_FRAMEWORKS) + if (EXISTS "${framework}/Versions/${version}") + list (APPEND framework_paths "${framework}/Versions/${version}") + endif() + endforeach() + endforeach() + elseif (implementation STREQUAL "IronPython") + foreach (version IN LISTS _PGF_VERSION) + foreach (framework IN LISTS _${_PYTHON_PREFIX}_${implementation}_FRAMEWORKS) + # pick-up all available versions + file (GLOB versions LIST_DIRECTORIES true RELATIVE "${framework}/Versions/" + "${framework}/Versions/${version}*") + list (SORT versions ORDER DESCENDING) + list (TRANSFORM versions PREPEND "${framework}/Versions/") + list (APPEND framework_paths ${versions}) + endforeach() + endforeach() + endif() + endforeach() + + set (${_PYTHON_PGF_FRAMEWORK_PATHS} ${framework_paths} PARENT_SCOPE) +endfunction() + +function (_PYTHON_GET_REGISTRIES _PYTHON_PGR_REGISTRY_PATHS) + cmake_parse_arguments (PARSE_ARGV 1 _PGR "" "" "IMPLEMENTATIONS;VERSION") + + if (NOT _PGR_IMPLEMENTATIONS) + set (_PGR_IMPLEMENTATIONS ${_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS}) + endif() + + set (registries) + + foreach (implementation IN LISTS _PGR_IMPLEMENTATIONS) + if (implementation STREQUAL "CPython") + foreach (version IN LISTS _PGR_VERSION) + string (REPLACE "." "" version_no_dots ${version}) + list (APPEND registries + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${version}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${version}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath]) + if (version VERSION_GREATER_EQUAL "3.5") + get_filename_component (arch "[HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\${version};SysArchitecture]" NAME) + if (arch MATCHES "(${_${_PYTHON_PREFIX}_ARCH}|${_${_PYTHON_PREFIX}_ARCH2})bit") + list (APPEND registries + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${version}\\InstallPath]) + endif() + else() + list (APPEND registries + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${version}\\InstallPath]) + endif() + list (APPEND registries + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${version}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${version}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${version}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath]) + endforeach() + elseif (implementation STREQUAL "IronPython") + foreach (version IN LISTS _PGR_VERSION) + list (APPEND registries [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${version}\\InstallPath]) + endforeach() + endif() + endforeach() + + set (${_PYTHON_PGR_REGISTRY_PATHS} "${registries}" PARENT_SCOPE) +endfunction() + + +function (_PYTHON_GET_ABIFLAGS _PGABIFLAGS) + set (abiflags) + list (GET _${_PYTHON_PREFIX}_FIND_ABI 0 pydebug) + list (GET _${_PYTHON_PREFIX}_FIND_ABI 1 pymalloc) + list (GET _${_PYTHON_PREFIX}_FIND_ABI 2 unicode) + + if (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ANY") + set (abiflags "mu" "m" "u" "") + elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "ON") + set (abiflags "mu" "u") + elseif (pymalloc STREQUAL "ANY" AND unicode STREQUAL "OFF") + set (abiflags "m" "") + elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY") + set (abiflags "mu" "m") + elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ON") + set (abiflags "mu") + elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "OFF") + set (abiflags "m") + elseif (pymalloc STREQUAL "ON" AND unicode STREQUAL "ANY") + set (abiflags "u" "") + elseif (pymalloc STREQUAL "OFF" AND unicode STREQUAL "ON") + set (abiflags "u") + endif() + + if (pydebug STREQUAL "ON") + if (abiflags) + list (TRANSFORM abiflags PREPEND "d") + else() + set (abiflags "d") + endif() + elseif (pydebug STREQUAL "ANY") + if (abiflags) + set (flags "${abiflags}") + list (TRANSFORM flags PREPEND "d") + list (APPEND abiflags "${flags}") + else() + set (abiflags "" "d") + endif() + endif() + + set (${_PGABIFLAGS} "${abiflags}" PARENT_SCOPE) +endfunction() + +function (_PYTHON_GET_PATH_SUFFIXES _PYTHON_PGPS_PATH_SUFFIXES) + cmake_parse_arguments (PARSE_ARGV 1 _PGPS "INTERPRETER;COMPILER;LIBRARY;INCLUDE" "" "IMPLEMENTATIONS;VERSION") + + if (NOT _PGPS_IMPLEMENTATIONS) + set (_PGPS_IMPLEMENTATIONS ${_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS}) + endif() + + if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS) + set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}") + else() + set (abi "mu" "m" "u" "") + endif() + + set (path_suffixes) + + foreach (implementation IN LISTS _PGPS_IMPLEMENTATIONS) + if (implementation STREQUAL "CPython") + if (_PGPS_INTERPRETER) + list (APPEND path_suffixes bin Scripts) + else() + foreach (version IN LISTS _PGPS_VERSION) + if (_PGPS_LIBRARY) + if (CMAKE_LIBRARY_ARCHITECTURE) + list (APPEND path_suffixes lib/${CMAKE_LIBRARY_ARCHITECTURE}) + endif() + list (APPEND path_suffixes lib libs) + + if (CMAKE_LIBRARY_ARCHITECTURE) + set (suffixes "${abi}") + if (suffixes) + list (TRANSFORM suffixes PREPEND "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") + list (TRANSFORM suffixes APPEND "-${CMAKE_LIBRARY_ARCHITECTURE}") + else() + set (suffixes "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}-${CMAKE_LIBRARY_ARCHITECTURE}") + endif() + list (APPEND path_suffixes ${suffixes}) + endif() + set (suffixes "${abi}") + if (suffixes) + list (TRANSFORM suffixes PREPEND "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") + else() + set (suffixes "lib/python${_PGPS_VERSION}/config-${_PGPS_VERSION}") + endif() + list (APPEND path_suffixes ${suffixes}) + elseif (_PGPS_INCLUDE) + set (suffixes "${abi}") + if (suffixes) + list (TRANSFORM suffixes PREPEND "include/python${_PGPS_VERSION}") + else() + set (suffixes "include/python${_PGPS_VERSION}") + endif() + list (APPEND path_suffixes ${suffixes} include) + endif() + endforeach() + endif() + elseif (implementation STREQUAL "IronPython") + if (_PGPS_INTERPRETER OR _PGPS_COMPILER) + foreach (version IN LISTS _PGPS_VERSION) + list (APPEND path_suffixes "share/ironpython${version}") + endforeach() + list (APPEND path_suffixes ${_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES}) + endif() + elseif (implementation STREQUAL "PyPy") + if (_PGPS_INTERPRETER) + list (APPEND path_suffixes ${_${_PYTHON_PREFIX}_PYPY_EXECUTABLE_PATH_SUFFIXES}) + elseif (_PGPS_LIBRARY) + list (APPEND path_suffixes ${_${_PYTHON_PREFIX}_PYPY_LIBRARY_PATH_SUFFIXES}) + elseif (_PGPS_INCLUDE) + list (APPEND path_suffixes ${_${_PYTHON_PREFIX}_PYPY_INCLUDE_PATH_SUFFIXES}) + endif() + endif() + endforeach() + list (REMOVE_DUPLICATES path_suffixes) + + set (${_PYTHON_PGPS_PATH_SUFFIXES} ${path_suffixes} PARENT_SCOPE) +endfunction() + +function (_PYTHON_GET_NAMES _PYTHON_PGN_NAMES) + cmake_parse_arguments (PARSE_ARGV 1 _PGN "POSIX;INTERPRETER;COMPILER;CONFIG;LIBRARY;WIN32;DEBUG" "" "IMPLEMENTATIONS;VERSION") + + if (NOT _PGN_IMPLEMENTATIONS) + set (_PGN_IMPLEMENTATIONS ${_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS}) + endif() + + set (names) + + foreach (implementation IN LISTS _PGN_IMPLEMENTATIONS) + if (implementation STREQUAL "CPython") + if (_PGN_INTERPRETER AND _${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES STREQUAL "FIRST") + list (APPEND names python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} python) + endif() + foreach (version IN LISTS _PGN_VERSION) + if (_PGN_WIN32) + string (REPLACE "." "" version_no_dots ${version}) + + set (name python${version_no_dots}) + if (_PGN_DEBUG) + string (APPEND name "_d") + endif() + + list (APPEND names "${name}") + endif() + + if (_PGN_POSIX) + if (DEFINED _${_PYTHON_PREFIX}_ABIFLAGS) + set (abi "${_${_PYTHON_PREFIX}_ABIFLAGS}") + else() + if (_PGN_INTERPRETER OR _PGN_CONFIG) + set (abi "") + else() + set (abi "mu" "m" "u" "") + endif() + endif() + + if (abi) + if (_PGN_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) + set (abinames "${abi}") + list (TRANSFORM abinames PREPEND "${CMAKE_LIBRARY_ARCHITECTURE}-python${version}") + list (TRANSFORM abinames APPEND "-config") + list (APPEND names ${abinames}) + endif() + set (abinames "${abi}") + list (TRANSFORM abinames PREPEND "python${version}") + if (_PGN_CONFIG) + list (TRANSFORM abinames APPEND "-config") + endif() + list (APPEND names ${abinames}) + else() + unset (abinames) + if (_PGN_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) + set (abinames "${CMAKE_LIBRARY_ARCHITECTURE}-python${version}") + endif() + list (APPEND abinames "python${version}") + if (_PGN_CONFIG) + list (TRANSFORM abinames APPEND "-config") + endif() + list (APPEND names ${abinames}) + endif() + endif() + endforeach() + if (_PGN_INTERPRETER AND _${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES STREQUAL "LAST") + list (APPEND names python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} python) + endif() + elseif (implementation STREQUAL "IronPython") + if (_PGN_INTERPRETER) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Do not use wrapper script on Linux because it is buggy: -c interpreter option cannot be used + foreach (version IN LISTS _PGN_VERSION) + list (APPEND names "ipy${version}") + endforeach() + endif() + list (APPEND names ${_${_PYTHON_PREFIX}_IRON_PYTHON_INTERPRETER_NAMES}) + elseif (_PGN_COMPILER) + list (APPEND names ${_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_NAMES}) + endif() + elseif (implementation STREQUAL "PyPy") + if (_PGN_INTERPRETER) + list (APPEND names ${_${_PYTHON_PREFIX}_PYPY_NAMES}) + elseif (_PGN_LIBRARY) + if (_PGN_WIN32) + foreach (version IN LISTS _PGN_VERSION) + string (REPLACE "." "" version_no_dots ${version}) + + set (name "python${version_no_dots}") + if (_PGN_DEBUG) + string (APPEND name "_d") + endif() + list (APPEND names "${name}") + endforeach() + endif() + list (APPEND names ${_${_PYTHON_PREFIX}_PYPY_LIB_NAMES}) + endif() + endif() + endforeach() + + set (${_PYTHON_PGN_NAMES} ${names} PARENT_SCOPE) +endfunction() + +function (_PYTHON_GET_CONFIG_VAR _PYTHON_PGCV_VALUE NAME) + unset (${_PYTHON_PGCV_VALUE} PARENT_SCOPE) + + if (NOT NAME MATCHES "^(PREFIX|ABIFLAGS|CONFIGDIR|INCLUDES|LIBS|SOABI)$") + return() + endif() + + if (_${_PYTHON_PREFIX}_CONFIG) + if (NAME STREQUAL "SOABI") + set (config_flag "--extension-suffix") + else() + set (config_flag "--${NAME}") + endif() + string (TOLOWER "${config_flag}" config_flag) + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" ${config_flag} + RESULT_VARIABLE _result + OUTPUT_VARIABLE _values + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_result) + unset (_values) + else() + if (NAME STREQUAL "INCLUDES") + # do some clean-up + string (REGEX MATCHALL "(-I|-iwithsysroot)[ ]*[^ ]+" _values "${_values}") + string (REGEX REPLACE "(-I|-iwithsysroot)[ ]*" "" _values "${_values}") + list (REMOVE_DUPLICATES _values) + elseif (NAME STREQUAL "SOABI") + # clean-up: remove prefix character and suffix + if (_values MATCHES "^(\\.${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.so|\\.pyd)$") + set(_values "") + else() + string (REGEX REPLACE "^[.-](.+)(${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.(so|pyd))$" "\\1" _values "${_values}") + endif() + endif() + endif() + endif() + + if (_${_PYTHON_PREFIX}_EXECUTABLE AND NOT CMAKE_CROSSCOMPILING) + if (NAME STREQUAL "PREFIX") + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.PREFIX,sysconfig.EXEC_PREFIX,sysconfig.BASE_EXEC_PREFIX]))\nexcept Exception:\n import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_config_var('base') or '', sysconfig.get_config_var('installed_base') or '']))" + RESULT_VARIABLE _result + OUTPUT_VARIABLE _values + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_result) + unset (_values) + else() + list (REMOVE_DUPLICATES _values) + endif() + elseif (NAME STREQUAL "INCLUDES") + if (WIN32) + set (_scheme "nt") + else() + set (_scheme "posix_prefix") + endif() + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_python_inc(plat_specific=True),sysconfig.get_python_inc(plat_specific=False)]))\nexcept Exception:\n import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_path('platinclude'),sysconfig.get_path('platinclude','${_scheme}'),sysconfig.get_path('include'),sysconfig.get_path('include','${_scheme}')]))" + RESULT_VARIABLE _result + OUTPUT_VARIABLE _values + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_result) + unset (_values) + else() + list (REMOVE_DUPLICATES _values) + endif() + elseif (NAME STREQUAL "SOABI") + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_config_var('SOABI') or '',sysconfig.get_config_var('EXT_SUFFIX') or '',sysconfig.get_config_var('SO') or '']))\nexcept Exception:\n import sysconfig;sys.stdout.write(';'.join([sysconfig.get_config_var('SOABI') or '',sysconfig.get_config_var('EXT_SUFFIX') or '',sysconfig.get_config_var('SO') or '']))" + RESULT_VARIABLE _result + OUTPUT_VARIABLE _soabi + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_result) + unset (_values) + else() + foreach (_item IN LISTS _soabi) + if (_item) + set (_values "${_item}") + break() + endif() + endforeach() + if (_values) + # clean-up: remove prefix character and suffix + if (_values MATCHES "^(\\.${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.so|\\.pyd)$") + set(_values "") + else() + string (REGEX REPLACE "^[.-](.+)(${CMAKE_SHARED_LIBRARY_SUFFIX}|\\.(so|pyd))$" "\\1" _values "${_values}") + endif() + endif() + endif() + else() + set (config_flag "${NAME}") + if (NAME STREQUAL "CONFIGDIR") + set (config_flag "LIBPL") + endif() + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(sysconfig.get_config_var('${config_flag}'))\nexcept Exception:\n import sysconfig\n sys.stdout.write(sysconfig.get_config_var('${config_flag}'))" + RESULT_VARIABLE _result + OUTPUT_VARIABLE _values + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_result) + unset (_values) + endif() + endif() + endif() + + if (NAME STREQUAL "ABIFLAGS" OR NAME STREQUAL "SOABI") + set (${_PYTHON_PGCV_VALUE} "${_values}" PARENT_SCOPE) + return() + endif() + + if (NOT _values OR _values STREQUAL "None") + return() + endif() + + if (NAME STREQUAL "LIBS") + # do some clean-up + string (REGEX MATCHALL "-(l|framework)[ ]*[^ ]+" _values "${_values}") + # remove elements relative to python library itself + list (FILTER _values EXCLUDE REGEX "-lpython") + list (REMOVE_DUPLICATES _values) + endif() + + if (WIN32 AND NAME MATCHES "^(PREFIX|CONFIGDIR|INCLUDES)$") + file (TO_CMAKE_PATH "${_values}" _values) + endif() + + set (${_PYTHON_PGCV_VALUE} "${_values}" PARENT_SCOPE) +endfunction() + +function (_PYTHON_GET_VERSION) + cmake_parse_arguments (PARSE_ARGV 0 _PGV "LIBRARY;INCLUDE" "PREFIX" "") + + unset (${_PGV_PREFIX}VERSION PARENT_SCOPE) + unset (${_PGV_PREFIX}VERSION_MAJOR PARENT_SCOPE) + unset (${_PGV_PREFIX}VERSION_MINOR PARENT_SCOPE) + unset (${_PGV_PREFIX}VERSION_PATCH PARENT_SCOPE) + unset (${_PGV_PREFIX}ABI PARENT_SCOPE) + + if (_PGV_LIBRARY) + # retrieve version and abi from library name + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) + get_filename_component (library_name "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" NAME) + # extract version from library name + if (library_name MATCHES "python([23])([0-9]+)") + set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE) + set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) + elseif (library_name MATCHES "python([23])\\.([0-9]+)([dmu]*)") + set (${_PGV_PREFIX}VERSION_MAJOR "${CMAKE_MATCH_1}" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION_MINOR "${CMAKE_MATCH_2}" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}" PARENT_SCOPE) + set (${_PGV_PREFIX}ABI "${CMAKE_MATCH_3}" PARENT_SCOPE) + elseif (library_name MATCHES "pypy(3)?-c") + set (version "${CMAKE_MATCH_1}") + if (version EQUAL "3") + set (${_PGV_PREFIX}VERSION_MAJOR "3" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION "3" PARENT_SCOPE) + else() + set (${_PGV_PREFIX}VERSION_MAJOR "2" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION "2" PARENT_SCOPE) + endif() + set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) + endif() + endif() + else() + if (_${_PYTHON_PREFIX}_INCLUDE_DIR) + # retrieve version from header file + file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/patchlevel.h" version + REGEX "^#define[ \t]+PY_VERSION[ \t]+\"[^\"]+\"") + string (REGEX REPLACE "^#define[ \t]+PY_VERSION[ \t]+\"([^\"]+)\".*" "\\1" + version "${version}") + string (REGEX MATCHALL "[0-9]+" versions "${version}") + list (GET versions 0 version_major) + list (GET versions 1 version_minor) + list (GET versions 2 version_patch) + + set (${_PGV_PREFIX}VERSION "${version_major}.${version_minor}.${version_patch}" PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION_MAJOR ${version_major} PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION_MINOR ${version_minor} PARENT_SCOPE) + set (${_PGV_PREFIX}VERSION_PATCH ${version_patch} PARENT_SCOPE) + + # compute ABI flags + if (version_major VERSION_GREATER "2") + file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/pyconfig.h" config REGEX "(Py_DEBUG|WITH_PYMALLOC|Py_UNICODE_SIZE|MS_WIN32)") + set (abi) + if (config MATCHES "#[ ]*define[ ]+MS_WIN32") + # ABI not used on Windows + set (abi "") + else() + if (NOT config) + # pyconfig.h can be a wrapper to a platform specific pyconfig.h + # In this case, try to identify ABI from include directory + if (_${_PYTHON_PREFIX}_INCLUDE_DIR MATCHES "python${version_major}\\.${version_minor}+([dmu]*)") + set (abi "${CMAKE_MATCH_1}") + else() + set (abi "") + endif() + else() + if (config MATCHES "#[ ]*define[ ]+Py_DEBUG[ ]+1") + string (APPEND abi "d") + endif() + if (config MATCHES "#[ ]*define[ ]+WITH_PYMALLOC[ ]+1") + string (APPEND abi "m") + endif() + if (config MATCHES "#[ ]*define[ ]+Py_UNICODE_SIZE[ ]+4") + string (APPEND abi "u") + endif() + endif() + set (${_PGV_PREFIX}ABI "${abi}" PARENT_SCOPE) + endif() + else() + # ABI not supported + set (${_PGV_PREFIX}ABI "" PARENT_SCOPE) + endif() + endif() + endif() +endfunction() + +function (_PYTHON_GET_LAUNCHER _PYTHON_PGL_NAME) + cmake_parse_arguments (PARSE_ARGV 1 _PGL "INTERPRETER;COMPILER" "" "") + + unset ({_PYTHON_PGL_NAME} PARENT_SCOPE) + + if ((_PGL_INTERPRETER AND NOT _${_PYTHON_PREFIX}_EXECUTABLE) + OR (_PGL_COMPILER AND NOT _${_PYTHON_PREFIX}_COMPILER)) + return() + endif() + + if ("IronPython" IN_LIST _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS + AND NOT SYSTEM_NAME MATCHES "Windows|Linux") + if (_PGL_INTERPRETER) + get_filename_component (name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME) + get_filename_component (ext "${_${_PYTHON_PREFIX}_EXECUTABLE}" LAST_EXT) + if (name IN_LIST _${_PYTHON_PREFIX}_IRON_PYTHON_INTERPRETER_NAMES + AND ext STREQUAL ".exe") + set (${_PYTHON_PGL_NAME} "${${_PYTHON_PREFIX}_DOTNET_LAUNCHER}" PARENT_SCOPE) + endif() + else() + get_filename_component (name "${_${_PYTHON_PREFIX}_COMPILER}" NAME) + get_filename_component (ext "${_${_PYTHON_PREFIX}_COMPILER}" LAST_EXT) + if (name IN_LIST _${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_NAMES + AND ext STREQUAL ".exe") + set (${_PYTHON_PGL_NAME} "${${_PYTHON_PREFIX}_DOTNET_LAUNCHER}" PARENT_SCOPE) + endif() + endif() + endif() +endfunction() + + +function (_PYTHON_VALIDATE_INTERPRETER) + if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) + return() + endif() + + cmake_parse_arguments (PARSE_ARGV 0 _PVI "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "") + + if (_PVI_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_EXECUTABLE}") + # interpreter does not exist anymore + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot find the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + + _python_get_launcher (launcher INTERPRETER) + + # validate ABI compatibility + if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI) + execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write(sys.abiflags)" + RESULT_VARIABLE result + OUTPUT_VARIABLE abi + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (result) + # assume ABI is not supported + set (abi "") + endif() + if (NOT abi IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) + # incompatible ABI + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong ABI for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + endif() + + if (_PVI_IN_RANGE OR _PVI_VERSION) + # retrieve full version + execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))" + RESULT_VARIABLE result + OUTPUT_VARIABLE version + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (result) + # interpreter is not usable + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + + if (_PVI_VERSION) + # check against specified version + ## compute number of components for version + string (REGEX REPLACE "[^.]" "" dots "${_PVI_VERSION}") + ## add one dot because there is one dot less than there are components + string (LENGTH "${dots}." count) + if (count GREATER 3) + set (count 3) + endif() + set (version_regex "^[0-9]+") + if (count EQUAL 3) + string (APPEND version_regex "\\.[0-9]+\\.[0-9]+") + elseif (count EQUAL 2) + string (APPEND version_regex "\\.[0-9]+") + endif() + # extract needed range + string (REGEX MATCH "${version_regex}" version "${version}") + + if (_PVI_EXACT AND NOT version VERSION_EQUAL _PVI_VERSION) + # interpreter has wrong version + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + else() + # check that version is OK + string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}") + string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${_PVI_VERSION}") + if (NOT major_version VERSION_EQUAL expected_major_version + OR NOT version VERSION_GREATER_EQUAL _PVI_VERSION) + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + endif() + endif() + + if (_PVI_IN_RANGE) + # check if version is in the requested range + find_package_check_version ("${version}" in_range HANDLE_VERSION_RANGE) + if (NOT in_range) + # interpreter has invalid version + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + endif() + else() + get_filename_component (python_name "${_${_PYTHON_PREFIX}_EXECUTABLE}" NAME) + if (NOT python_name STREQUAL "python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}${CMAKE_EXECUTABLE_SUFFIX}") + # executable found do not have version in name + # ensure major version is OK + execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write(str(sys.version_info[0]))" + RESULT_VARIABLE result + OUTPUT_VARIABLE version + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (result OR NOT version EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + # interpreter not usable or has wrong major version + if (result) + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + else() + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong major version for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + endif() + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + endif() + endif() + + if (CMAKE_SIZEOF_VOID_P AND ("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + OR "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + AND NOT CMAKE_CROSSCOMPILING) + # In this case, interpreter must have same architecture as environment + execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys, struct; sys.stdout.write(str(struct.calcsize(\"P\")))" + RESULT_VARIABLE result + OUTPUT_VARIABLE size + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (result OR NOT size EQUAL CMAKE_SIZEOF_VOID_P) + # interpreter not usable or has wrong architecture + if (result) + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + else() + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Wrong architecture for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"" PARENT_SCOPE) + endif() + set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND") + return() + endif() + endif() +endfunction() + + +function (_PYTHON_VALIDATE_COMPILER) + if (NOT _${_PYTHON_PREFIX}_COMPILER) + return() + endif() + + cmake_parse_arguments (PARSE_ARGV 0 _PVC "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "") + + if (_PVC_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_COMPILER}") + # Compiler does not exist anymore + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot find the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") + return() + endif() + + _python_get_launcher (launcher COMPILER) + + # retrieve python environment version from compiler + set (working_dir "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") + file (WRITE "${working_dir}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))\n") + execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_COMPILER}" + ${_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS} + /target:exe /embed "${working_dir}/version.py" + WORKING_DIRECTORY "${working_dir}" + OUTPUT_QUIET + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + get_filename_component (ir_dir "${_${_PYTHON_PREFIX}_COMPILER}" DIRECTORY) + execute_process (COMMAND "${CMAKE_COMMAND}" -E env "MONO_PATH=${ir_dir}" + ${${_PYTHON_PREFIX}_DOTNET_LAUNCHER} "${working_dir}/version.exe" + WORKING_DIRECTORY "${working_dir}" + RESULT_VARIABLE result + OUTPUT_VARIABLE version + ERROR_QUIET) + file (REMOVE_RECURSE "${working_dir}") + if (result) + # compiler is not usable + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot use the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") + return() + endif() + + if (_PVC_VERSION OR _PVC_IN_RANGE) + if (_PVC_VERSION) + # check against specified version + ## compute number of components for version + string (REGEX REPLACE "[^.]" "" dots "${_PVC_VERSION}") + ## add one dot because there is one dot less than there are components + string (LENGTH "${dots}." count) + if (count GREATER 3) + set (count 3) + endif() + set (version_regex "^[0-9]+") + if (count EQUAL 3) + string (APPEND version_regex "\\.[0-9]+\\.[0-9]+") + elseif (count EQUAL 2) + string (APPEND version_regex "\\.[0-9]+") + endif() + # extract needed range + string (REGEX MATCH "${version_regex}" version "${version}") + + if (_PVC_EXACT AND NOT version VERSION_EQUAL _PVC_VERSION) + # interpreter has wrong version + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") + return() + else() + # check that version is OK + string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}") + string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" expected_major_version "${_PVC_VERSION}") + if (NOT major_version VERSION_EQUAL expected_major_version + OR NOT version VERSION_GREATER_EQUAL _PVC_VERSION) + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") + return() + endif() + endif() + endif() + + if (_PVC_IN_RANGE) + # check if version is in the requested range + find_package_check_version ("${version}" in_range HANDLE_VERSION_RANGE) + if (NOT in_range) + # interpreter has invalid version + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") + return() + endif() + endif() + else() + string(REGEX REPLACE "^([0-9]+)\\.?.*$" "\\1" major_version "${version}") + if (NOT major_version EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + # Compiler has wrong major version + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Wrong major version for the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_COMPILER PROPERTY VALUE "${_PYTHON_PREFIX}_COMPILER-NOTFOUND") + return() + endif() + endif() +endfunction() + + +function (_PYTHON_VALIDATE_LIBRARY) + if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG) + return() + endif() + + cmake_parse_arguments (PARSE_ARGV 0 _PVL "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "") + + if (_PVL_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") + # library does not exist anymore + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") + if (WIN32) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_DEBUG PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND") + endif() + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + return() + endif() + + # retrieve version and abi from library name + _python_get_version (LIBRARY PREFIX lib_) + + if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT lib_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) + # incompatible ABI + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") + else() + if (_PVL_VERSION OR _PVL_IN_RANGE) + if (_PVL_VERSION) + # library have only major.minor information + string (REGEX MATCH "[0-9](\\.[0-9]+)?" version "${_PVL_VERSION}") + if ((_PVL_EXACT AND NOT lib_VERSION VERSION_EQUAL version) OR (lib_VERSION VERSION_LESS version)) + # library has wrong version + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") + endif() + endif() + + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND _PVL_IN_RANGE) + # check if library version is in the requested range + find_package_check_version ("${lib_VERSION}" in_range HANDLE_VERSION_RANGE) + if (NOT in_range) + # library has wrong version + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") + endif() + endif() + else() + if (NOT lib_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + # library has wrong major version + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong major version for the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") + endif() + endif() + endif() + + if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + if (WIN32) + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_DEBUG PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_DEBUG-NOTFOUND") + endif() + unset (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE CACHE) + unset (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG CACHE) + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + endif() +endfunction() + + +function (_PYTHON_VALIDATE_INCLUDE_DIR) + if (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) + return() + endif() + + cmake_parse_arguments (PARSE_ARGV 0 _PVID "IN_RANGE;EXACT;CHECK_EXISTS" "VERSION" "") + + if (_PVID_CHECK_EXISTS AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") + # include file does not exist anymore + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + return() + endif() + + # retrieve version from header file + _python_get_version (INCLUDE PREFIX inc_) + + if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT inc_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) + # incompatible ABI + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong ABI for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + else() + if (_PVID_VERSION OR _PVID_IN_RANGE) + if (_PVID_VERSION) + if ((_PVID_EXACT AND NOT inc_VERSION VERSION_EQUAL expected_version) OR (inc_VERSION VERSION_LESS expected_version)) + # include dir has wrong version + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + endif() + endif() + + if (_${_PYTHON_PREFIX}_INCLUDE_DIR AND PVID_IN_RANGE) + # check if include dir is in the request range + find_package_check_version ("${inc_VERSION}" in_range HANDLE_VERSION_RANGE) + if (NOT in_range) + # include dir has wrong version + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + endif() + endif() + else() + if (NOT inc_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + # include dir has wrong major version + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Wrong major version for the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"" PARENT_SCOPE) + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + endif() + endif() + endif() +endfunction() + + +function (_PYTHON_FIND_RUNTIME_LIBRARY _PYTHON_LIB) + string (REPLACE "_RUNTIME" "" _PYTHON_LIB "${_PYTHON_LIB}") + # look at runtime part on systems supporting it + if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR + (CMAKE_SYSTEM_NAME MATCHES "MSYS|CYGWIN" + AND ${_PYTHON_LIB} MATCHES "${CMAKE_IMPORT_LIBRARY_SUFFIX}$")) + set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX}) + # MSYS has a special syntax for runtime libraries + if (CMAKE_SYSTEM_NAME MATCHES "MSYS") + list (APPEND CMAKE_FIND_LIBRARY_PREFIXES "msys-") + endif() + find_library (${ARGV}) + endif() +endfunction() + + +function (_PYTHON_SET_LIBRARY_DIRS _PYTHON_SLD_RESULT) + unset (_PYTHON_DIRS) + set (_PYTHON_LIBS ${ARGN}) + foreach (_PYTHON_LIB IN LISTS _PYTHON_LIBS) + if (${_PYTHON_LIB}) + get_filename_component (_PYTHON_DIR "${${_PYTHON_LIB}}" DIRECTORY) + list (APPEND _PYTHON_DIRS "${_PYTHON_DIR}") + endif() + endforeach() + list (REMOVE_DUPLICATES _PYTHON_DIRS) + set (${_PYTHON_SLD_RESULT} ${_PYTHON_DIRS} PARENT_SCOPE) +endfunction() + + +function (_PYTHON_SET_DEVELOPMENT_MODULE_FOUND module) + if ("Development.${module}" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + string(TOUPPER "${module}" id) + set (module_found TRUE) + + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS + AND NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + set (module_found FALSE) + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS + AND NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) + set (module_found FALSE) + endif() + + set (${_PYTHON_PREFIX}_Development.${module}_FOUND ${module_found} PARENT_SCOPE) + endif() +endfunction() + + +if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + # range must include internal major version + if (${_PYTHON_PREFIX}_FIND_VERSION_MIN_MAJOR VERSION_GREATER _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR + OR ((${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" + AND ${_PYTHON_PREFIX}_FIND_VERSION_MAX VERSION_LESS _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + OR (${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" + AND ${_PYTHON_PREFIX}_FIND_VERSION_MAX VERSION_LESS_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR))) + _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong version range specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_RANGE}\", but expected version range must include major version \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") + + cmake_policy(POP) + return() + endif() +else() + if (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR + AND NOT ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + # If major version is specified, it must be the same as internal major version + _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong major version specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}\", but expected major version is \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") + + cmake_policy(POP) + return() + endif() +endif() + + +# handle components +if (NOT ${_PYTHON_PREFIX}_FIND_COMPONENTS) + set (${_PYTHON_PREFIX}_FIND_COMPONENTS Interpreter) + set (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter TRUE) +endif() +if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + list (APPEND ${_PYTHON_PREFIX}_FIND_COMPONENTS "Interpreter" "Development.Module") +endif() +if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + list (APPEND ${_PYTHON_PREFIX}_FIND_COMPONENTS "Development.Module" "Development.Embed") +endif() +list (REMOVE_DUPLICATES ${_PYTHON_PREFIX}_FIND_COMPONENTS) +foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development Development.Module Development.Embed NumPy) + set (${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_FOUND FALSE) +endforeach() +if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development) + set (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Module TRUE) + set (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Embed TRUE) +endif() + +unset (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) +unset (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) +unset (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS) +if ("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + if (CMAKE_SYSTEM_NAME MATCHES "^(Windows.*|CYGWIN|MSYS)$") + list (APPEND _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS "LIBRARY") + endif() + list (APPEND _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS "INCLUDE_DIR") +endif() +if ("Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + list (APPEND _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS "LIBRARY" "INCLUDE_DIR") +endif() +set (_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS ${_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS} ${_${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS}) +list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + +# Set versions to search +## default: search any version +set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSIONS}) +unset (_${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + +if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS) + if ((${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" + AND _${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION_MIN) + AND ((${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" + AND _${_PYTHON_PREFIX}_VERSION VERSION_LESS_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION_MAX) + OR (${_PYTHON_PREFIX}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" + AND _${_PYTHON_PREFIX}_VERSION VERSION_LESS ${_PYTHON_PREFIX}_FIND_VERSION_MAX))) + list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION}) + endif() + endforeach() +else() + if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT GREATER 1) + if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + set (_${_PYTHON_PREFIX}_FIND_VERSION_EXACT "EXACT") + set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}) + else() + unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) + # add all compatible versions + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS) + if (_${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL "${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}") + list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION}) + endif() + endforeach() + endif() + endif() +endif() + +# Set ABIs to search +## default: search any ABI +if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR VERSION_LESS "3") + # ABI not supported + unset (_${_PYTHON_PREFIX}_FIND_ABI) + set (_${_PYTHON_PREFIX}_ABIFLAGS "") +else() + unset (_${_PYTHON_PREFIX}_FIND_ABI) + unset (_${_PYTHON_PREFIX}_ABIFLAGS) + if (DEFINED ${_PYTHON_PREFIX}_FIND_ABI) + # normalization + string (TOUPPER "${${_PYTHON_PREFIX}_FIND_ABI}" _${_PYTHON_PREFIX}_FIND_ABI) + list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(TRUE|Y(ES)?|1)$" "ON") + list (TRANSFORM _${_PYTHON_PREFIX}_FIND_ABI REPLACE "^(FALSE|N(O)?|0)$" "OFF") + if (NOT _${_PYTHON_PREFIX}_FIND_ABI MATCHES "^(ON|OFF|ANY);(ON|OFF|ANY);(ON|OFF|ANY)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_ABI}: invalid value for '${_PYTHON_PREFIX}_FIND_ABI'. Ignore it") + unset (_${_PYTHON_PREFIX}_FIND_ABI) + endif() + _python_get_abiflags (_${_PYTHON_PREFIX}_ABIFLAGS) + endif() +endif() +unset (${_PYTHON_PREFIX}_SOABI) + +# Define lookup strategy +cmake_policy (GET CMP0094 _${_PYTHON_PREFIX}_LOOKUP_POLICY) +if (_${_PYTHON_PREFIX}_LOOKUP_POLICY STREQUAL "NEW") + set (_${_PYTHON_PREFIX}_FIND_STRATEGY "LOCATION") +else() + set (_${_PYTHON_PREFIX}_FIND_STRATEGY "VERSION") +endif() +if (DEFINED ${_PYTHON_PREFIX}_FIND_STRATEGY) + if (NOT ${_PYTHON_PREFIX}_FIND_STRATEGY MATCHES "^(VERSION|LOCATION)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_STRATEGY}: invalid value for '${_PYTHON_PREFIX}_FIND_STRATEGY'. 'VERSION' or 'LOCATION' expected.") + set (_${_PYTHON_PREFIX}_FIND_STRATEGY "VERSION") + else() + set (_${_PYTHON_PREFIX}_FIND_STRATEGY "${${_PYTHON_PREFIX}_FIND_STRATEGY}") + endif() +endif() + +# Python and Anaconda distributions: define which architectures can be used +if (CMAKE_SIZEOF_VOID_P) + # In this case, search only for 64bit or 32bit + math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") + set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) +else() + # architecture unknown, search for both 64bit and 32bit + set (_${_PYTHON_PREFIX}_ARCH 64) + set (_${_PYTHON_PREFIX}_ARCH2 32) +endif() + +# IronPython support +unset (_${_PYTHON_PREFIX}_IRON_PYTHON_INTERPRETER_NAMES) +unset (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_NAMES) +unset (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS) +if (CMAKE_SIZEOF_VOID_P) + if (_${_PYTHON_PREFIX}_ARCH EQUAL "32") + set (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS "/platform:x86") + else() + set (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS "/platform:x64") + endif() +endif() +if (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Do not use wrapper script on Linux because it is buggy: -c interpreter option cannot be used + list (APPEND _${_PYTHON_PREFIX}_IRON_PYTHON_INTERPRETER_NAMES "ipy${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}" "ipy64" "ipy32" "ipy") + list (APPEND _${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_NAMES "ipyc") +endif() +list (APPEND _${_PYTHON_PREFIX}_IRON_PYTHON_INTERPRETER_NAMES "ipy.exe") +list (APPEND _${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_NAMES "ipyc.exe") +set (_${_PYTHON_PREFIX}_IRON_PYTHON_PATH_SUFFIXES net45 net40 bin) + +# PyPy support +if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL "3") + set (_${_PYTHON_PREFIX}_PYPY_NAMES pypy3) + set (_${_PYTHON_PREFIX}_PYPY_LIB_NAMES pypy3-c) + if (WIN32) + # special name for runtime part + list (APPEND _${_PYTHON_PREFIX}_PYPY_LIB_NAMES libpypy3-c) + endif() + set (_${_PYTHON_PREFIX}_PYPY_INCLUDE_PATH_SUFFIXES lib/pypy3) +else() + set (_${_PYTHON_PREFIX}_PYPY_NAMES pypy) + set (_${_PYTHON_PREFIX}_PYPY_LIB_NAMES pypy-c) + if (WIN32) + # special name for runtime part + list (APPEND _${_PYTHON_PREFIX}_PYPY_LIB_NAMES libpypy-c) + endif() + set (_${_PYTHON_PREFIX}_PYPY_INCLUDE_PATH_SUFFIXES lib/pypy) +endif() +set (_${_PYTHON_PREFIX}_PYPY_EXECUTABLE_PATH_SUFFIXES bin) +set (_${_PYTHON_PREFIX}_PYPY_LIBRARY_PATH_SUFFIXES lib libs bin) +list (APPEND _${_PYTHON_PREFIX}_PYPY_INCLUDE_PATH_SUFFIXES include) + +# Python Implementations handling +unset (_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) +if (DEFINED ${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + foreach (_${_PYTHON_PREFIX}_IMPLEMENTATION IN LISTS ${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + if (NOT _${_PYTHON_PREFIX}_IMPLEMENTATION MATCHES "^(CPython|IronPython|PyPy)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${_${_PYTHON_PREFIX}_IMPLEMENTATION}: invalid value for '${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS'. 'CPython', 'IronPython' or 'PyPy' expected. Value will be ignored.") + else() + list (APPEND _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS ${_${_PYTHON_PREFIX}_IMPLEMENTATION}) + endif() + endforeach() +else() + if (WIN32) + set (_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS CPython IronPython) + else() + set (_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS CPython) + endif() +endif() + +# compute list of names for header file +unset (_${_PYTHON_PREFIX}_INCLUDE_NAMES) +foreach (_${_PYTHON_PREFIX}_IMPLEMENTATION IN LISTS _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + if (_${_PYTHON_PREFIX}_IMPLEMENTATION STREQUAL "CPython") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_NAMES "Python.h") + elseif (_${_PYTHON_PREFIX}_IMPLEMENTATION STREQUAL "PyPy") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_NAMES "PyPy.h") + endif() +endforeach() + + +# Apple frameworks handling +_python_find_frameworks () + +set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK "FIRST") + +if (DEFINED ${_PYTHON_PREFIX}_FIND_FRAMEWORK) + if (NOT ${_PYTHON_PREFIX}_FIND_FRAMEWORK MATCHES "^(FIRST|LAST|NEVER)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_FRAMEWORK}: invalid value for '${_PYTHON_PREFIX}_FIND_FRAMEWORK'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") + else() + set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK ${${_PYTHON_PREFIX}_FIND_FRAMEWORK}) + endif() +elseif (DEFINED CMAKE_FIND_FRAMEWORK) + if (CMAKE_FIND_FRAMEWORK STREQUAL "ONLY") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: CMAKE_FIND_FRAMEWORK: 'ONLY' value is not supported. 'FIRST' will be used instead.") + elseif (NOT CMAKE_FIND_FRAMEWORK MATCHES "^(FIRST|LAST|NEVER)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${CMAKE_FIND_FRAMEWORK}: invalid value for 'CMAKE_FIND_FRAMEWORK'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") + else() + set (_${_PYTHON_PREFIX}_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) + endif() +endif() + +# Save CMAKE_FIND_APPBUNDLE +if (DEFINED CMAKE_FIND_APPBUNDLE) + set (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE}) +else() + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) +endif() +# To avoid app bundle lookup +set (CMAKE_FIND_APPBUNDLE "NEVER") + +# Save CMAKE_FIND_FRAMEWORK +if (DEFINED CMAKE_FIND_FRAMEWORK) + set (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) +else() + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) +endif() +# To avoid framework lookup +set (CMAKE_FIND_FRAMEWORK "NEVER") + +# Windows Registry handling +if (DEFINED ${_PYTHON_PREFIX}_FIND_REGISTRY) + if (NOT ${_PYTHON_PREFIX}_FIND_REGISTRY MATCHES "^(FIRST|LAST|NEVER)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_REGISTRY}: invalid value for '${_PYTHON_PREFIX}_FIND_REGISTRY'. 'FIRST', 'LAST' or 'NEVER' expected. 'FIRST' will be used instead.") + set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST") + else() + set (_${_PYTHON_PREFIX}_FIND_REGISTRY ${${_PYTHON_PREFIX}_FIND_REGISTRY}) + endif() +else() + set (_${_PYTHON_PREFIX}_FIND_REGISTRY "FIRST") +endif() + +# virtual environments recognition +if (DEFINED ENV{VIRTUAL_ENV} OR DEFINED ENV{CONDA_PREFIX}) + if (DEFINED ${_PYTHON_PREFIX}_FIND_VIRTUALENV) + if (NOT ${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY|STANDARD)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}: invalid value for '${_PYTHON_PREFIX}_FIND_VIRTUALENV'. 'FIRST', 'ONLY' or 'STANDARD' expected. 'FIRST' will be used instead.") + set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV "FIRST") + else() + set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV ${${_PYTHON_PREFIX}_FIND_VIRTUALENV}) + endif() + else() + set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV FIRST) + endif() +else() + set (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STANDARD) +endif() + + +# Python naming handling +if (DEFINED ${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES) + if (NOT ${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES MATCHES "^(FIRST|LAST|NEVER)$") + message (AUTHOR_WARNING "Find${_PYTHON_PREFIX}: ${_${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES}: invalid value for '${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES'. 'FIRST', 'LAST' or 'NEVER' expected. 'LAST' will be used instead.") + set (_${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES LAST) + else() + set (_${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES ${${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES}) + endif() +else() + set (_${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES LAST) +endif() + + +# Compute search signature +# This signature will be used to check validity of cached variables on new search +set (_${_PYTHON_PREFIX}_SIGNATURE "${${_PYTHON_PREFIX}_ROOT_DIR}:${_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS}:${_${_PYTHON_PREFIX}_FIND_STRATEGY}:${${_PYTHON_PREFIX}_FIND_VIRTUALENV}${_${_PYTHON_PREFIX}_FIND_UNVERSIONED_NAMES}") +if (NOT WIN32) + string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${${_PYTHON_PREFIX}_USE_STATIC_LIBS}:") +endif() +if (CMAKE_HOST_APPLE) + string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${_${_PYTHON_PREFIX}_FIND_FRAMEWORK}") +endif() +if (CMAKE_HOST_WIN32) + string (APPEND _${_PYTHON_PREFIX}_SIGNATURE ":${_${_PYTHON_PREFIX}_FIND_REGISTRY}") +endif() + +function (_PYTHON_CHECK_DEVELOPMENT_SIGNATURE module) + if ("Development.${module}" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + string (TOUPPER "${module}" id) + set (signature "${_${_PYTHON_PREFIX}_SIGNATURE}:") + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + list (APPEND signature "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}:") + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + list (APPEND signature "${_${_PYTHON_PREFIX}_INCLUDE_DIR}:") + endif() + string (MD5 signature "${signature}") + if (signature STREQUAL _${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE) + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + _python_validate_library (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) + elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + _python_validate_library (IN_RANGE CHECK_EXISTS) + elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION) + _python_validate_library (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) + else() + _python_validate_library (CHECK_EXISTS) + endif() + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + _python_validate_include_dir (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) + elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + _python_validate_include_dir (IN_RANGE CHECK_EXISTS) + elseif (${_PYTHON_PREFIX}_FIND_VERSION) + _python_validate_include_dir (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) + else() + _python_validate_include_dir (CHECK_EXISTS) + endif() + endif() + else() + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + unset (_${_PYTHON_PREFIX}_LIBRARY_RELEASE CACHE) + unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG CACHE) + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + unset (_${_PYTHON_PREFIX}_INCLUDE_DIR CACHE) + endif() + endif() + if (("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS + AND NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + OR ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS + AND NOT _${_PYTHON_PREFIX}_INCLUDE_DIR)) + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + unset (_${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE CACHE) + endif() + endif() +endfunction() + +function (_PYTHON_COMPUTE_DEVELOPMENT_SIGNATURE module) + string (TOUPPER "${module}" id) + if (${_PYTHON_PREFIX}_Development.${module}_FOUND) + set (signature "${_${_PYTHON_PREFIX}_SIGNATURE}:") + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + list (APPEND signature "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}:") + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_${id}_ARTIFACTS) + list (APPEND signature "${_${_PYTHON_PREFIX}_INCLUDE_DIR}:") + endif() + string (MD5 signature "${signature}") + set (_${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE "${signature}" CACHE INTERNAL "") + else() + unset (_${_PYTHON_PREFIX}_DEVELOPMENT_${id}_SIGNATURE CACHE) + endif() +endfunction() + + +unset (_${_PYTHON_PREFIX}_REQUIRED_VARS) +unset (_${_PYTHON_PREFIX}_CACHED_VARS) +unset (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE) +unset (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE) +unset (_${_PYTHON_PREFIX}_Development_REASON_FAILURE) +unset (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE) + + +# preamble +## For IronPython on platforms other than Windows, search for the .Net interpreter +if ("IronPython" IN_LIST _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS + AND NOT WIN32) + find_program (${_PYTHON_PREFIX}_DOTNET_LAUNCHER + NAMES "mono") +endif() + + +# first step, search for the interpreter +if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_EXECUTABLE + _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES) + if (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_EXECUTABLE) + endif() + + if (DEFINED ${_PYTHON_PREFIX}_EXECUTABLE + AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_EXECUTABLE}") + if (NOT ${_PYTHON_PREFIX}_EXECUTABLE STREQUAL _${_PYTHON_PREFIX}_EXECUTABLE) + # invalidate cache properties + unset (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES CACHE) + endif() + set (_${_PYTHON_PREFIX}_EXECUTABLE "${${_PYTHON_PREFIX}_EXECUTABLE}" CACHE INTERNAL "") + elseif (DEFINED _${_PYTHON_PREFIX}_EXECUTABLE) + # compute interpreter signature and check validity of definition + string (MD5 __${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_EXECUTABLE}") + if (__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE) + # check version validity + if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + _python_validate_interpreter (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) + elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + _python_validate_interpreter (IN_RANGE CHECK_EXISTS) + elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION) + _python_validate_interpreter (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) + else() + _python_validate_interpreter (CHECK_EXISTS) + endif() + else() + unset (_${_PYTHON_PREFIX}_EXECUTABLE CACHE) + endif() + if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) + unset (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE CACHE) + unset (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES CACHE) + endif() + endif() + + if (NOT _${_PYTHON_PREFIX}_EXECUTABLE) + set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") + # build all executable names + _python_get_names (_${_PYTHON_PREFIX}_NAMES VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} POSIX INTERPRETER) + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} INTERPRETER) + + # Framework Paths + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS}) + # Registry Paths + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS}) + + set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT}) + if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE) + elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION) + list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS VERSION ${${_PYTHON_PREFIX}_FIND_VERSION}) + endif() + + while (TRUE) + # Virtual environments handling + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + + _python_validate_interpreter (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") + break() + endif() + endif() + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + endif() + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + endif() + + # try using HINTS + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + # try using standard paths + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) + _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + endif() + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + _python_validate_interpreter (${${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + endif() + + break() + endwhile() + else() + # look-up for various versions and locations + set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS EXACT) + if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE) + endif() + + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + _python_get_names (_${_PYTHON_PREFIX}_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX INTERPRETER) + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} INTERPRETER) + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_VERSION}) + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS VERSION ${_${_PYTHON_PREFIX}_VERSION}) + + # Virtual environments handling + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") + continue() + endif() + endif() + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + + # try using HINTS + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + # try using standard paths. + # NAMES_PER_DIR is not defined on purpose to have a chance to find + # expected version. + # For example, typical systems have 'python' for version 2.* and 'python3' + # for version 3.*. So looking for names per dir will find, potentially, + # systematically 'python' (i.e. version 2) even if version 3 is searched. + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) + _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + endif() + + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + endif() + + _python_validate_interpreter (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + endforeach() + + if (NOT _${_PYTHON_PREFIX}_EXECUTABLE AND + NOT _${_PYTHON_PREFIX}_FIND_VIRTUALENV STREQUAL "ONLY") + # No specific version found. Retry with generic names and standard paths. + # NAMES_PER_DIR is not defined on purpose to have a chance to find + # expected version. + # For example, typical systems have 'python' for version 2.* and 'python3' + # for version 3.*. So looking for names per dir will find, potentially, + # systematically 'python' (i.e. version 2) even if version 3 is searched. + _python_get_names (_${_PYTHON_PREFIX}_NAMES POSIX INTERPRETER) + find_program (_${_PYTHON_PREFIX}_EXECUTABLE + NAMES ${_${_PYTHON_PREFIX}_NAMES}) + _python_validate_interpreter () + endif() + endif() + endif() + + set (${_PYTHON_PREFIX}_EXECUTABLE "${_${_PYTHON_PREFIX}_EXECUTABLE}") + _python_get_launcher (_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER INTERPRETER) + + # retrieve exact version of executable found + if (_${_PYTHON_PREFIX}_EXECUTABLE) + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _${_PYTHON_PREFIX}_RESULT) + set (_${_PYTHON_PREFIX}_EXECUTABLE_USABLE TRUE) + else() + # Interpreter is not usable + set (_${_PYTHON_PREFIX}_EXECUTABLE_USABLE FALSE) + unset (${_PYTHON_PREFIX}_VERSION) + set (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE "Cannot run the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"") + endif() + endif() + + if (_${_PYTHON_PREFIX}_EXECUTABLE AND _${_PYTHON_PREFIX}_EXECUTABLE_USABLE) + if (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES) + set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) + + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 0 ${_PYTHON_PREFIX}_INTERPRETER_ID) + + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 1 ${_PYTHON_PREFIX}_VERSION_MAJOR) + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 2 ${_PYTHON_PREFIX}_VERSION_MINOR) + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 3 ${_PYTHON_PREFIX}_VERSION_PATCH) + + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 4 _${_PYTHON_PREFIX}_ARCH) + set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) + + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 5 _${_PYTHON_PREFIX}_ABIFLAGS) + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 6 ${_PYTHON_PREFIX}_SOABI) + + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 7 ${_PYTHON_PREFIX}_STDLIB) + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 8 ${_PYTHON_PREFIX}_STDARCH) + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 9 ${_PYTHON_PREFIX}_SITELIB) + list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 10 ${_PYTHON_PREFIX}_SITEARCH) + else() + string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${${_PYTHON_PREFIX}_VERSION}") + list (GET _${_PYTHON_PREFIX}_VERSIONS 0 ${_PYTHON_PREFIX}_VERSION_MAJOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 1 ${_PYTHON_PREFIX}_VERSION_MINOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 2 ${_PYTHON_PREFIX}_VERSION_PATCH) + + if (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) + + # Use interpreter version and ABI for future searches to ensure consistency + set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETR_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write(sys.abiflags)" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_ABIFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + # assunme ABI is not supported + set (_${_PYTHON_PREFIX}_ABIFLAGS "") + endif() + endif() + + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + unset (_${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE) + + # compute and save interpreter signature + string (MD5 __${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_EXECUTABLE}") + set (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE "${__${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}" CACHE INTERNAL "") + + if (NOT CMAKE_SIZEOF_VOID_P) + # determine interpreter architecture + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write(str(sys.maxsize > 2**32))" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_IS64BIT + ERROR_VARIABLE ${_PYTHON_PREFIX}_IS64BIT) + if (NOT _${_PYTHON_PREFIX}_RESULT) + if (${_PYTHON_PREFIX}_IS64BIT) + set (_${_PYTHON_PREFIX}_ARCH 64) + set (_${_PYTHON_PREFIX}_ARCH2 64) + else() + set (_${_PYTHON_PREFIX}_ARCH 32) + set (_${_PYTHON_PREFIX}_ARCH2 32) + endif() + endif() + endif() + + # retrieve interpreter identity + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -V + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID + ERROR_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID) + if (NOT _${_PYTHON_PREFIX}_RESULT) + if (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Anaconda") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "Anaconda") + elseif (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Enthought") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "Canopy") + elseif (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "PyPy ([0-9.]+)") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "PyPy") + set (${_PYTHON_PREFIX}_PyPy_VERSION "${CMAKE_MATCH_1}") + else() + string (REGEX REPLACE "^([^ ]+).*" "\\1" ${_PYTHON_PREFIX}_INTERPRETER_ID "${${_PYTHON_PREFIX}_INTERPRETER_ID}") + if (${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "Python") + # try to get a more precise ID + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write(sys.copyright)" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_COPYRIGHT + ERROR_QUIET) + if (${_PYTHON_PREFIX}_COPYRIGHT MATCHES "ActiveState") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "ActivePython") + endif() + endif() + endif() + else() + set (${_PYTHON_PREFIX}_INTERPRETER_ID Python) + endif() + + # retrieve various package installation directories + execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys\ntry:\n from distutils import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_python_lib(plat_specific=False,standard_lib=True),sysconfig.get_python_lib(plat_specific=True,standard_lib=True),sysconfig.get_python_lib(plat_specific=False,standard_lib=False),sysconfig.get_python_lib(plat_specific=True,standard_lib=False)]))\nexcept Exception:\n import sysconfig\n sys.stdout.write(';'.join([sysconfig.get_path('stdlib'),sysconfig.get_path('platstdlib'),sysconfig.get_path('purelib'),sysconfig.get_path('platlib')]))" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_LIBPATHS + ERROR_QUIET) + if (NOT _${_PYTHON_PREFIX}_RESULT) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 0 ${_PYTHON_PREFIX}_STDLIB) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 1 ${_PYTHON_PREFIX}_STDARCH) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 2 ${_PYTHON_PREFIX}_SITELIB) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 3 ${_PYTHON_PREFIX}_SITEARCH) + else() + unset (${_PYTHON_PREFIX}_STDLIB) + unset (${_PYTHON_PREFIX}_STDARCH) + unset (${_PYTHON_PREFIX}_SITELIB) + unset (${_PYTHON_PREFIX}_SITEARCH) + endif() + + _python_get_config_var (${_PYTHON_PREFIX}_SOABI SOABI) + + # store properties in the cache to speed-up future searches + set (_${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES + "${${_PYTHON_PREFIX}_INTERPRETER_ID};${${_PYTHON_PREFIX}_VERSION_MAJOR};${${_PYTHON_PREFIX}_VERSION_MINOR};${${_PYTHON_PREFIX}_VERSION_PATCH};${_${_PYTHON_PREFIX}_ARCH};${_${_PYTHON_PREFIX}_ABIFLAGS};${${_PYTHON_PREFIX}_SOABI};${${_PYTHON_PREFIX}_STDLIB};${${_PYTHON_PREFIX}_STDARCH};${${_PYTHON_PREFIX}_SITELIB};${${_PYTHON_PREFIX}_SITEARCH}" CACHE INTERNAL "${_PYTHON_PREFIX} Properties") + else() + unset (_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE CACHE) + unset (${_PYTHON_PREFIX}_INTERPRETER_ID) + endif() + endif() + endif() + + if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) + set (${_PYTHON_PREFIX}_EXECUTABLE "${_${_PYTHON_PREFIX}_EXECUTABLE}" CACHE FILEPATH "${_PYTHON_PREFIX} Interpreter") + endif() + + _python_mark_as_internal (_${_PYTHON_PREFIX}_EXECUTABLE + _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES + _${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE) +endif() + + +# second step, search for compiler (IronPython) +if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_COMPILER) + if (${_PYTHON_PREFIX}_FIND_REQUIRED_Compiler) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_COMPILER) + endif() + + if (NOT "IronPython" IN_LIST _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + unset (_${_PYTHON_PREFIX}_COMPILER CACHE) + unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) + elseif (DEFINED ${_PYTHON_PREFIX}_COMPILER + AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_COMPILER}") + set (_${_PYTHON_PREFIX}_COMPILER "${${_PYTHON_PREFIX}_COMPILER}" CACHE INTERNAL "") + elseif (DEFINED _${_PYTHON_PREFIX}_COMPILER) + # compute compiler signature and check validity of definition + string (MD5 __${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_COMPILER}") + if (__${_PYTHON_PREFIX}_COMPILER_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_COMPILER_SIGNATURE) + # check version validity + if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + _python_validate_compiler (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} EXACT CHECK_EXISTS) + elseif (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + _python_validate_compiler (IN_RANGE CHECK_EXISTS) + elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION) + _python_validate_compiler (VERSION ${${_PYTHON_PREFIX}_FIND_VERSION} CHECK_EXISTS) + else() + _python_validate_compiler (CHECK_EXISTS) + endif() + else() + unset (_${_PYTHON_PREFIX}_COMPILER CACHE) + unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) + endif() + endif() + + if ("IronPython" IN_LIST _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS + AND NOT _${_PYTHON_PREFIX}_COMPILER) + # IronPython specific artifacts + # If IronPython interpreter is found, use its path + unset (_${_PYTHON_PREFIX}_IRON_ROOT) + if (${_PYTHON_PREFIX}_Interpreter_FOUND AND ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") + get_filename_component (_${_PYTHON_PREFIX}_IRON_ROOT "${${_PYTHON_PREFIX}_EXECUTABLE}" DIRECTORY) + endif() + + if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") + _python_get_names (_${_PYTHON_PREFIX}_COMPILER_NAMES + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} + COMPILER) + + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} + COMPILER) + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS}) + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS}) + + set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS ${_${_PYTHON_PREFIX}_FIND_VERSION_EXACT}) + if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE) + elseif (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION) + list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS VERSION ${${_PYTHON_PREFIX}_FIND_VERSION}) + endif() + + while (TRUE) + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + + # try using HINTS + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + + # try using standard paths + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) + _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + _python_validate_compiler (${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + + break() + endwhile() + else() + # try using root dir and registry + set (_${_PYTHON_PREFIX}_VALIDATE_OPTIONS EXACT) + if (${_PYTHON_PREFIX}_FIND_VERSION_RANGE) + list (APPEND _${_PYTHON_PREFIX}_VALIDATE_OPTIONS IN_RANGE) + endif() + + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + _python_get_names (_${_PYTHON_PREFIX}_COMPILER_NAMES + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} + COMPILER) + + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSION} + COMPILER) + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_VERSION}) + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_VERSION}) + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + + # try using HINTS + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + # Windows registry + if (CMAKE_HOST_WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_DEFAULT_PATH) + _python_validate_compiler (VERSION ${_${_PYTHON_PREFIX}_VERSION} ${_${_PYTHON_PREFIX}_VALIDATE_OPTIONS}) + if (_${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endif() + endforeach() + + # no specific version found, re-try in standard paths + _python_get_names (_${_PYTHON_PREFIX}_COMPILER_NAMES + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} + COMPILER) + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES + IMPLEMENTATIONS IronPython + VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} + COMPILER) + find_program (_${_PYTHON_PREFIX}_COMPILER + NAMES ${_${_PYTHON_PREFIX}_COMPILER_NAMES} + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) + _python_validate_compiler () + endif() + endif() + + set (${_PYTHON_PREFIX}_COMPILER "${_${_PYTHON_PREFIX}_COMPILER}") + + if (_${_PYTHON_PREFIX}_COMPILER) + # retrieve python environment version from compiler + _python_get_launcher (_${_PYTHON_PREFIX}_COMPILER_LAUNCHER COMPILER) + set (_${_PYTHON_PREFIX}_VERSION_DIR "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") + file (WRITE "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))\n") + execute_process (COMMAND ${_${_PYTHON_PREFIX}_COMPILER_LAUNCHER} "${_${_PYTHON_PREFIX}_COMPILER}" + ${_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS} + /target:exe /embed "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" + WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" + OUTPUT_QUIET + ERROR_QUIET) + get_filename_component (_${_PYTHON_PREFIX}_IR_DIR "${_${_PYTHON_PREFIX}_COMPILER}" DIRECTORY) + execute_process (COMMAND "${CMAKE_COMMAND}" -E env "MONO_PATH=${_${_PYTHON_PREFIX}_IR_DIR}" + ${${_PYTHON_PREFIX}_DOTNET_LAUNCHER} "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.exe" + WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_VERSION + ERROR_QUIET) + if (NOT _${_PYTHON_PREFIX}_RESULT) + set (_${_PYTHON_PREFIX}_COMPILER_USABLE TRUE) + string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${_${_PYTHON_PREFIX}_VERSION}") + list (GET _${_PYTHON_PREFIX}_VERSIONS 0 _${_PYTHON_PREFIX}_VERSION_MAJOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 1 _${_PYTHON_PREFIX}_VERSION_MINOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 2 _${_PYTHON_PREFIX}_VERSION_PATCH) + + if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND) + # set public version information + set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) + set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) + set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) + set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) + endif() + else() + # compiler not usable + set (_${_PYTHON_PREFIX}_COMPILER_USABLE FALSE) + set (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE "Cannot run the compiler \"${_${_PYTHON_PREFIX}_COMPILER}\"") + endif() + file (REMOVE_RECURSE "${_${_PYTHON_PREFIX}_VERSION_DIR}") + endif() + + if (_${_PYTHON_PREFIX}_COMPILER AND _${_PYTHON_PREFIX}_COMPILER_USABLE) + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + # Compiler must be compatible with interpreter + if ("${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}" VERSION_EQUAL "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}") + set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) + endif() + elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) + # Use compiler version for future searches to ensure consistency + set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) + endif() + endif() + + if (${_PYTHON_PREFIX}_Compiler_FOUND) + unset (_${_PYTHON_PREFIX}_Compiler_REASON_FAILURE) + + # compute and save compiler signature + string (MD5 __${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${_${_PYTHON_PREFIX}_SIGNATURE}:${_${_PYTHON_PREFIX}_COMPILER}") + set (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE "${__${_PYTHON_PREFIX}_COMPILER_SIGNATURE}" CACHE INTERNAL "") + + set (${_PYTHON_PREFIX}_COMPILER_ID IronPython) + else() + unset (_${_PYTHON_PREFIX}_COMPILER_SIGNATURE CACHE) + unset (${_PYTHON_PREFIX}_COMPILER_ID) + endif() + + if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) + set (${_PYTHON_PREFIX}_COMPILER "${_${_PYTHON_PREFIX}_COMPILER}" CACHE FILEPATH "${_PYTHON_PREFIX} Compiler") + endif() + + _python_mark_as_internal (_${_PYTHON_PREFIX}_COMPILER + _${_PYTHON_PREFIX}_COMPILER_SIGNATURE) +endif() + +# third step, search for the development artifacts +if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Module) + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_LIBRARIES) + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_INCLUDE_DIRS) + endif() +endif() +if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development.Embed) + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_LIBRARIES) + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_EMBED_ARTIFACTS) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_INCLUDE_DIRS) + endif() +endif() +list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_REQUIRED_VARS) +## Development environment is not compatible with IronPython interpreter +if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + OR "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + AND ((${_PYTHON_PREFIX}_Interpreter_FOUND + AND NOT ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") + OR NOT ${_PYTHON_PREFIX}_Interpreter_FOUND)) + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + # reduce possible implementations to the interpreter one + if (${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "PyPy") + set (_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS "PyPy") + else() + set (_${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS "CPython") + endif() + else() + list (REMOVE_ITEM _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS "IronPython") + endif() + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_LIBRARY_RELEASE + _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + _${_PYTHON_PREFIX}_LIBRARY_DEBUG + _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_INCLUDE_DIR) + endif() + + _python_check_development_signature (Module) + _python_check_development_signature (Embed) + + if (DEFINED ${_PYTHON_PREFIX}_LIBRARY + AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_LIBRARY}") + set (_${_PYTHON_PREFIX}_LIBRARY_RELEASE "${${_PYTHON_PREFIX}_LIBRARY}" CACHE INTERNAL "") + unset (_${_PYTHON_PREFIX}_LIBRARY_DEBUG CACHE) + unset (_${_PYTHON_PREFIX}_INCLUDE_DIR CACHE) + endif() + if (DEFINED ${_PYTHON_PREFIX}_INCLUDE_DIR + AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_INCLUDE_DIR}") + set (_${_PYTHON_PREFIX}_INCLUDE_DIR "${${_PYTHON_PREFIX}_INCLUDE_DIR}" CACHE INTERNAL "") + endif() + + # Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) + if (DEFINED ${_PYTHON_PREFIX}_USE_STATIC_LIBS AND NOT WIN32) + set(_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(${_PYTHON_PREFIX}_USE_STATIC_LIBS) + set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + else() + list (REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + endif() + + if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE OR NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) + # if python interpreter is found, use it to look-up for artifacts + # to ensure consistency between interpreter and development environments. + # If not, try to locate a compatible config tool + if ((NOT ${_PYTHON_PREFIX}_Interpreter_FOUND OR CMAKE_CROSSCOMPILING) + AND "CPython" IN_LIST _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") + set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) + endif() + + if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") + _python_get_names (_${_PYTHON_PREFIX}_CONFIG_NAMES VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} POSIX CONFIG) + # Framework Paths + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS}) + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES bin + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + PATH_SUFFIXES bin) + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES bin + NO_DEFAULT_PATH) + endif() + + if (_${_PYTHON_PREFIX}_CONFIG) + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --help + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE __${_PYTHON_PREFIX}_HELP + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + # assume config tool is not usable + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + endif() + endif() + + if (_${_PYTHON_PREFIX}_CONFIG) + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --abiflags + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE __${_PYTHON_PREFIX}_ABIFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + # assume ABI is not supported + set (__${_PYTHON_PREFIX}_ABIFLAGS "") + endif() + if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) + # Wrong ABI + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + endif() + endif() + + if (_${_PYTHON_PREFIX}_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) + # check that config tool match library architecture + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --configdir + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_CONFIGDIR + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + else() + string(FIND "${_${_PYTHON_PREFIX}_CONFIGDIR}" "${CMAKE_LIBRARY_ARCHITECTURE}" _${_PYTHON_PREFIX}_RESULT) + if (_${_PYTHON_PREFIX}_RESULT EQUAL -1) + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + endif() + endif() + endif() + else() + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + # try to use pythonX.Y-config tool + _python_get_names (_${_PYTHON_PREFIX}_CONFIG_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} POSIX CONFIG) + + # Framework Paths + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_VERSION}) + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES bin + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + PATH_SUFFIXES bin) + + # Apple frameworks handling + if (CMAKE_HOST_APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + NAMES_PER_DIR + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES bin + NO_DEFAULT_PATH) + endif() + + unset (_${_PYTHON_PREFIX}_CONFIG_NAMES) + + if (_${_PYTHON_PREFIX}_CONFIG) + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --help + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE __${_PYTHON_PREFIX}_HELP + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + # assume config tool is not usable + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + endif() + endif() + + if (NOT _${_PYTHON_PREFIX}_CONFIG) + continue() + endif() + + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --abiflags + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE __${_PYTHON_PREFIX}_ABIFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + # assume ABI is not supported + set (__${_PYTHON_PREFIX}_ABIFLAGS "") + endif() + if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND NOT __${_PYTHON_PREFIX}_ABIFLAGS IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS) + # Wrong ABI + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + continue() + endif() + + if (_${_PYTHON_PREFIX}_CONFIG AND DEFINED CMAKE_LIBRARY_ARCHITECTURE) + # check that config tool match library architecture + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --configdir + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_CONFIGDIR + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + continue() + endif() + string (FIND "${_${_PYTHON_PREFIX}_CONFIGDIR}" "${CMAKE_LIBRARY_ARCHITECTURE}" _${_PYTHON_PREFIX}_RESULT) + if (_${_PYTHON_PREFIX}_RESULT EQUAL -1) + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + continue() + endif() + endif() + + if (_${_PYTHON_PREFIX}_CONFIG) + break() + endif() + endforeach() + endif() + endif() + endif() + + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + if ((${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING) OR _${_PYTHON_PREFIX}_CONFIG) + # retrieve root install directory + _python_get_config_var (_${_PYTHON_PREFIX}_PREFIX PREFIX) + + # enforce current ABI + _python_get_config_var (_${_PYTHON_PREFIX}_ABIFLAGS ABIFLAGS) + + set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") + + # retrieve library + ## compute some paths and artifact names + if (_${_PYTHON_PREFIX}_CONFIG) + string (REGEX REPLACE "^.+python([0-9.]+)[a-z]*-config" "\\1" _${_PYTHON_PREFIX}_VERSION "${_${_PYTHON_PREFIX}_CONFIG}") + else() + set (_${_PYTHON_PREFIX}_VERSION "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}") + endif() + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} LIBRARY) + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 POSIX LIBRARY) + + _python_get_config_var (_${_PYTHON_PREFIX}_CONFIGDIR CONFIGDIR) + list (APPEND _${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_CONFIGDIR}") + + list (APPEND _${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # Rely on HINTS and standard paths if interpreter or config tool failed to locate artifacts + if (NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") + set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) + endif() + + if (_${_PYTHON_PREFIX}_FIND_STRATEGY STREQUAL "LOCATION") + # library names + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} WIN32 POSIX LIBRARY) + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} WIN32 DEBUG) + # Paths suffixes + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} LIBRARY) + + # Framework Paths + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_LIB_FIND_VERSIONS}) + # Registry Paths + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS VERSION ${_${_PYTHON_PREFIX}_FIND_VERSIONS} ) + + if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # search in HINTS locations + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + + if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) + else() + unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) + endif() + + if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) + else() + unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) + endif() + + # search in all default paths + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + PATHS ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) + else() + foreach (_${_PYTHON_PREFIX}_LIB_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 POSIX LIBRARY) + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} WIN32 DEBUG) + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION}) + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION}) + + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_LIB_VERSION} LIBRARY) + + if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # search in HINTS locations + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + + if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) + else() + unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) + endif() + + if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) + else() + unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) + endif() + + # search in all default paths + find_library (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + PATHS ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES}) + + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) + break() + endif() + endforeach() + endif() + endif() + endif() + + # finalize library version information + _python_get_version (LIBRARY PREFIX _${_PYTHON_PREFIX}_) + if (_${_PYTHON_PREFIX}_VERSION EQUAL "${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}") + # not able to extract full version from library name + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + # update from interpreter + set (_${_PYTHON_PREFIX}_VERSION ${${_PYTHON_PREFIX}_VERSION}) + set (_${_PYTHON_PREFIX}_VERSION_MAJOR ${${_PYTHON_PREFIX}_VERSION_MAJOR}) + set (_${_PYTHON_PREFIX}_VERSION_MINOR ${${_PYTHON_PREFIX}_VERSION_MINOR}) + set (_${_PYTHON_PREFIX}_VERSION_PATCH ${${_PYTHON_PREFIX}_VERSION_PATCH}) + endif() + endif() + + set (${_PYTHON_PREFIX}_LIBRARY_RELEASE "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") + + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE AND NOT EXISTS "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}") + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the library \"${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}\"") + set_property (CACHE _${_PYTHON_PREFIX}_LIBRARY_RELEASE PROPERTY VALUE "${_PYTHON_PREFIX}_LIBRARY_RELEASE-NOTFOUND") + endif() + + set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + if (WIN32 AND _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + # search for debug library + # use release library location as a hint + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 DEBUG) + get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) + find_library (_${_PYTHON_PREFIX}_LIBRARY_DEBUG + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} + NAMES_PER_DIR + HINTS "${_${_PYTHON_PREFIX}_PATH}" ${_${_PYTHON_PREFIX}_HINTS} + NO_DEFAULT_PATH) + # second try including CMAKE variables to catch-up non conventional layouts + find_library (_${_PYTHON_PREFIX}_LIBRARY_DEBUG + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} + NAMES_PER_DIR + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # retrieve runtime libraries + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 POSIX LIBRARY) + get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) + get_filename_component (_${_PYTHON_PREFIX}_PATH2 "${_${_PYTHON_PREFIX}_PATH}" DIRECTORY) + _python_find_runtime_library (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS "${_${_PYTHON_PREFIX}_PATH}" + "${_${_PYTHON_PREFIX}_PATH2}" ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES bin) + endif() + if (_${_PYTHON_PREFIX}_LIBRARY_DEBUG) + _python_get_names (_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG VERSION ${_${_PYTHON_PREFIX}_VERSION} WIN32 DEBUG) + get_filename_component (_${_PYTHON_PREFIX}_PATH "${_${_PYTHON_PREFIX}_LIBRARY_DEBUG}" DIRECTORY) + get_filename_component (_${_PYTHON_PREFIX}_PATH2 "${_${_PYTHON_PREFIX}_PATH}" DIRECTORY) + _python_find_runtime_library (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES_DEBUG} + NAMES_PER_DIR + HINTS "${_${_PYTHON_PREFIX}_PATH}" + "${_${_PYTHON_PREFIX}_PATH2}" ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES bin) + endif() + endif() + + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + while (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS + AND NOT _${_PYTHON_PREFIX}_LIBRARY_RELEASE) + # Don't search for include dir if no library was founded + break() + endif() + + if ((${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING) OR _${_PYTHON_PREFIX}_CONFIG) + _python_get_config_var (_${_PYTHON_PREFIX}_INCLUDE_DIRS INCLUDES) + + find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES ${_${_PYTHON_PREFIX}_INCLUDE_NAMES} + HINTS ${_${_PYTHON_PREFIX}_INCLUDE_DIRS} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # Rely on HINTS and standard paths if interpreter or config tool failed to locate artifacts + if (NOT _${_PYTHON_PREFIX}_INCLUDE_DIR) + unset (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS) + if (_${_PYTHON_PREFIX}_FIND_VIRTUALENV MATCHES "^(FIRST|ONLY)$") + set (_${_PYTHON_PREFIX}_VIRTUALENV_PATHS ENV VIRTUAL_ENV ENV CONDA_PREFIX) + endif() + unset (_${_PYTHON_PREFIX}_INCLUDE_HINTS) + + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) + # Use the library's install prefix as a hint + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "^(.+/Frameworks/Python.framework/Versions/[0-9.]+)") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") + elseif (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "^(.+)/lib(64|32)?/python[0-9.]+/config") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") + elseif (DEFINED CMAKE_LIBRARY_ARCHITECTURE AND ${_${_PYTHON_PREFIX}_LIBRARY_RELEASE} MATCHES "^(.+)/lib/${CMAKE_LIBRARY_ARCHITECTURE}") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") + else() + # assume library is in a directory under root + get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) + get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_PREFIX}" DIRECTORY) + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") + endif() + endif() + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS VERSION ${_${_PYTHON_PREFIX}_VERSION}) + _python_get_registries (_${_PYTHON_PREFIX}_REGISTRY_PATHS VERSION ${_${_PYTHON_PREFIX}_VERSION}) + _python_get_path_suffixes (_${_PYTHON_PREFIX}_PATH_SUFFIXES VERSION ${_${_PYTHON_PREFIX}_VERSION} INCLUDE) + + if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "FIRST") + find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES ${_${_PYTHON_PREFIX}_INCLUDE_NAMES} + HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "FIRST") + find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES ${_${_PYTHON_PREFIX}_INCLUDE_NAMES} + HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${_${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + if (APPLE AND _${_PYTHON_PREFIX}_FIND_FRAMEWORK STREQUAL "LAST") + set (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS}) + else() + unset (__${_PYTHON_PREFIX}_FRAMEWORK_PATHS) + endif() + + if (WIN32 AND _${_PYTHON_PREFIX}_FIND_REGISTRY STREQUAL "LAST") + set (__${_PYTHON_PREFIX}_REGISTRY_PATHS ${_${_PYTHON_PREFIX}_REGISTRY_PATHS}) + else() + unset (__${_PYTHON_PREFIX}_REGISTRY_PATHS) + endif() + + find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES ${_${_PYTHON_PREFIX}_INCLUDE_NAMES} + HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_VIRTUALENV_PATHS} + ${__${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + ${__${_PYTHON_PREFIX}_REGISTRY_PATHS} + PATH_SUFFIXES ${_${_PYTHON_PREFIX}_PATH_SUFFIXES} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + # search header file in standard locations + find_path (_${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES ${_${_PYTHON_PREFIX}_INCLUDE_NAMES}) + + break() + endwhile() + + set (${_PYTHON_PREFIX}_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") + + if (_${_PYTHON_PREFIX}_INCLUDE_DIR AND NOT EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}") + set (_${_PYTHON_PREFIX}_Development_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_INCLUDE_DIR}\"") + set_property (CACHE _${_PYTHON_PREFIX}_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_INCLUDE_DIR-NOTFOUND") + endif() + + if (_${_PYTHON_PREFIX}_INCLUDE_DIR) + # retrieve version from header file + _python_get_version (INCLUDE PREFIX _${_PYTHON_PREFIX}_INC_) + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE) + if ("${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}" + VERSION_EQUAL _${_PYTHON_PREFIX}_VERSION) + # update versioning + set (_${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_INC_VERSION}) + set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH}) + endif() + else() + set (_${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_INC_VERSION}) + set (_${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}) + set (_${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}) + set (_${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_INC_VERSION_PATCH}) + endif() + endif() + endif() + + if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT ${_PYTHON_PREFIX}_Compiler_FOUND) + # set public version information + set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) + set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) + set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) + set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) + endif() + + # define public variables + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + set (${_PYTHON_PREFIX}_LIBRARY_DEBUG "${_${_PYTHON_PREFIX}_LIBRARY_DEBUG}") + _python_select_library_configurations (${_PYTHON_PREFIX}) + + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") + + if (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") + elseif (_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") + else() + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${_PYTHON_PREFIX}_RUNTIME_LIBRARY-NOTFOUND") + endif() + + _python_set_library_dirs (${_PYTHON_PREFIX}_LIBRARY_DIRS + _${_PYTHON_PREFIX}_LIBRARY_RELEASE + _${_PYTHON_PREFIX}_LIBRARY_DEBUG) + if (UNIX) + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS ${${_PYTHON_PREFIX}_LIBRARY_DIRS}) + endif() + else() + _python_set_library_dirs (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS + _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) + endif() + endif() + + if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE OR _${_PYTHON_PREFIX}_INCLUDE_DIR) + if (${_PYTHON_PREFIX}_Interpreter_FOUND OR ${_PYTHON_PREFIX}_Compiler_FOUND) + # development environment must be compatible with interpreter/compiler + if ("${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}" VERSION_EQUAL "${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}" + AND "${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}" VERSION_EQUAL "${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}") + _python_set_development_module_found (Module) + _python_set_development_module_found (Embed) + endif() + elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR + AND "${_${_PYTHON_PREFIX}_INC_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_INC_VERSION_MINOR}" VERSION_EQUAL "${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR}") + _python_set_development_module_found (Module) + _python_set_development_module_found (Embed) + endif() + if (DEFINED _${_PYTHON_PREFIX}_FIND_ABI AND + (NOT _${_PYTHON_PREFIX}_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS + OR NOT _${_PYTHON_PREFIX}_INC_ABI IN_LIST _${_PYTHON_PREFIX}_ABIFLAGS)) + set (${_PYTHON_PREFIX}_Development.Module_FOUND FALSE) + set (${_PYTHON_PREFIX}_Development.Embed_FOUND FALSE) + endif() + endif() + + if (( ${_PYTHON_PREFIX}_Development.Module_FOUND + AND ${_PYTHON_PREFIX}_Development.Embed_FOUND) + OR (NOT "Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Development.Embed_FOUND) + OR (NOT "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Development.Module_FOUND)) + unset (_${_PYTHON_PREFIX}_Development_REASON_FAILURE) + endif() + + if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Development.Module_FOUND + AND ${_PYTHON_PREFIX}_Development.Embed_FOUND) + set (${_PYTHON_PREFIX}_Development_FOUND TRUE) + endif() + + if ((${_PYTHON_PREFIX}_Development.Module_FOUND + OR ${_PYTHON_PREFIX}_Development.Embed_FOUND) + AND EXISTS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/PyPy.h") + # retrieve PyPy version + file (STRINGS "${_${_PYTHON_PREFIX}_INCLUDE_DIR}/patchlevel.h" ${_PYTHON_PREFIX}_PyPy_VERSION + REGEX "^#define[ \t]+PYPY_VERSION[ \t]+\"[^\"]+\"") + string (REGEX REPLACE "^#define[ \t]+PYPY_VERSION[ \t]+\"([^\"]+)\".*" "\\1" + ${_PYTHON_PREFIX}_PyPy_VERSION "${${_PYTHON_PREFIX}_PyPy_VERSION}") + endif() + + unset(${_PYTHON_PREFIX}_LINK_OPTIONS) + if (${_PYTHON_PREFIX}_Development.Embed_FOUND AND APPLE + AND ${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") + # rpath must be specified if python is part of a framework + unset(_${_PYTHON_PREFIX}_is_prefix) + foreach (_${_PYTHON_PREFIX}_implementation IN LISTS _${_PYTHON_PREFIX}_FIND_IMPLEMENTATIONS) + foreach (_${_PYTHON_PREFIX}_framework IN LISTS _${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_implementation}_FRAMEWORKS) + cmake_path (IS_PREFIX _${_PYTHON_PREFIX}_framework "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" _${_PYTHON_PREFIX}_is_prefix) + if (_${_PYTHON_PREFIX}_is_prefix) + cmake_path (GET _${_PYTHON_PREFIX}_framework PARENT_PATH _${_PYTHON_PREFIX}_framework) + set (${_PYTHON_PREFIX}_LINK_OPTIONS "LINKER:-rpath,${_${_PYTHON_PREFIX}_framework}") + break() + endif() + endforeach() + if (_${_PYTHON_PREFIX}_is_prefix) + break() + endif() + endforeach() + unset(_${_PYTHON_PREFIX}_implementation) + unset(_${_PYTHON_PREFIX}_framework) + unset(_${_PYTHON_PREFIX}_is_prefix) + endif() + + if (NOT DEFINED ${_PYTHON_PREFIX}_SOABI) + _python_get_config_var (${_PYTHON_PREFIX}_SOABI SOABI) + endif() + + _python_compute_development_signature (Module) + _python_compute_development_signature (Embed) + + # Restore the original find library ordering + if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) + set (CMAKE_FIND_LIBRARY_SUFFIXES ${_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES}) + endif() + + if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + set (${_PYTHON_PREFIX}_LIBRARY "${_${_PYTHON_PREFIX}_LIBRARY_RELEASE}" CACHE FILEPATH "${_PYTHON_PREFIX} Library") + endif() + if ("INCLUDE_DIR" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS) + set (${_PYTHON_PREFIX}_INCLUDE_DIR "${_${_PYTHON_PREFIX}_INCLUDE_DIR}" CACHE FILEPATH "${_PYTHON_PREFIX} Include Directory") + endif() + endif() + + _python_mark_as_internal (_${_PYTHON_PREFIX}_LIBRARY_RELEASE + _${_PYTHON_PREFIX}_LIBRARY_DEBUG + _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG + _${_PYTHON_PREFIX}_INCLUDE_DIR + _${_PYTHON_PREFIX}_CONFIG + _${_PYTHON_PREFIX}_DEVELOPMENT_MODULE_SIGNATURE + _${_PYTHON_PREFIX}_DEVELOPMENT_EMBED_SIGNATURE) +endif() + +if (${_PYTHON_PREFIX}_FIND_REQUIRED_NumPy) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS) +endif() +if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS AND ${_PYTHON_PREFIX}_Interpreter_FOUND) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) + + if (DEFINED ${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR + AND IS_ABSOLUTE "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") + set (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}" CACHE INTERNAL "") + elseif (DEFINED _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) + # compute numpy signature. Depends on interpreter and development signatures + string (MD5 __${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}:${_${_PYTHON_PREFIX}_DEVELOPMENT_MODULE_SIGNATURE}:${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") + if (NOT __${_PYTHON_PREFIX}_NUMPY_SIGNATURE STREQUAL _${_PYTHON_PREFIX}_NUMPY_SIGNATURE + OR NOT EXISTS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") + unset (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR CACHE) + unset (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE CACHE) + endif() + endif() + + if (NOT _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) + execute_process(COMMAND ${${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys\ntry: import numpy; sys.stdout.write(numpy.get_include())\nexcept:pass\n" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_NumPy_PATH + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if (NOT _${_PYTHON_PREFIX}_RESULT) + find_path (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR + NAMES "numpy/arrayobject.h" "numpy/numpyconfig.h" + HINTS "${_${_PYTHON_PREFIX}_NumPy_PATH}" + NO_DEFAULT_PATH) + endif() + endif() + + set (${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") + + if(_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR AND NOT EXISTS "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}") + set (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE "Cannot find the directory \"${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}\"") + set_property (CACHE _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR PROPERTY VALUE "${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR-NOTFOUND") + endif() + + if (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR) + execute_process (COMMAND ${${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys\ntry: import numpy; sys.stdout.write(numpy.__version__)\nexcept:pass\n" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_NumPy_VERSION) + if (NOT _${_PYTHON_PREFIX}_RESULT) + set (${_PYTHON_PREFIX}_NumPy_VERSION "${_${_PYTHON_PREFIX}_NumPy_VERSION}") + else() + unset (${_PYTHON_PREFIX}_NumPy_VERSION) + endif() + + # final step: set NumPy founded only if Development.Module component is founded as well + set(${_PYTHON_PREFIX}_NumPy_FOUND ${${_PYTHON_PREFIX}_Development.Module_FOUND}) + else() + set (${_PYTHON_PREFIX}_NumPy_FOUND FALSE) + endif() + + if (${_PYTHON_PREFIX}_NumPy_FOUND) + unset (_${_PYTHON_PREFIX}_NumPy_REASON_FAILURE) + + # compute and save numpy signature + string (MD5 __${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${_${_PYTHON_PREFIX}_INTERPRETER_SIGNATURE}:${_${_PYTHON_PREFIX}_DEVELOPMENT_MODULE_SIGNATURE}:${${_PYTHON_PREFIX}_NumPyINCLUDE_DIR}") + set (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE "${__${_PYTHON_PREFIX}_NUMPY_SIGNATURE}" CACHE INTERNAL "") + else() + unset (_${_PYTHON_PREFIX}_NUMPY_SIGNATURE CACHE) + endif() + + if (${_PYTHON_PREFIX}_ARTIFACTS_INTERACTIVE) + set (${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR "${_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR}" CACHE FILEPATH "${_PYTHON_PREFIX} NumPy Include Directory") + endif() + + _python_mark_as_internal (_${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR + _${_PYTHON_PREFIX}_NUMPY_SIGNATURE) +endif() + +# final validation +if (${_PYTHON_PREFIX}_VERSION_MAJOR AND + NOT ${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Found unsuitable major version \"${${_PYTHON_PREFIX}_VERSION_MAJOR}\", but required major version is exact version \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") + + cmake_policy(POP) + return() +endif() + +unset (_${_PYTHON_PREFIX}_REASON_FAILURE) +foreach (_${_PYTHON_PREFIX}_COMPONENT IN ITEMS Interpreter Compiler Development NumPy) + if (_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE) + string (APPEND _${_PYTHON_PREFIX}_REASON_FAILURE "\n ${_${_PYTHON_PREFIX}_COMPONENT}: ${_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE}") + unset (_${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_REASON_FAILURE) + endif() +endforeach() + +find_package_handle_standard_args (${_PYTHON_PREFIX} + REQUIRED_VARS ${_${_PYTHON_PREFIX}_REQUIRED_VARS} + VERSION_VAR ${_PYTHON_PREFIX}_VERSION + HANDLE_VERSION_RANGE + HANDLE_COMPONENTS + REASON_FAILURE_MESSAGE "${_${_PYTHON_PREFIX}_REASON_FAILURE}") + +# Create imported targets and helper functions +if(_${_PYTHON_PREFIX}_CMAKE_ROLE STREQUAL "PROJECT") + if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Interpreter_FOUND + AND NOT TARGET ${_PYTHON_PREFIX}::Interpreter) + add_executable (${_PYTHON_PREFIX}::Interpreter IMPORTED) + set_property (TARGET ${_PYTHON_PREFIX}::Interpreter + PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_EXECUTABLE}") + endif() + + if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Compiler_FOUND + AND NOT TARGET ${_PYTHON_PREFIX}::Compiler) + add_executable (${_PYTHON_PREFIX}::Compiler IMPORTED) + set_property (TARGET ${_PYTHON_PREFIX}::Compiler + PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_COMPILER}") + endif() + + if (("Development.Module" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Development.Module_FOUND) + OR ("Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Development.Embed_FOUND)) + + macro (__PYTHON_IMPORT_LIBRARY __name) + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$" + OR ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) + set (_${_PYTHON_PREFIX}_LIBRARY_TYPE SHARED) + else() + set (_${_PYTHON_PREFIX}_LIBRARY_TYPE STATIC) + endif() + + if (NOT TARGET ${__name}) + add_library (${__name} ${_${_PYTHON_PREFIX}_LIBRARY_TYPE} IMPORTED) + endif() + + set_property (TARGET ${__name} + PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}") + + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) + # System manage shared libraries in two parts: import and runtime + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + set_property (TARGET ${__name} PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) + set_target_properties (${__name} + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" + IMPORTED_IMPLIB_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" + IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") + set_target_properties (${__name} + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" + IMPORTED_IMPLIB_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}" + IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") + else() + set_target_properties (${__name} + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_IMPLIB "${${_PYTHON_PREFIX}_LIBRARIES}" + IMPORTED_LOCATION "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") + endif() + else() + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + set_property (TARGET ${__name} PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) + set_target_properties (${__name} + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" + IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}") + set_target_properties (${__name} + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" + IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}") + else() + set_target_properties (${__name} + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}") + endif() + endif() + + if (_${_PYTHON_PREFIX}_LIBRARY_TYPE STREQUAL "STATIC") + # extend link information with dependent libraries + _python_get_config_var (_${_PYTHON_PREFIX}_LINK_LIBRARIES LIBS) + if (_${_PYTHON_PREFIX}_LINK_LIBRARIES) + set_property (TARGET ${__name} + PROPERTY INTERFACE_LINK_LIBRARIES ${_${_PYTHON_PREFIX}_LINK_LIBRARIES}) + endif() + endif() + + if (${_PYTHON_PREFIX}_LINK_OPTIONS + AND _${_PYTHON_PREFIX}_LIBRARY_TYPE STREQUAL "SHARED") + set_property (TARGET ${__name} PROPERTY INTERFACE_LINK_OPTIONS "${${_PYTHON_PREFIX}_LINK_OPTIONS}") + endif() + endmacro() + + if (${_PYTHON_PREFIX}_Development.Embed_FOUND) + __python_import_library (${_PYTHON_PREFIX}::Python) + endif() + + if (${_PYTHON_PREFIX}_Development.Module_FOUND) + if ("LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_MODULE_ARTIFACTS) + # On Windows/CYGWIN/MSYS, Python::Module is the same as Python::Python + # but ALIAS cannot be used because the imported library is not GLOBAL. + __python_import_library (${_PYTHON_PREFIX}::Module) + else() + if (NOT TARGET ${_PYTHON_PREFIX}::Module) + add_library (${_PYTHON_PREFIX}::Module INTERFACE IMPORTED) + endif() + set_property (TARGET ${_PYTHON_PREFIX}::Module + PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}") + + # When available, enforce shared library generation with undefined symbols + if (APPLE) + set_property (TARGET ${_PYTHON_PREFIX}::Module + PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") + endif() + if (CMAKE_SYSTEM_NAME STREQUAL "SunOS") + set_property (TARGET ${_PYTHON_PREFIX}::Module + PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-z,nodefs") + endif() + if (CMAKE_SYSTEM_NAME STREQUAL "AIX") + set_property (TARGET ${_PYTHON_PREFIX}::Module + PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-b,erok") + endif() + endif() + endif() + + # + # PYTHON_ADD_LIBRARY ( [STATIC|SHARED|MODULE] src1 src2 ... srcN) + # It is used to build modules for python. + # + function (__${_PYTHON_PREFIX}_ADD_LIBRARY prefix name) + cmake_parse_arguments (PARSE_ARGV 2 PYTHON_ADD_LIBRARY "STATIC;SHARED;MODULE;WITH_SOABI" "" "") + + if (PYTHON_ADD_LIBRARY_STATIC) + set (type STATIC) + elseif (PYTHON_ADD_LIBRARY_SHARED) + set (type SHARED) + else() + set (type MODULE) + endif() + + if (type STREQUAL "MODULE" AND NOT TARGET ${prefix}::Module) + message (SEND_ERROR "${prefix}_ADD_LIBRARY: dependent target '${prefix}::Module' is not defined.\n Did you miss to request COMPONENT 'Development.Module'?") + return() + endif() + if (NOT type STREQUAL "MODULE" AND NOT TARGET ${prefix}::Python) + message (SEND_ERROR "${prefix}_ADD_LIBRARY: dependent target '${prefix}::Python' is not defined.\n Did you miss to request COMPONENT 'Development.Embed'?") + return() + endif() + + add_library (${name} ${type} ${PYTHON_ADD_LIBRARY_UNPARSED_ARGUMENTS}) + + get_property (type TARGET ${name} PROPERTY TYPE) + + if (type STREQUAL "MODULE_LIBRARY") + target_link_libraries (${name} PRIVATE ${prefix}::Module) + # customize library name to follow module name rules + set_property (TARGET ${name} PROPERTY PREFIX "") + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set_property (TARGET ${name} PROPERTY SUFFIX ".pyd") + endif() + + if (PYTHON_ADD_LIBRARY_WITH_SOABI AND ${prefix}_SOABI) + get_property (suffix TARGET ${name} PROPERTY SUFFIX) + if (NOT suffix) + set (suffix "${CMAKE_SHARED_MODULE_SUFFIX}") + endif() + set_property (TARGET ${name} PROPERTY SUFFIX ".${${prefix}_SOABI}${suffix}") + endif() + else() + if (PYTHON_ADD_LIBRARY_WITH_SOABI) + message (AUTHOR_WARNING "Find${prefix}: Option `WITH_SOABI` is only supported for `MODULE` library type.") + endif() + target_link_libraries (${name} PRIVATE ${prefix}::Python) + endif() + endfunction() + endif() + + if ("NumPy" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS AND ${_PYTHON_PREFIX}_NumPy_FOUND + AND NOT TARGET ${_PYTHON_PREFIX}::NumPy AND TARGET ${_PYTHON_PREFIX}::Module) + add_library (${_PYTHON_PREFIX}::NumPy INTERFACE IMPORTED) + set_property (TARGET ${_PYTHON_PREFIX}::NumPy + PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_NumPy_INCLUDE_DIRS}") + target_link_libraries (${_PYTHON_PREFIX}::NumPy INTERFACE ${_PYTHON_PREFIX}::Module) + endif() +endif() + +# final clean-up + +# Restore CMAKE_FIND_APPBUNDLE +if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) + set (CMAKE_FIND_APPBUNDLE ${_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE}) + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_APPBUNDLE) +else() + unset (CMAKE_FIND_APPBUNDLE) +endif() +# Restore CMAKE_FIND_FRAMEWORK +if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) + set (CMAKE_FIND_FRAMEWORK ${_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK}) + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) +else() + unset (CMAKE_FIND_FRAMEWORK) +endif() + +cmake_policy(POP) diff --git a/scenario/cmake/FindPython3.cmake b/scenario/cmake/FindPython3.cmake new file mode 100644 index 000000000..f826fcfe2 --- /dev/null +++ b/scenario/cmake/FindPython3.cmake @@ -0,0 +1,493 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPython3 +----------- + +.. versionadded:: 3.12 + +Find Python 3 interpreter, compiler and development environment (include +directories and libraries). + +.. versionadded:: 3.19 + When a version is requested, it can be specified as a simple value or as a + range. For a detailed description of version range usage and capabilities, + refer to the :command:`find_package` command. + +The following components are supported: + +* ``Interpreter``: search for Python 3 interpreter +* ``Compiler``: search for Python 3 compiler. Only offered by IronPython. +* ``Development``: search for development artifacts (include directories and + libraries). + + .. versionadded:: 3.18 + This component includes two sub-components which can be specified + independently: + + * ``Development.Module``: search for artifacts for Python 3 module + developments. + * ``Development.Embed``: search for artifacts for Python 3 embedding + developments. + +* ``NumPy``: search for NumPy include directories. + +.. versionadded:: 3.14 + Added the ``NumPy`` component. + +If no ``COMPONENTS`` are specified, ``Interpreter`` is assumed. + +If component ``Development`` is specified, it implies sub-components +``Development.Module`` and ``Development.Embed``. + +To ensure consistent versions between components ``Interpreter``, ``Compiler``, +``Development`` (or one of its sub-components) and ``NumPy``, specify all +components at the same time:: + + find_package (Python3 COMPONENTS Interpreter Development) + +This module looks only for version 3 of Python. This module can be used +concurrently with :module:`FindPython2` module to use both Python versions. + +The :module:`FindPython` module can be used if Python version does not matter +for you. + +.. note:: + + If components ``Interpreter`` and ``Development`` (or one of its + sub-components) are both specified, this module search only for interpreter + with same platform architecture as the one defined by ``CMake`` + configuration. This constraint does not apply if only ``Interpreter`` + component is specified. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :ref:`Imported Targets `: + +.. versionchanged:: 3.14 + :ref:`Imported Targets ` are only created when + :prop_gbl:`CMAKE_ROLE` is ``PROJECT``. + +``Python3::Interpreter`` + Python 3 interpreter. Target defined if component ``Interpreter`` is found. +``Python3::Compiler`` + Python 3 compiler. Target defined if component ``Compiler`` is found. + +``Python3::Module`` + .. versionadded:: 3.15 + + Python 3 library for Python module. Target defined if component + ``Development.Module`` is found. + +``Python3::Python`` + Python 3 library for Python embedding. Target defined if component + ``Development.Embed`` is found. + +``Python3::NumPy`` + .. versionadded:: 3.14 + + NumPy library for Python 3. Target defined if component ``NumPy`` is found. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables in your project +(see :ref:`Standard Variable Names `): + +``Python3_FOUND`` + System has the Python 3 requested components. +``Python3_Interpreter_FOUND`` + System has the Python 3 interpreter. +``Python3_EXECUTABLE`` + Path to the Python 3 interpreter. +``Python3_INTERPRETER_ID`` + A short string unique to the interpreter. Possible values include: + * Python + * ActivePython + * Anaconda + * Canopy + * IronPython + * PyPy +``Python3_STDLIB`` + Standard platform independent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=False,standard_lib=True)`` + or else ``sysconfig.get_path('stdlib')``. +``Python3_STDARCH`` + Standard platform dependent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=True,standard_lib=True)`` + or else ``sysconfig.get_path('platstdlib')``. +``Python3_SITELIB`` + Third-party platform independent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=False,standard_lib=False)`` + or else ``sysconfig.get_path('purelib')``. +``Python3_SITEARCH`` + Third-party platform dependent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=True,standard_lib=False)`` + or else ``sysconfig.get_path('platlib')``. + +``Python3_SOABI`` + .. versionadded:: 3.17 + + Extension suffix for modules. + + Information returned by + ``distutils.sysconfig.get_config_var('SOABI')`` or computed from + ``distutils.sysconfig.get_config_var('EXT_SUFFIX')`` or + ``python3-config --extension-suffix``. If package ``distutils.sysconfig`` is + not available, ``sysconfig.get_config_var('SOABI')`` or + ``sysconfig.get_config_var('EXT_SUFFIX')`` are used. + +``Python3_Compiler_FOUND`` + System has the Python 3 compiler. +``Python3_COMPILER`` + Path to the Python 3 compiler. Only offered by IronPython. +``Python3_COMPILER_ID`` + A short string unique to the compiler. Possible values include: + * IronPython + +``Python3_DOTNET_LAUNCHER`` + .. versionadded:: 3.18 + + The ``.Net`` interpreter. Only used by ``IronPython`` implementation. + +``Python3_Development_FOUND`` + + System has the Python 3 development artifacts. + +``Python3_Development.Module_FOUND`` + .. versionadded:: 3.18 + + System has the Python 3 development artifacts for Python module. + +``Python3_Development.Embed_FOUND`` + .. versionadded:: 3.18 + + System has the Python 3 development artifacts for Python embedding. + +``Python3_INCLUDE_DIRS`` + + The Python 3 include directories. + +``Python3_LINK_OPTIONS`` + .. versionadded:: 3.19 + + The Python 3 link options. Some configurations require specific link options + for a correct build and execution. + +``Python3_LIBRARIES`` + The Python 3 libraries. +``Python3_LIBRARY_DIRS`` + The Python 3 library directories. +``Python3_RUNTIME_LIBRARY_DIRS`` + The Python 3 runtime library directories. +``Python3_VERSION`` + Python 3 version. +``Python3_VERSION_MAJOR`` + Python 3 major version. +``Python3_VERSION_MINOR`` + Python 3 minor version. +``Python3_VERSION_PATCH`` + Python 3 patch version. + +``Python3_PyPy_VERSION`` + .. versionadded:: 3.18 + + Python 3 PyPy version. + +``Python3_NumPy_FOUND`` + .. versionadded:: 3.14 + + System has the NumPy. + +``Python3_NumPy_INCLUDE_DIRS`` + .. versionadded:: 3.14 + + The NumPy include directories. + +``Python3_NumPy_VERSION`` + .. versionadded:: 3.14 + + The NumPy version. + +Hints +^^^^^ + +``Python3_ROOT_DIR`` + Define the root directory of a Python 3 installation. + +``Python3_USE_STATIC_LIBS`` + * If not defined, search for shared libraries and static libraries in that + order. + * If set to TRUE, search **only** for static libraries. + * If set to FALSE, search **only** for shared libraries. + +``Python3_FIND_ABI`` + .. versionadded:: 3.16 + + This variable defines which ABIs, as defined in + `PEP 3149 `_, should be searched. + + .. note:: + + If ``Python3_FIND_ABI`` is not defined, any ABI will be searched. + + The ``Python3_FIND_ABI`` variable is a 3-tuple specifying, in that order, + ``pydebug`` (``d``), ``pymalloc`` (``m``) and ``unicode`` (``u``) flags. + Each element can be set to one of the following: + + * ``ON``: Corresponding flag is selected. + * ``OFF``: Corresponding flag is not selected. + * ``ANY``: The two possibilities (``ON`` and ``OFF``) will be searched. + + From this 3-tuple, various ABIs will be searched starting from the most + specialized to the most general. Moreover, ``debug`` versions will be + searched **after** ``non-debug`` ones. + + For example, if we have:: + + set (Python3_FIND_ABI "ON" "ANY" "ANY") + + The following flags combinations will be appended, in that order, to the + artifact names: ``dmu``, ``dm``, ``du``, and ``d``. + + And to search any possible ABIs:: + + set (Python3_FIND_ABI "ANY" "ANY" "ANY") + + The following combinations, in that order, will be used: ``mu``, ``m``, + ``u``, ````, ``dmu``, ``dm``, ``du`` and ``d``. + + .. note:: + + This hint is useful only on ``POSIX`` systems. So, on ``Windows`` systems, + when ``Python3_FIND_ABI`` is defined, ``Python`` distributions from + `python.org `_ will be found only if value for + each flag is ``OFF`` or ``ANY``. + +``Python3_FIND_STRATEGY`` + .. versionadded:: 3.15 + + This variable defines how lookup will be done. + The ``Python3_FIND_STRATEGY`` variable can be set to one of the following: + + * ``VERSION``: Try to find the most recent version in all specified + locations. + This is the default if policy :policy:`CMP0094` is undefined or set to + ``OLD``. + * ``LOCATION``: Stops lookup as soon as a version satisfying version + constraints is founded. + This is the default if policy :policy:`CMP0094` is set to ``NEW``. + +``Python3_FIND_REGISTRY`` + .. versionadded:: 3.13 + + On Windows the ``Python3_FIND_REGISTRY`` variable determine the order + of preference between registry and environment variables. + The ``Python3_FIND_REGISTRY`` variable can be set to one of the following: + + * ``FIRST``: Try to use registry before environment variables. + This is the default. + * ``LAST``: Try to use registry after environment variables. + * ``NEVER``: Never try to use registry. + +``Python3_FIND_FRAMEWORK`` + .. versionadded:: 3.15 + + On macOS the ``Python3_FIND_FRAMEWORK`` variable determine the order of + preference between Apple-style and unix-style package components. + This variable can take same values as :variable:`CMAKE_FIND_FRAMEWORK` + variable. + + .. note:: + + Value ``ONLY`` is not supported so ``FIRST`` will be used instead. + + If ``Python3_FIND_FRAMEWORK`` is not defined, :variable:`CMAKE_FIND_FRAMEWORK` + variable will be used, if any. + +``Python3_FIND_VIRTUALENV`` + .. versionadded:: 3.15 + + This variable defines the handling of virtual environments managed by + ``virtualenv`` or ``conda``. It is meaningful only when a virtual environment + is active (i.e. the ``activate`` script has been evaluated). In this case, it + takes precedence over ``Python3_FIND_REGISTRY`` and ``CMAKE_FIND_FRAMEWORK`` + variables. The ``Python3_FIND_VIRTUALENV`` variable can be set to one of the + following: + + * ``FIRST``: The virtual environment is used before any other standard + paths to look-up for the interpreter. This is the default. + * ``ONLY``: Only the virtual environment is used to look-up for the + interpreter. + * ``STANDARD``: The virtual environment is not used to look-up for the + interpreter but environment variable ``PATH`` is always considered. + In this case, variable ``Python3_FIND_REGISTRY`` (Windows) or + ``CMAKE_FIND_FRAMEWORK`` (macOS) can be set with value ``LAST`` or + ``NEVER`` to select preferably the interpreter from the virtual + environment. + + .. versionadded:: 3.17 + Added support for ``conda`` environments. + + .. note:: + + If the component ``Development`` is requested, it is **strongly** + recommended to also include the component ``Interpreter`` to get expected + result. + +``Python3_FIND_IMPLEMENTATIONS`` + .. versionadded:: 3.18 + + This variable defines, in an ordered list, the different implementations + which will be searched. The ``Python3_FIND_IMPLEMENTATIONS`` variable can + hold the following values: + + * ``CPython``: this is the standard implementation. Various products, like + ``Anaconda`` or ``ActivePython``, rely on this implementation. + * ``IronPython``: This implementation use the ``CSharp`` language for + ``.NET Framework`` on top of the `Dynamic Language Runtime` (``DLR``). + See `IronPython `_. + * ``PyPy``: This implementation use ``RPython`` language and + ``RPython translation toolchain`` to produce the python interpreter. + See `PyPy `_. + + The default value is: + + * Windows platform: ``CPython``, ``IronPython`` + * Other platforms: ``CPython`` + + .. note:: + + This hint has the lowest priority of all hints, so even if, for example, + you specify ``IronPython`` first and ``CPython`` in second, a python + product based on ``CPython`` can be selected because, for example with + ``Python3_FIND_STRATEGY=LOCATION``, each location will be search first for + ``IronPython`` and second for ``CPython``. + + .. note:: + + When ``IronPython`` is specified, on platforms other than ``Windows``, the + ``.Net`` interpreter (i.e. ``mono`` command) is expected to be available + through the ``PATH`` variable. + +``Python3_FIND_UNVERSIONED_NAMES`` + .. versionadded:: 3.20 + + This variable defines how the generic names will be searched. Currently, it + only applies to the generic names of the interpreter, namely, ``python3`` and + ``python``. + The ``Python3_FIND_UNVERSIONED_NAMES`` variable can be set to one of the + following values: + + * ``FIRST``: The generic names are searched before the more specialized ones + (such as ``python3.5`` for example). + * ``LAST``: The generic names are searched after the more specialized ones. + This is the default. + * ``NEVER``: The generic name are not searched at all. + +Artifacts Specification +^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.16 + +To solve special cases, it is possible to specify directly the artifacts by +setting the following variables: + +``Python3_EXECUTABLE`` + The path to the interpreter. + +``Python3_COMPILER`` + The path to the compiler. + +``Python3_DOTNET_LAUNCHER`` + .. versionadded:: 3.18 + + The ``.Net`` interpreter. Only used by ``IronPython`` implementation. + +``Python3_LIBRARY`` + The path to the library. It will be used to compute the + variables ``Python3_LIBRARIES``, ``Python3_LIBRARY_DIRS`` and + ``Python3_RUNTIME_LIBRARY_DIRS``. + +``Python3_INCLUDE_DIR`` + The path to the directory of the ``Python`` headers. It will be used to + compute the variable ``Python3_INCLUDE_DIRS``. + +``Python3_NumPy_INCLUDE_DIR`` + The path to the directory of the ``NumPy`` headers. It will be used to + compute the variable ``Python3_NumPy_INCLUDE_DIRS``. + +.. note:: + + All paths must be absolute. Any artifact specified with a relative path + will be ignored. + +.. note:: + + When an artifact is specified, all ``HINTS`` will be ignored and no search + will be performed for this artifact. + + If more than one artifact is specified, it is the user's responsibility to + ensure the consistency of the various artifacts. + +By default, this module supports multiple calls in different directories of a +project with different version/component requirements while providing correct +and consistent results for each call. To support this behavior, ``CMake`` cache +is not used in the traditional way which can be problematic for interactive +specification. So, to enable also interactive specification, module behavior +can be controlled with the following variable: + +``Python3_ARTIFACTS_INTERACTIVE`` + .. versionadded:: 3.18 + + Selects the behavior of the module. This is a boolean variable: + + * If set to ``TRUE``: Create CMake cache entries for the above artifact + specification variables so that users can edit them interactively. + This disables support for multiple version/component requirements. + * If set to ``FALSE`` or undefined: Enable multiple version/component + requirements. + +Commands +^^^^^^^^ + +This module defines the command ``Python3_add_library`` (when +:prop_gbl:`CMAKE_ROLE` is ``PROJECT``), which has the same semantics as +:command:`add_library` and adds a dependency to target ``Python3::Python`` or, +when library type is ``MODULE``, to target ``Python3::Module`` and takes care +of Python module naming rules:: + + Python3_add_library ( [STATIC | SHARED | MODULE [WITH_SOABI]] + [ ...]) + +If the library type is not specified, ``MODULE`` is assumed. + +.. versionadded:: 3.17 + For ``MODULE`` library type, if option ``WITH_SOABI`` is specified, the + module suffix will include the ``Python3_SOABI`` value, if any. +#]=======================================================================] + + +set (_PYTHON_PREFIX Python3) + +set (_Python3_REQUIRED_VERSION_MAJOR 3) + +include (${CMAKE_CURRENT_LIST_DIR}/FindPython/Support.cmake) + +if (COMMAND __Python3_add_library) + macro (Python3_add_library) + __Python3_add_library (Python3 ${ARGV}) + endmacro() +endif() + +unset (_PYTHON_PREFIX) diff --git a/cmake/FindSphinx.cmake b/scenario/cmake/FindSphinx.cmake similarity index 100% rename from cmake/FindSphinx.cmake rename to scenario/cmake/FindSphinx.cmake diff --git a/cmake/FindSphinxApidoc.cmake b/scenario/cmake/FindSphinxApidoc.cmake similarity index 100% rename from cmake/FindSphinxApidoc.cmake rename to scenario/cmake/FindSphinxApidoc.cmake diff --git a/cmake/FindSphinxMultiVersion.cmake b/scenario/cmake/FindSphinxMultiVersion.cmake similarity index 100% rename from cmake/FindSphinxMultiVersion.cmake rename to scenario/cmake/FindSphinxMultiVersion.cmake diff --git a/cmake/ImportTargetsCitadel.cmake b/scenario/cmake/ImportTargetsCitadel.cmake similarity index 100% rename from cmake/ImportTargetsCitadel.cmake rename to scenario/cmake/ImportTargetsCitadel.cmake diff --git a/cmake/ImportTargetsDome.cmake b/scenario/cmake/ImportTargetsDome.cmake similarity index 100% rename from cmake/ImportTargetsDome.cmake rename to scenario/cmake/ImportTargetsDome.cmake diff --git a/scenario/cmake/ImportTargetsEdifice.cmake b/scenario/cmake/ImportTargetsEdifice.cmake new file mode 100644 index 000000000..8099dbb8c --- /dev/null +++ b/scenario/cmake/ImportTargetsEdifice.cmake @@ -0,0 +1,73 @@ +include(AliasImportedTarget) + +# https://ignitionrobotics.org/docs/edifice/install#edifice-libraries + +alias_imported_target( + PACKAGE_ORIG sdformat11 + PACKAGE_DEST sdformat + TARGETS_ORIG sdformat11 + TARGETS_DEST sdformat + NAMESPACE_ORIG sdformat11 + NAMESPACE_DEST sdformat + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-gazebo5 + PACKAGE_DEST ignition-gazebo + TARGETS_ORIG ignition-gazebo5 core + TARGETS_DEST ignition-gazebo core + NAMESPACE_ORIG ignition-gazebo5 + NAMESPACE_DEST ignition-gazebo + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-common4 + PACKAGE_DEST ignition-common + TARGETS_ORIG ignition-common4 + TARGETS_DEST ignition-common + NAMESPACE_ORIG ignition-common4 + NAMESPACE_DEST ignition-common + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-sensors5-all + PACKAGE_DEST ignition-sensors-all + TARGETS_ORIG ignition-sensors5-all + TARGETS_DEST ignition-sensors-all + NAMESPACE_ORIG ignition-sensors5 + NAMESPACE_DEST ignition-sensors + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-rendering5 + PACKAGE_DEST ignition-rendering + TARGETS_ORIG ignition-rendering5 + TARGETS_DEST ignition-rendering + NAMESPACE_ORIG ignition-rendering5 + NAMESPACE_DEST ignition-rendering + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-gazebo5-rendering + PACKAGE_DEST ignition-gazebo-rendering + TARGETS_ORIG ignition-gazebo5-rendering + TARGETS_DEST ignition-gazebo-rendering + NAMESPACE_ORIG ignition-gazebo5 + NAMESPACE_DEST ignition-gazebo + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-physics4 + PACKAGE_DEST ignition-physics + TARGETS_ORIG ignition-physics4 + TARGETS_DEST ignition-physics + NAMESPACE_ORIG ignition-physics4 + NAMESPACE_DEST ignition-physics + REQUIRED TRUE + ) diff --git a/scenario/cmake/ImportTargetsFortress.cmake b/scenario/cmake/ImportTargetsFortress.cmake new file mode 100644 index 000000000..57f7575cd --- /dev/null +++ b/scenario/cmake/ImportTargetsFortress.cmake @@ -0,0 +1,73 @@ +include(AliasImportedTarget) + +# https://ignitionrobotics.org/docs/fortress/install#fortress-libraries + +alias_imported_target( + PACKAGE_ORIG sdformat12 + PACKAGE_DEST sdformat + TARGETS_ORIG sdformat12 + TARGETS_DEST sdformat + NAMESPACE_ORIG sdformat12 + NAMESPACE_DEST sdformat + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-gazebo6 + PACKAGE_DEST ignition-gazebo + TARGETS_ORIG ignition-gazebo6 core + TARGETS_DEST ignition-gazebo core + NAMESPACE_ORIG ignition-gazebo6 + NAMESPACE_DEST ignition-gazebo + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-common4 + PACKAGE_DEST ignition-common + TARGETS_ORIG ignition-common4 + TARGETS_DEST ignition-common + NAMESPACE_ORIG ignition-common4 + NAMESPACE_DEST ignition-common + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-sensors6-all + PACKAGE_DEST ignition-sensors-all + TARGETS_ORIG ignition-sensors6-all + TARGETS_DEST ignition-sensors-all + NAMESPACE_ORIG ignition-sensors6 + NAMESPACE_DEST ignition-sensors + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-rendering6 + PACKAGE_DEST ignition-rendering + TARGETS_ORIG ignition-rendering6 + TARGETS_DEST ignition-rendering + NAMESPACE_ORIG ignition-rendering6 + NAMESPACE_DEST ignition-rendering + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-gazebo6-rendering + PACKAGE_DEST ignition-gazebo-rendering + TARGETS_ORIG ignition-gazebo6-rendering + TARGETS_DEST ignition-gazebo-rendering + NAMESPACE_ORIG ignition-gazebo6 + NAMESPACE_DEST ignition-gazebo + REQUIRED TRUE + ) + +alias_imported_target( + PACKAGE_ORIG ignition-physics5 + PACKAGE_DEST ignition-physics + TARGETS_ORIG ignition-physics5 + TARGETS_DEST ignition-physics + NAMESPACE_ORIG ignition-physics5 + NAMESPACE_DEST ignition-physics + REQUIRED TRUE + ) diff --git a/deps/CMakeLists.txt b/scenario/deps/CMakeLists.txt similarity index 100% rename from deps/CMakeLists.txt rename to scenario/deps/CMakeLists.txt diff --git a/deps/clara/clara.hpp b/scenario/deps/clara/clara.hpp similarity index 100% rename from deps/clara/clara.hpp rename to scenario/deps/clara/clara.hpp diff --git a/scenario/pyproject.toml b/scenario/pyproject.toml new file mode 100644 index 000000000..0fd292638 --- /dev/null +++ b/scenario/pyproject.toml @@ -0,0 +1,17 @@ +# Copyright (C) 2021 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. + +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "wheel", + "setuptools>=45", + "setuptools_scm[toml]>=6.0", + "cmake-build-extension", + "idyntree>=3.1", +] + +[tool.setuptools_scm] +root = "../" +local_scheme = "dirty-tag" diff --git a/scenario/setup.cfg b/scenario/setup.cfg new file mode 100644 index 000000000..83dd3bfc8 --- /dev/null +++ b/scenario/setup.cfg @@ -0,0 +1,55 @@ +# Copyright (C) 2021 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. + +[metadata] +name = scenario +description = SCENe interfAces for Robot Input/Output. +long_description = file: README.md +long_description_content_type = text/markdown +author = Diego Ferigo +author_email = dgferigo@gmail.com +license = LGPL +platforms = any +url = https://github.com/robotology/gym-ignition/tree/master/scenario + +project_urls = + Changelog = https://github.com/robotology/gym-ignition/releases + Tracker = https://github.com/robotology/gym-ignition/issues + Documentation = https://robotology.github.io/gym-ignition + Source = https://github.com/robotology/gym-ignition/tree/master/scenario + +keywords = + robotics + gazebo + ignition + simulation + physics + multibody + dynamics + physics simulation + middleware + real-time + +classifiers = + Development Status :: 5 - Production/Stable + Operating System :: POSIX :: Linux + Topic :: Games/Entertainment :: Simulation + Topic :: Scientific/Engineering :: Artificial Intelligence + Topic :: Scientific/Engineering :: Physics + Topic :: Software Development + Framework :: Robot Framework + Intended Audience :: Developers + Intended Audience :: Science/Research + Programming Language :: C++ + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: Implementation :: CPython + License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) + +[options] +zip_safe = False +python_requires = >=3.8 +install_requires = + packaging diff --git a/scenario/setup.py b/scenario/setup.py new file mode 100644 index 000000000..9c2ccc273 --- /dev/null +++ b/scenario/setup.py @@ -0,0 +1,19 @@ +# Copyright (C) 2019 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. + +import setuptools +from cmake_build_extension import BuildExtension, CMakeExtension + +setuptools.setup( + ext_modules=[ + CMakeExtension( + name="ScenarioCMakeProject", + install_prefix="scenario", + cmake_build_type="PyPI", + cmake_depends_on=["idyntree"], + disable_editable=True, + ) + ], + cmdclass=dict(build_ext=BuildExtension), +) diff --git a/cpp/scenario/CMakeLists.txt b/scenario/src/CMakeLists.txt similarity index 91% rename from cpp/scenario/CMakeLists.txt rename to scenario/src/CMakeLists.txt index 1f1cffe53..7721c9f77 100644 --- a/cpp/scenario/CMakeLists.txt +++ b/scenario/src/CMakeLists.txt @@ -9,6 +9,9 @@ set(SCENARIO_PRIVATE_DEPENDENCIES "") if(SCENARIO_USE_IGNITION) + option(ENABLE_PROFILER "Enable Ignition Profiler" OFF) + mark_as_advanced(ENABLE_PROFILER) + add_subdirectory(gazebo) add_subdirectory(plugins) add_subdirectory(controllers) diff --git a/cpp/scenario/controllers/CMakeLists.txt b/scenario/src/controllers/CMakeLists.txt similarity index 100% rename from cpp/scenario/controllers/CMakeLists.txt rename to scenario/src/controllers/CMakeLists.txt diff --git a/cpp/scenario/controllers/include/scenario/controllers/ComputedTorqueFixedBase.h b/scenario/src/controllers/include/scenario/controllers/ComputedTorqueFixedBase.h similarity index 100% rename from cpp/scenario/controllers/include/scenario/controllers/ComputedTorqueFixedBase.h rename to scenario/src/controllers/include/scenario/controllers/ComputedTorqueFixedBase.h diff --git a/cpp/scenario/controllers/include/scenario/controllers/Controller.h b/scenario/src/controllers/include/scenario/controllers/Controller.h similarity index 100% rename from cpp/scenario/controllers/include/scenario/controllers/Controller.h rename to scenario/src/controllers/include/scenario/controllers/Controller.h diff --git a/cpp/scenario/controllers/include/scenario/controllers/References.h b/scenario/src/controllers/include/scenario/controllers/References.h similarity index 100% rename from cpp/scenario/controllers/include/scenario/controllers/References.h rename to scenario/src/controllers/include/scenario/controllers/References.h diff --git a/cpp/scenario/controllers/src/ComputedTorqueFixedBase.cpp b/scenario/src/controllers/src/ComputedTorqueFixedBase.cpp similarity index 100% rename from cpp/scenario/controllers/src/ComputedTorqueFixedBase.cpp rename to scenario/src/controllers/src/ComputedTorqueFixedBase.cpp diff --git a/cpp/scenario/core/CMakeLists.txt b/scenario/src/core/CMakeLists.txt similarity index 100% rename from cpp/scenario/core/CMakeLists.txt rename to scenario/src/core/CMakeLists.txt diff --git a/cpp/scenario/core/include/scenario/core/Joint.h b/scenario/src/core/include/scenario/core/Joint.h similarity index 92% rename from cpp/scenario/core/include/scenario/core/Joint.h rename to scenario/src/core/include/scenario/core/Joint.h index ea98c6a8f..f61aeb51b 100644 --- a/cpp/scenario/core/include/scenario/core/Joint.h +++ b/scenario/src/core/include/scenario/core/Joint.h @@ -218,6 +218,28 @@ class scenario::core::Joint */ virtual Limit positionLimit(const size_t dof = 0) const = 0; + /** + * Get the velocity limit of a joint DOF. + * + * @param dof The index of the DOF. + * @throw std::runtime_error if the DOF is not valid. + * @return The velocity limit of the joint DOF. + */ + virtual Limit velocityLimit(const size_t dof = 0) const = 0; + + /** + * Set the maximum velocity of a joint DOF. + * + * This limit can be used to clip the velocity applied by joint + * controllers. + * + * @param maxVelocity The maximum velocity. + * @param dof The index of the DOF. + * @return True for success, false otherwise. + */ + virtual bool setVelocityLimit(const double maxVelocity, + const size_t dof = 0) = 0; + /** * Get the maximum generalized force that could be applied to a joint * DOF. @@ -378,6 +400,25 @@ class scenario::core::Joint */ virtual JointLimit jointPositionLimit() const = 0; + /** + * Get the velocity limits of the joint. + * + * @return The velocity limits of the joint. + */ + virtual JointLimit jointVelocityLimit() const = 0; + + /** + * Set the maximum velocity of the joint. + * + * This limit can be used to clip the velocity applied by joint + * controllers. + * + * @param maxVelocity A vector with the maximum velocity of the joint DOFs. + * @return True for success, false otherwise. + */ + virtual bool + setJointVelocityLimit(const std::vector& maxVelocity) = 0; + /** * Get the maximum generalized force that could be applied to the joint. * diff --git a/cpp/scenario/core/include/scenario/core/Link.h b/scenario/src/core/include/scenario/core/Link.h similarity index 98% rename from cpp/scenario/core/include/scenario/core/Link.h rename to scenario/src/core/include/scenario/core/Link.h index 4b2205dbf..37cdaf04b 100644 --- a/cpp/scenario/core/include/scenario/core/Link.h +++ b/scenario/src/core/include/scenario/core/Link.h @@ -161,7 +161,8 @@ class scenario::core::Link /** * Check if the link has active contacts. * - * @return True if the link has at least one contact, false otherwise. + * @return True if the link has at least one contact and contacts are + * enabled, false otherwise. */ virtual bool inContact() const = 0; diff --git a/cpp/scenario/core/include/scenario/core/Model.h b/scenario/src/core/include/scenario/core/Model.h similarity index 100% rename from cpp/scenario/core/include/scenario/core/Model.h rename to scenario/src/core/include/scenario/core/Model.h diff --git a/cpp/scenario/core/include/scenario/core/World.h b/scenario/src/core/include/scenario/core/World.h similarity index 84% rename from cpp/scenario/core/include/scenario/core/World.h rename to scenario/src/core/include/scenario/core/World.h index 3fb55e196..a02936a0c 100644 --- a/cpp/scenario/core/include/scenario/core/World.h +++ b/scenario/src/core/include/scenario/core/World.h @@ -72,6 +72,16 @@ class scenario::core::World * @return The model if it is part of the world, ``nullptr`` otherwise. */ virtual ModelPtr getModel(const std::string& modelName) const = 0; + + /** + * Get the models of the world. + * + * @param modelNames Optional vector of considered models. By default, + * ``World::modelNames`` is used. + * @return A vector of pointers to the model objects. + */ + virtual std::vector models( // + const std::vector& modelNames = {}) const = 0; }; #endif // SCENARIO_CORE_WORLD_H diff --git a/cpp/scenario/core/include/scenario/core/utils/Log.h b/scenario/src/core/include/scenario/core/utils/Log.h similarity index 100% rename from cpp/scenario/core/include/scenario/core/utils/Log.h rename to scenario/src/core/include/scenario/core/utils/Log.h diff --git a/cpp/scenario/core/include/scenario/core/utils/signals.h b/scenario/src/core/include/scenario/core/utils/signals.h similarity index 100% rename from cpp/scenario/core/include/scenario/core/utils/signals.h rename to scenario/src/core/include/scenario/core/utils/signals.h diff --git a/cpp/scenario/core/include/scenario/core/utils/utils.h b/scenario/src/core/include/scenario/core/utils/utils.h similarity index 100% rename from cpp/scenario/core/include/scenario/core/utils/utils.h rename to scenario/src/core/include/scenario/core/utils/utils.h diff --git a/cpp/scenario/core/src/signals.cpp b/scenario/src/core/src/signals.cpp similarity index 96% rename from cpp/scenario/core/src/signals.cpp rename to scenario/src/core/src/signals.cpp index 6dec1f209..c332c1927 100644 --- a/cpp/scenario/core/src/signals.cpp +++ b/scenario/src/core/src/signals.cpp @@ -73,8 +73,6 @@ SignalManager::setCallback(const SignalType type, { SignalCallback oldCallback = this->getCallback(type); - sDebug << "Setting callback for signal " << Impl::ToString(type) - << std::endl; std::signal(type, SignalManager::ExecuteCallback); pImpl->callbacks[type] = callback; diff --git a/cpp/scenario/core/src/utils.cpp b/scenario/src/core/src/utils.cpp similarity index 100% rename from cpp/scenario/core/src/utils.cpp rename to scenario/src/core/src/utils.cpp diff --git a/cpp/scenario/gazebo/CMakeLists.txt b/scenario/src/gazebo/CMakeLists.txt similarity index 97% rename from cpp/scenario/gazebo/CMakeLists.txt rename to scenario/src/gazebo/CMakeLists.txt index 06a84761a..7d3f54fe9 100644 --- a/cpp/scenario/gazebo/CMakeLists.txt +++ b/scenario/src/gazebo/CMakeLists.txt @@ -34,7 +34,6 @@ set(EXTRA_COMPONENTS_PUBLIC_HEADERS include/scenario/gazebo/components/BaseWorldAccelerationTarget.h include/scenario/gazebo/components/JointControlMode.h include/scenario/gazebo/components/JointController.h - include/scenario/gazebo/components/WorldVelocityCmd.h include/scenario/gazebo/components/JointPositionTarget.h include/scenario/gazebo/components/JointVelocityTarget.h include/scenario/gazebo/components/JointAccelerationTarget.h @@ -100,6 +99,7 @@ target_link_libraries(ScenarioGazebo PRIVATE ScenarioCore::CoreUtils ScenarioGazebo::ExtraComponents + ${ignition-physics.ignition-physics} ${ignition-fuel_tools.ignition-fuel_tools}) set_target_properties(ScenarioGazebo PROPERTIES @@ -127,7 +127,6 @@ target_link_libraries(GazeboSimulator ScenarioCore::CoreUtils ScenarioGazebo::ScenarioGazebo ScenarioGazebo::ExtraComponents - ScenarioGazeboPlugins::ECMSingleton ${ignition-fuel_tools.ignition-fuel_tools}) set_target_properties(GazeboSimulator PROPERTIES @@ -172,7 +171,7 @@ install_basic_package_files(ScenarioGazebo VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion EXPORT ScenarioGazeboExport - DEPENDENCIES ScenarioCore ScenarioGazeboPlugins ${ignition-gazebo} ${ignition-common} + DEPENDENCIES ScenarioCore ${ignition-gazebo} ${ignition-common} NAMESPACE ScenarioGazebo:: NO_CHECK_REQUIRED_COMPONENTS_MACRO INSTALL_DESTINATION diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/GazeboEntity.h b/scenario/src/gazebo/include/scenario/gazebo/GazeboEntity.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/GazeboEntity.h rename to scenario/src/gazebo/include/scenario/gazebo/GazeboEntity.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/GazeboSimulator.h b/scenario/src/gazebo/include/scenario/gazebo/GazeboSimulator.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/GazeboSimulator.h rename to scenario/src/gazebo/include/scenario/gazebo/GazeboSimulator.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/Joint.h b/scenario/src/gazebo/include/scenario/gazebo/Joint.h similarity index 91% rename from cpp/scenario/gazebo/include/scenario/gazebo/Joint.h rename to scenario/src/gazebo/include/scenario/gazebo/Joint.h index 187742b4b..ad1c98df6 100644 --- a/cpp/scenario/gazebo/include/scenario/gazebo/Joint.h +++ b/scenario/src/gazebo/include/scenario/gazebo/Joint.h @@ -67,6 +67,18 @@ class scenario::gazebo::Joint final // Gazebo Joint // ============ + /** + * Insert a Ignition Gazebo plugin to the joint. + * + * @param libName The library name of the plugin. + * @param className The class name (or alias) of the plugin. + * @param context Optional XML plugin context. + * @return True for success, false otherwise. + */ + bool insertJointPlugin(const std::string& libName, + const std::string& className, + const std::string& context = {}); + /** * Reset the position of a joint DOF. * @@ -193,6 +205,11 @@ class scenario::gazebo::Joint final core::Limit positionLimit(const size_t dof = 0) const override; + core::Limit velocityLimit(const size_t dof = 0) const override; + + bool setVelocityLimit(const double maxVelocity, + const size_t dof = 0) override; + double maxGeneralizedForce(const size_t dof = 0) const override; bool setMaxGeneralizedForce(const double maxForce, @@ -232,6 +249,10 @@ class scenario::gazebo::Joint final core::JointLimit jointPositionLimit() const override; + core::JointLimit jointVelocityLimit() const override; + + bool setJointVelocityLimit(const std::vector& maxVelocity) override; + std::vector jointMaxGeneralizedForce() const override; bool setJointMaxGeneralizedForce( // diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/Link.h b/scenario/src/gazebo/include/scenario/gazebo/Link.h similarity index 91% rename from cpp/scenario/gazebo/include/scenario/gazebo/Link.h rename to scenario/src/gazebo/include/scenario/gazebo/Link.h index a1f014985..639a11c62 100644 --- a/cpp/scenario/gazebo/include/scenario/gazebo/Link.h +++ b/scenario/src/gazebo/include/scenario/gazebo/Link.h @@ -65,6 +65,22 @@ class scenario::gazebo::Link final bool createECMResources() override; + // =========== + // Gazebo Link + // =========== + + /** + * Insert a Ignition Gazebo plugin to the link. + * + * @param libName The library name of the plugin. + * @param className The class name (or alias) of the plugin. + * @param context Optional XML plugin context. + * @return True for success, false otherwise. + */ + bool insertLinkPlugin(const std::string& libName, + const std::string& className, + const std::string& context = {}); + // ========= // Link Core // ========= diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/Log.h b/scenario/src/gazebo/include/scenario/gazebo/Log.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/Log.h rename to scenario/src/gazebo/include/scenario/gazebo/Log.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/Model.h b/scenario/src/gazebo/include/scenario/gazebo/Model.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/Model.h rename to scenario/src/gazebo/include/scenario/gazebo/Model.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/World.h b/scenario/src/gazebo/include/scenario/gazebo/World.h similarity index 98% rename from cpp/scenario/gazebo/include/scenario/gazebo/World.h rename to scenario/src/gazebo/include/scenario/gazebo/World.h index 7deefba5a..5bec422db 100644 --- a/cpp/scenario/gazebo/include/scenario/gazebo/World.h +++ b/scenario/src/gazebo/include/scenario/gazebo/World.h @@ -202,6 +202,9 @@ class scenario::gazebo::World final scenario::core::ModelPtr getModel(const std::string& modelName) const override; + std::vector models( // + const std::vector& modelNames = {}) const override; + private: class Impl; std::unique_ptr pImpl; diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/BasePoseTarget.h b/scenario/src/gazebo/include/scenario/gazebo/components/BasePoseTarget.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/BasePoseTarget.h rename to scenario/src/gazebo/include/scenario/gazebo/components/BasePoseTarget.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/BaseWorldAccelerationTarget.h b/scenario/src/gazebo/include/scenario/gazebo/components/BaseWorldAccelerationTarget.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/BaseWorldAccelerationTarget.h rename to scenario/src/gazebo/include/scenario/gazebo/components/BaseWorldAccelerationTarget.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/BaseWorldVelocityTarget.h b/scenario/src/gazebo/include/scenario/gazebo/components/BaseWorldVelocityTarget.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/BaseWorldVelocityTarget.h rename to scenario/src/gazebo/include/scenario/gazebo/components/BaseWorldVelocityTarget.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/ExternalWorldWrenchCmdWithDuration.h b/scenario/src/gazebo/include/scenario/gazebo/components/ExternalWorldWrenchCmdWithDuration.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/ExternalWorldWrenchCmdWithDuration.h rename to scenario/src/gazebo/include/scenario/gazebo/components/ExternalWorldWrenchCmdWithDuration.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/HistoryOfAppliedJointForces.h b/scenario/src/gazebo/include/scenario/gazebo/components/HistoryOfAppliedJointForces.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/HistoryOfAppliedJointForces.h rename to scenario/src/gazebo/include/scenario/gazebo/components/HistoryOfAppliedJointForces.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointAcceleration.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointAcceleration.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointAcceleration.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointAcceleration.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointAccelerationTarget.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointAccelerationTarget.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointAccelerationTarget.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointAccelerationTarget.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointControlMode.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointControlMode.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointControlMode.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointControlMode.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointController.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointController.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointController.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointController.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointControllerPeriod.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointControllerPeriod.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointControllerPeriod.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointControllerPeriod.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointPID.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointPID.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointPID.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointPID.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointPositionTarget.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointPositionTarget.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointPositionTarget.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointPositionTarget.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/JointVelocityTarget.h b/scenario/src/gazebo/include/scenario/gazebo/components/JointVelocityTarget.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/JointVelocityTarget.h rename to scenario/src/gazebo/include/scenario/gazebo/components/JointVelocityTarget.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/SimulatedTime.h b/scenario/src/gazebo/include/scenario/gazebo/components/SimulatedTime.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/SimulatedTime.h rename to scenario/src/gazebo/include/scenario/gazebo/components/SimulatedTime.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/components/Timestamp.h b/scenario/src/gazebo/include/scenario/gazebo/components/Timestamp.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/components/Timestamp.h rename to scenario/src/gazebo/include/scenario/gazebo/components/Timestamp.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/exceptions.h b/scenario/src/gazebo/include/scenario/gazebo/exceptions.h similarity index 100% rename from cpp/scenario/gazebo/include/scenario/gazebo/exceptions.h rename to scenario/src/gazebo/include/scenario/gazebo/exceptions.h diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/helpers.h b/scenario/src/gazebo/include/scenario/gazebo/helpers.h similarity index 91% rename from cpp/scenario/gazebo/include/scenario/gazebo/helpers.h rename to scenario/src/gazebo/include/scenario/gazebo/helpers.h index 138d63def..267a38c8f 100644 --- a/cpp/scenario/gazebo/include/scenario/gazebo/helpers.h +++ b/scenario/src/gazebo/include/scenario/gazebo/helpers.h @@ -149,9 +149,7 @@ namespace scenario::gazebo::utils { sdf::World renameSDFWorld(const sdf::World& world, const std::string& newWorldName); - bool renameSDFModel(sdf::Root& sdfRoot, - const std::string& newModelName, - const size_t modelIndex = 0); + bool renameSDFModel(sdf::Root& sdfRoot, const std::string& newModelName); bool updateSDFPhysics(sdf::Root& sdfRoot, const double maxStepSize, @@ -165,17 +163,17 @@ namespace scenario::gazebo::utils { sdf::JointType toSdf(const scenario::core::JointType type); scenario::core::JointType fromSdf(const sdf::JointType sdfType); - std::pair - fromModelToBaseVelocity(const ignition::math::Vector3d& linModelVelocity, - const ignition::math::Vector3d& angModelVelocity, - const ignition::math::Pose3d& M_H_B, - const ignition::math::Quaterniond& W_R_B); + ignition::math::Vector3d fromModelToBaseLinearVelocity( + const ignition::math::Vector3d& linModelVelocity, + const ignition::math::Vector3d& angModelVelocity, + const ignition::math::Pose3d& M_H_B, + const ignition::math::Quaterniond& W_R_B); - std::pair - fromBaseToModelVelocity(const ignition::math::Vector3d& linBaseVelocity, - const ignition::math::Vector3d& angBaseVelocity, - const ignition::math::Pose3d& M_H_B, - const ignition::math::Quaterniond& W_R_B); + ignition::math::Vector3d fromBaseToModelLinearVelocity( + const ignition::math::Vector3d& linBaseVelocity, + const ignition::math::Vector3d& angBaseVelocity, + const ignition::math::Pose3d& M_H_B, + const ignition::math::Quaterniond& W_R_B); std::shared_ptr getParentWorld(const GazeboEntity& gazeboEntity); @@ -320,17 +318,17 @@ namespace scenario::gazebo::utils { curSimTime) {} - inline ignition::msgs::Vector3d force() const + const ignition::msgs::Vector3d& force() const { return m_wrench.force(); } - inline ignition::msgs::Vector3d torque() const + const ignition::msgs::Vector3d& torque() const { return m_wrench.torque(); } - inline std::chrono::steady_clock::duration expiration() + const std::chrono::steady_clock::duration& expiration() { return m_expiration; } @@ -351,6 +349,8 @@ namespace scenario::gazebo::utils { public: LinkWrenchCmd() = default; + bool empty() const { return m_wrenches.empty(); } + inline void addWorldWrench(const WrenchWithDuration& wrench) { m_wrenches.push_back(wrench); @@ -377,14 +377,19 @@ namespace scenario::gazebo::utils { return totalWrench; } - inline void cleanExpired(const std::chrono::steady_clock::duration& now) + void cleanExpired(const std::chrono::steady_clock::duration& now) { - auto end = std::remove_if( - m_wrenches.begin(), - m_wrenches.end(), - [&](const WrenchWithDuration& w) { return w.expired(now); }); - - m_wrenches.erase(end, m_wrenches.end()); + if (m_wrenches.empty()) + return; + + // Remove the expired wrenches + m_wrenches.erase( // + std::remove_if(m_wrenches.begin(), + m_wrenches.end(), + [&](const WrenchWithDuration& w) { + return w.expired(now); + }), + m_wrenches.end()); } private: diff --git a/cpp/scenario/gazebo/include/scenario/gazebo/utils.h b/scenario/src/gazebo/include/scenario/gazebo/utils.h similarity index 85% rename from cpp/scenario/gazebo/include/scenario/gazebo/utils.h rename to scenario/src/gazebo/include/scenario/gazebo/utils.h index 008e1c5d4..5d8cb0833 100644 --- a/cpp/scenario/gazebo/include/scenario/gazebo/utils.h +++ b/scenario/src/gazebo/include/scenario/gazebo/utils.h @@ -27,6 +27,7 @@ #ifndef SCENARIO_GAZEBO_UTILS_H #define SCENARIO_GAZEBO_UTILS_H +#include "scenario/gazebo/GazeboEntity.h" #include #include #include @@ -112,15 +113,15 @@ namespace scenario::gazebo::utils { /** * Get the name of a model from a SDF file. * + * @note sdformat supports only one model per SDF file. + * * @param fileName An SDF file. It could be either an absolute path * to the file or the file name if the parent folder is part * of the ``IGN_GAZEBO_RESOURCE_PATH`` environment variable. - * @param modelIndex The index of the model in the SDF file. By - * default it finds the first model. - * @return The name of the model. + * @return The name of the model if the file was found and is valid, + * an empty string otherwise. */ - std::string getModelNameFromSdf(const std::string& fileName, - const size_t modelIndex = 0); + std::string getModelNameFromSdf(const std::string& fileName); /** * Get the name of a world from a SDF file. @@ -130,7 +131,8 @@ namespace scenario::gazebo::utils { * of the ``IGN_GAZEBO_RESOURCE_PATH`` environment variable. * @param worldIndex The index of the world in the SDF file. By * default it finds the first world. - * @return The name of the world. + * @return The name of the world if the file was found and is valid, + * an empty string otherwise. */ std::string getWorldNameFromSdf(const std::string& fileName, const size_t worldIndex = 0); @@ -238,6 +240,25 @@ namespace scenario::gazebo::utils { std::vector denormalize(const std::vector& input, const std::vector& low, const std::vector& high); + + /** + * Insert a plugin to any Gazebo entity. + * + * @note This function will not return true if the plugin is successful. + * This function just triggers an event that notifies the server to load a + * plugin, and it does not receive any return value that could be used to + * assess the outcome. + * + * @param gazeboEntity The Gazebo entity (world, model, joint, ...). + * @param libName The name of the plugin library. + * @param className The name of the class implementing the plugin. + * @param context The optional plugin SDF context. + * @return True if the entity is valid, false otherwise. + */ + bool insertPluginToGazeboEntity(const GazeboEntity& gazeboEntity, + const std::string& libName, + const std::string& className, + const std::string& context = ""); } // namespace scenario::gazebo::utils #endif // SCENARIO_GAZEBO_UTILS_H diff --git a/cpp/scenario/gazebo/src/GazeboSimulator.cpp b/scenario/src/gazebo/src/GazeboSimulator.cpp similarity index 60% rename from cpp/scenario/gazebo/src/GazeboSimulator.cpp rename to scenario/src/gazebo/src/GazeboSimulator.cpp index 05049143f..790c2e149 100644 --- a/cpp/scenario/gazebo/src/GazeboSimulator.cpp +++ b/scenario/src/gazebo/src/GazeboSimulator.cpp @@ -33,12 +33,15 @@ #include "scenario/gazebo/components/Timestamp.h" #include "scenario/gazebo/helpers.h" #include "scenario/gazebo/utils.h" -#include "scenario/plugins/gazebo/ECMSingleton.h" #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -49,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +61,13 @@ using namespace scenario::gazebo; namespace scenario::gazebo::detail { + class ECMProvider; struct PhysicsData; + struct SimulationResources + { + ignition::gazebo::EventManager* eventMgr = nullptr; + ignition::gazebo::EntityComponentManager* ecm = nullptr; + }; } // namespace scenario::gazebo::detail struct detail::PhysicsData @@ -66,26 +76,26 @@ struct detail::PhysicsData double maxStepSize = -1; double realTimeUpdateRate = -1; - bool operator==(const PhysicsData& other) - { - return doubleEq(this->rtf, other.rtf) - && doubleEq(this->maxStepSize, other.maxStepSize) - && doubleEq(this->realTimeUpdateRate, other.realTimeUpdateRate); - } - - static bool doubleEq(const double first, const double second) - { - return std::abs(first - second) - < 10 * std::numeric_limits::epsilon(); - } + bool valid() const { return this->rtf > 0 && this->maxStepSize > 0; } +}; - friend std::ostream& operator<<(std::ostream& out, const PhysicsData& data) - { - out << "max_step_size=" << data.maxStepSize << std::endl; - out << "real_time_factor=" << data.rtf << std::endl; - out << "real_time_update_rate=" << data.realTimeUpdateRate; - return out; - } +class scenario::gazebo::detail::ECMProvider final + : public ignition::gazebo::System + , public ignition::gazebo::ISystemConfigure +{ +public: + ECMProvider() + : ignition::gazebo::System() + {} + + void Configure(const ignition::gazebo::Entity& entity, + const std::shared_ptr& /*sdf*/, + ignition::gazebo::EntityComponentManager& ecm, + ignition::gazebo::EventManager& eventMgr); + + std::string worldName; + ignition::gazebo::EventManager* eventMgr = nullptr; + ignition::gazebo::EntityComponentManager* ecm = nullptr; }; // ============== @@ -94,7 +104,7 @@ struct detail::PhysicsData class GazeboSimulator::Impl { -public: // attributes +public: sdf::ElementPtr sdfElement = nullptr; struct @@ -107,17 +117,17 @@ class GazeboSimulator::Impl using WorldName = std::string; using GazeboWorldPtr = std::shared_ptr; + std::unordered_map worlds; + std::unordered_map resources; -public: // methods bool insertSDFWorld(const sdf::World& world); std::shared_ptr getServer(); static std::shared_ptr - CreateGazeboWorld(const std::string& worldName); + CreateGazeboWorld(const std::string& worldName, + const detail::SimulationResources& resources); - static detail::PhysicsData getPhysicsData(const sdf::Root& root, - const size_t worldIndex); bool sceneBroadcasterActive(const std::string& worldName); }; @@ -153,12 +163,34 @@ GazeboSimulator::~GazeboSimulator() double GazeboSimulator::stepSize() const { - return pImpl->gazebo.physics.maxStepSize; + if (!this->initialized()) { + return pImpl->gazebo.physics.maxStepSize; + } + + // Get the first world + const auto world = this->getWorld(this->worldNames().front()); + + // Get the active physics parameters + const auto& physics = utils::getExistingComponentData< // + ignition::gazebo::components::Physics>(world->ecm(), world->entity()); + + return physics.MaxStepSize(); } double GazeboSimulator::realTimeFactor() const { - return pImpl->gazebo.physics.rtf; + if (!this->initialized()) { + return pImpl->gazebo.physics.rtf; + } + + // Get the first world + const auto world = this->getWorld(this->worldNames().front()); + + // Get the active physics parameters + const auto& physics = utils::getExistingComponentData< // + ignition::gazebo::components::Physics>(world->ecm(), world->entity()); + + return physics.RealTimeFactor(); } size_t GazeboSimulator::stepsPerRun() const @@ -233,12 +265,41 @@ bool GazeboSimulator::run(const bool paused) iterations = 1; } + // Recent versions of Ignition Gazebo optimize the streaming of pose updates + // in order to reduce the bandwidth between server and client. + // However, it does not take into account paused steps. + // Here below we force all the Pose components to be streamed by manually + // setting them as changed. + if (paused) { + + // Process all worlds + for (const auto& worldName : this->worldNames()) { + + // Get the ECM + assert(this->pImpl->resources.find(worldName) + != this->pImpl->resources.end()); + auto* ecm = this->pImpl->resources.at(worldName).ecm; + + // Mark all all entities with Pose component as Changed + ecm->Each( + [&](const ignition::gazebo::Entity& entity, + ignition::gazebo::components::Pose*) -> bool { + ecm->SetChanged( + entity, + ignition::gazebo::components::Pose::typeId, + ignition::gazebo::ComponentState::OneTimeChange); + return true; + }); + } + } + + // Paused simulation run if (paused && !server->RunOnce(/*paused=*/true)) { sError << "The server couldn't execute the paused step" << std::endl; return false; } - // Run the simulation + // Unpaused simulation run if (!paused && !server->Run(/*blocking=*/deterministic, /*iterations=*/iterations, @@ -281,20 +342,29 @@ bool GazeboSimulator::gui(const int verbosity) "ignition-gazebo-scene-broadcaster-system", "ignition::gazebo::systems::SceneBroadcaster")) { sError << "Failed to load SceneBroadcaster plugin" << std::endl; + return false; } } // Allow specifying a GUI verbosity different than the server verbosity - int appliedVerbosity = verbosity; + int guiVerbosity = verbosity; - if (appliedVerbosity < 0) { + if (guiVerbosity < 0) { // Get the verbosity level - appliedVerbosity = ignition::common::Console::Verbosity(); + guiVerbosity = ignition::common::Console::Verbosity(); } +#if defined(WIN32) || defined(_WIN32) + const std::string redirect = ""; +#else + // Suppress GUI stderr. + // Recent versions of the GUI segfault printing an annoying stacktrace. + const std::string redirect = guiVerbosity >= 4 ? "" : " 2>/dev/null"; +#endif + // Spawn a new process with the GUI pImpl->gazebo.gui = std::make_unique( - "ign gazebo -g -v " + std::to_string(appliedVerbosity)); + "ign gazebo -g -v " + std::to_string(guiVerbosity) + redirect); bool guiServiceExists = false; ignition::transport::Node node; @@ -311,7 +381,7 @@ bool GazeboSimulator::gui(const int verbosity) } } - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } while (!guiServiceExists); sDebug << "GUI up and running" << std::endl; @@ -334,21 +404,6 @@ bool GazeboSimulator::close() this->pause(); } - // Remove the resources of the handled worlds from the singleton - if (this->initialized()) { - try { - for (const auto& worldName : this->worldNames()) { - plugins::gazebo::ECMSingleton::Instance().clean(worldName); - } - } - // This happens while tearing down everything. The ECMProvider plugin - // sometimes is destroyed before the simulator. - catch (std::runtime_error) { - sWarning << "Failed to clean the singleton from the worlds" - << std::endl; - } - } - // Delete the simulator pImpl->gazebo.server.reset(); @@ -494,11 +549,13 @@ std::vector GazeboSimulator::worldNames() const return {}; } - if (!scenario::plugins::gazebo::ECMSingleton::Instance().valid()) { - throw std::runtime_error("The ECM singleton is not valid"); + std::vector worldNames; + + for (const auto& [name, _] : this->pImpl->worlds) { + worldNames.push_back(name); } - return scenario::plugins::gazebo::ECMSingleton::Instance().worldNames(); + return worldNames; } std::shared_ptr @@ -544,6 +601,29 @@ GazeboSimulator::getWorld(const std::string& worldName) const // Implementation // ============== +void detail::ECMProvider::Configure( + const ignition::gazebo::Entity& entity, + const std::shared_ptr&, + ignition::gazebo::EntityComponentManager& ecm, + ignition::gazebo::EventManager& eventMgr) +{ + if (!ecm.EntityHasComponentType( + entity, ignition::gazebo::components::World::typeId)) { + sError << "The ECMProvider system was not inserted " + << "in a world element" << std::endl; + return; + } + + this->worldName = utils::getExistingComponentData< // + ignition::gazebo::components::Name>(&ecm, entity); + + this->ecm = &ecm; + this->eventMgr = &eventMgr; + + sDebug << "World '" << this->worldName + << "' successfully processed by ECMProvider" << std::endl; +} + bool GazeboSimulator::Impl::insertSDFWorld(const sdf::World& world) { if (!sdfElement) { @@ -573,156 +653,179 @@ bool GazeboSimulator::Impl::insertSDFWorld(const sdf::World& world) std::shared_ptr GazeboSimulator::Impl::getServer() { // Lazy initialization of the server - if (!gazebo.server) { - - if (gazebo.numOfIterations == 0) { - sError << "Non-deterministic mode (iterations=0) is not " - << "currently supported" << std::endl; - return nullptr; - } - - sdf::Root root; + if (gazebo.server) { + return gazebo.server; + } - if (!sdfElement) { - sMessage << "Using default empty world" << std::endl; - auto errors = root.LoadSdfString(utils::getEmptyWorld()); - assert(errors.empty()); // TODO - } - else { - auto errors = root.LoadSdfString(sdfElement->ToString("")); - assert(errors.empty()); // TODO - } + // ================= + // Create the server + // ================= - if (root.WorldCount() == 0) { - sError << "Failed to find a world in the SDF root" << std::endl; - return nullptr; - } - - // Get the plugin info of the ECM provider - auto getECMPluginInfo = [](const std::string& worldName) { - ignition::gazebo::ServerConfig::PluginInfo pluginInfo; - pluginInfo.SetFilename("ECMProvider"); - pluginInfo.SetName("scenario::plugins::gazebo::ECMProvider"); - pluginInfo.SetEntityType("world"); - pluginInfo.SetEntityName(worldName); - - return pluginInfo; - }; - - // Check if there are sdf parsing errors - assert(utils::sdfStringValid(root.Element()->Clone()->ToString(""))); - - // There is no way yet to set the physics step size if not passing - // through the physics element of the SDF. We update here the SDF - // overriding the default profile. - // NOTE: this could be avoided if gazebo::Server would expose the - // SimulationRunner::SetStepSize method. - for (size_t worldIdx = 0; worldIdx < root.WorldCount(); ++worldIdx) { - if (!utils::updateSDFPhysics(root, - gazebo.physics.maxStepSize, - gazebo.physics.rtf, - /*realTimeUpdateRate=*/-1, - worldIdx)) { - sError << "Failed to set physics profile" << std::endl; - return nullptr; - } + if (gazebo.numOfIterations == 0) { + sError << "Non-deterministic mode (iterations=0) is not " + << "currently supported" << std::endl; + return nullptr; + } - assert(Impl::getPhysicsData(root, worldIdx) == gazebo.physics); - } + sdf::Root root; - sDebug << "Physics profile:" << std::endl - << this->gazebo.physics << std::endl; + if (!sdfElement) { + sMessage << "Using default empty world" << std::endl; + auto errors = root.LoadSdfString(utils::getEmptyWorld()); + assert(errors.empty()); // TODO + } + else { + auto errors = root.LoadSdfString(sdfElement->ToString("")); + assert(errors.empty()); // TODO + } - if (utils::verboseFromEnvironment()) { - sDebug << "Loading the following SDF file in the gazebo server:" - << std::endl; - std::cout << root.Element()->ToString("") << std::endl; - } + if (root.WorldCount() == 0) { + sError << "Failed to find a world in the SDF root" << std::endl; + return nullptr; + } - ignition::gazebo::ServerConfig config; + // Check if there are sdf parsing errors + assert(utils::sdfStringValid(root.Element()->Clone()->ToString(""))); - config.SetSeed(0); - config.SetUseLevels(false); - config.SetSdfString(root.Element()->ToString("")); + if (utils::verboseFromEnvironment()) { + sDebug << "Loading the following SDF file in the gazebo server:" + << std::endl; + std::cout << root.Element()->ToString("") << std::endl; + } + + // Set the following environment variable to disable loading the default + // server plugins, which include upstream's Physics that is not compatible. + // https://github.com/ignitionrobotics/ign-gazebo/pull/281 + // TODO: this will not likely work in Windows. + std::string value; + if (!ignition::common::env( + ignition::gazebo::kServerConfigPathEnv, value, true) + && !ignition::common::setenv(ignition::gazebo::kServerConfigPathEnv, + "")) { + sError << "Failed to set " << ignition::gazebo::kServerConfigPathEnv + << std::endl; + return nullptr; + } - // Add the ECMProvider plugin for all worlds - for (size_t worldIdx = 0; worldIdx < root.WorldCount(); ++worldIdx) { - auto worldName = root.WorldByIndex(worldIdx)->Name(); - config.AddPlugin(getECMPluginInfo(worldName)); - } + ignition::gazebo::ServerConfig config; + config.SetSeed(0); + config.SetUseLevels(false); + config.SetSdfString(root.Element()->ToString("")); - // Create the server - auto server = std::make_shared(config); - assert(server); + // Create the server. + // The worlds are initialized with the physics parameters + // (rtf and physics step) defined in the SDF. + // They get overridden below. + auto server = std::make_shared(config); + assert(server); - sDebug << "Starting the gazebo server" << std::endl; + // Add a Configure-only system to get the ECM pointer + for (size_t worldIdx = 0; worldIdx < root.WorldCount(); ++worldIdx) { - if (!server->RunOnce(/*paused=*/true)) { - sError << "Failed to initialize the first gazebo server run" + auto provider = std::make_shared(); + if (const auto ok = server->AddSystem(provider, worldIdx); !ok) { + sError << "Failed to insert ECMProvider to world " << worldIdx << std::endl; return nullptr; } - for (size_t worldIdx = 0; worldIdx < root.WorldCount(); ++worldIdx) { - // Get the world name - const auto& worldName = root.WorldByIndex(worldIdx)->Name(); - - sDebug << "Creating and caching World '" << worldName << "'" - << std::endl; + // Get the ECM and EventManager pointers + detail::SimulationResources resources; + resources.ecm = provider->ecm; + resources.eventMgr = provider->eventMgr; + this->resources[provider->worldName] = resources; + } - // Create the world object. - // Note: performing this operation is important because the World - // objects are created and cached. During the first - // initialization, the World objects create important - // componentes like Timestamp and SimulatedTime. - const auto& world = Impl::CreateGazeboWorld(worldName); + std::this_thread::sleep_for(std::chrono::seconds(3)); - if (!(world && world->valid())) { - sError << "Failed to create world " << worldName << std::endl; - return nullptr; - } + sDebug << "Starting the gazebo server" << std::endl; - // Cache the world object - assert(this->worlds.find(worldName) == this->worlds.end()); - this->worlds[worldName] = world; - } + // TODO: is this redundant now? + if (!server->RunOnce(/*paused=*/true)) { + sError << "Failed to initialize the first gazebo server run" + << std::endl; + return nullptr; + } - // Store the server - gazebo.server = server; + if (!gazebo.physics.valid()) { + sError << "The physics parameters are not valid" << std::endl; + return nullptr; } - return gazebo.server; -} + // Set the Physics parameters. + // Note: all worlds must share the same parameters. + for (const auto& [worldName, resources] : this->resources) { -std::shared_ptr -GazeboSimulator::Impl::CreateGazeboWorld(const std::string& worldName) -{ - auto& ecmSingleton = scenario::plugins::gazebo::ECMSingleton::Instance(); + // Get the world entity + const auto worldEntity = resources.ecm->EntityByComponents( + ignition::gazebo::components::World(), + ignition::gazebo::components::Name(worldName)); - if (!ecmSingleton.hasWorld(worldName)) { - sError << "Failed to find world in the singleton" << std::endl; - return nullptr; + // Create a new PhysicsCmd component + auto& physics = + utils::getComponentData( + resources.ecm, worldEntity); + + // Store the physics parameters. + // They are processed the next simulator step. + physics.set_max_step_size(gazebo.physics.maxStepSize); + physics.set_real_time_factor(gazebo.physics.rtf); + physics.set_real_time_update_rate(gazebo.physics.realTimeUpdateRate); } - if (!ecmSingleton.valid(worldName)) { - sError << "Resources of world " << worldName << " not valid" + // Step the server to process the physics parameters. + // This call executes SimulationRunner::SetStepSize, updating the + // rate at which all systems are called. + // Note: it processes only the parameters of the first world. + if (!server->RunOnce(/*paused=*/true)) { + sError << "Failed to step the server to configure the physics" << std::endl; return nullptr; } - // Get the resources needed by the world - auto* ecm = ecmSingleton.getECM(worldName); - auto* eventManager = ecmSingleton.getEventManager(worldName); + for (size_t worldIdx = 0; worldIdx < root.WorldCount(); ++worldIdx) { + // Get the world name + const auto& worldName = root.WorldByIndex(worldIdx)->Name(); + + sDebug << "Creating and caching World '" << worldName << "'" + << std::endl; + + // Create the world object. + // Note: performing this operation is important because the + // World objects are created and cached. During the first + // initialization, the World objects create important + // componentes like Timestamp and SimulatedTime. + const auto& world = + Impl::CreateGazeboWorld(worldName, this->resources[worldName]); + + if (!(world && world->valid())) { + sError << "Failed to create world " << worldName << std::endl; + return nullptr; + } + // Cache the world object + assert(this->worlds.find(worldName) == this->worlds.end()); + this->worlds[worldName] = world; + } + + // Store and return the server + gazebo.server = server; + return gazebo.server; +} + +std::shared_ptr GazeboSimulator::Impl::CreateGazeboWorld( + const std::string& worldName, + const detail::SimulationResources& resources) +{ // Get the world entity - const auto worldEntity = - ecm->EntityByComponents(ignition::gazebo::components::World(), - ignition::gazebo::components::Name(worldName)); + const auto worldEntity = resources.ecm->EntityByComponents( + ignition::gazebo::components::World(), + ignition::gazebo::components::Name(worldName)); // Create the world object auto world = std::make_shared(); - if (!world->initialize(worldEntity, ecm, eventManager)) { + if (!world->initialize(worldEntity, resources.ecm, resources.eventMgr)) { sError << "Failed to initialize the world" << std::endl; return nullptr; } @@ -740,21 +843,6 @@ GazeboSimulator::Impl::CreateGazeboWorld(const std::string& worldName) return world; } -detail::PhysicsData -GazeboSimulator::Impl::getPhysicsData(const sdf::Root& root, - const size_t worldIndex) -{ - const sdf::World* world = root.WorldByIndex(worldIndex); - assert(world->PhysicsCount() == 1); - - detail::PhysicsData physics; - - physics.rtf = world->PhysicsByIndex(0)->RealTimeFactor(); - physics.maxStepSize = world->PhysicsByIndex(0)->MaxStepSize(); - - return physics; -} - bool GazeboSimulator::Impl::sceneBroadcasterActive(const std::string& worldName) { ignition::transport::Node node; diff --git a/cpp/scenario/gazebo/src/Joint.cpp b/scenario/src/gazebo/src/Joint.cpp similarity index 90% rename from cpp/scenario/gazebo/src/Joint.cpp rename to scenario/src/gazebo/src/Joint.cpp index 0d16e6d81..531640d63 100644 --- a/cpp/scenario/gazebo/src/Joint.cpp +++ b/scenario/src/gazebo/src/Joint.cpp @@ -40,6 +40,7 @@ #include "scenario/gazebo/components/Timestamp.h" #include "scenario/gazebo/exceptions.h" #include "scenario/gazebo/helpers.h" +#include "scenario/gazebo/utils.h" #include #include @@ -129,6 +130,14 @@ bool Joint::createECMResources() return true; } +bool Joint::insertJointPlugin(const std::string& libName, + const std::string& className, + const std::string& context) +{ + return utils::insertPluginToGazeboEntity( + *this, libName, className, context); +} + bool Joint::resetPosition(const double position, size_t dof) { if (dof >= this->dofs()) { @@ -617,6 +626,29 @@ scenario::core::Limit Joint::positionLimit(const size_t dof) const return core::Limit(jointLimit.min[dof], jointLimit.max[dof]); } +scenario::core::Limit Joint::velocityLimit(const size_t dof) const +{ + if (dof >= this->dofs()) { + throw exceptions::DOFMismatch(this->dofs(), dof, this->name()); + } + + const auto& jointLimit = this->jointVelocityLimit(); + assert(dof < jointLimit.min.size()); + assert(dof < jointLimit.max.size()); + + return core::Limit(jointLimit.min[dof], jointLimit.max[dof]); +} + +bool Joint::setVelocityLimit(const double maxVelocity, const size_t dof) +{ + if (dof >= this->dofs()) { + throw exceptions::DOFMismatch(this->dofs(), dof, this->name()); + } + auto velocityLimit = this->jointVelocityLimit(); + velocityLimit.max[dof] = maxVelocity; + return this->setJointVelocityLimit(velocityLimit.max); +} + double Joint::maxGeneralizedForce(const size_t dof) const { if (dof >= this->dofs()) { @@ -882,6 +914,83 @@ scenario::core::JointLimit Joint::jointPositionLimit() const return jointLimit; } +scenario::core::JointLimit Joint::jointVelocityLimit() const +{ + core::JointLimit jointLimit(this->dofs()); + + switch (this->type()) { + case core::JointType::Revolute: + case core::JointType::Prismatic: { + sdf::JointAxis& axis = utils::getExistingComponentData< // + ignition::gazebo::components::JointAxis>(m_ecm, m_entity); + jointLimit.min[0] = -axis.MaxVelocity(); + jointLimit.max[0] = axis.MaxVelocity(); + break; + } + case core::JointType::Fixed: + sWarning << "Fixed joints do not have DOFs, limits are not defined" + << std::endl; + break; + case core::JointType::Invalid: + case core::JointType::Ball: + sWarning << "Type of Joint '" << this->name() << "' has no limits" + << std::endl; + break; + } + + return jointLimit; +} + +bool Joint::setJointVelocityLimit(const std::vector& maxVelocity) +{ + if (!utils::parentModelJustCreated(*this)) { + sError << "The model has been already processed and its " + << "parameters cannot be modified" << std::endl; + return false; + } + + if (maxVelocity.size() != this->dofs()) { + sError << "Wrong number of elements (joint_dofs=" << this->dofs() << ")" + << std::endl; + return false; + } + + switch (this->type()) { + case core::JointType::Revolute: + case core::JointType::Prismatic: { + sdf::JointAxis& axis = utils::getExistingComponentData< // + ignition::gazebo::components::JointAxis>(m_ecm, m_entity); + axis.SetMaxVelocity(maxVelocity[0]); + return true; + } + case core::JointType::Ball: { + const auto maxVelocity0 = maxVelocity[0]; + + for (const auto max : maxVelocity) { + if (max != maxVelocity0) { + sWarning << "Setting different velocity limits for each " + << "DOF is not supported. " + << "Using the limit of the first DOF." + << std::endl; + break; + } + } + + sdf::JointAxis& axis = utils::getExistingComponentData< // + ignition::gazebo::components::JointAxis>(m_ecm, m_entity); + axis.SetMaxVelocity(maxVelocity0); + return true; + } + case core::JointType::Fixed: + case core::JointType::Invalid: + sWarning << "Fixed and Invalid joints have no friction defined." + << std::endl; + return false; + } + + return false; +} + std::vector Joint::jointMaxGeneralizedForce() const { std::vector maxGeneralizedForce; diff --git a/cpp/scenario/gazebo/src/Link.cpp b/scenario/src/gazebo/src/Link.cpp similarity index 93% rename from cpp/scenario/gazebo/src/Link.cpp rename to scenario/src/gazebo/src/Link.cpp index 16ae4e46d..0d0cb9f63 100644 --- a/cpp/scenario/gazebo/src/Link.cpp +++ b/scenario/src/gazebo/src/Link.cpp @@ -32,6 +32,7 @@ #include "scenario/gazebo/components/SimulatedTime.h" #include "scenario/gazebo/exceptions.h" #include "scenario/gazebo/helpers.h" +#include "scenario/gazebo/utils.h" #include #include @@ -62,16 +63,10 @@ class Link::Impl public: ignition::gazebo::Link link; - static bool IsCanonical(const Link& link) - { - return link.ecm()->EntityHasComponentType( - link.entity(), ignition::gazebo::components::CanonicalLink::typeId); - } - static ignition::math::Pose3d GetWorldPose(const Link& link, const Link::Impl& impl) { - if (!Impl::IsCanonical(link)) { + if (!impl.link.IsCanonical(*link.ecm())) { const auto& linkPoseOptional = impl.link.WorldPose(*link.ecm()); if (!linkPoseOptional.has_value()) { @@ -166,13 +161,21 @@ bool Link::createECMResources() m_ecm->CreateComponent(m_entity, components::AngularAcceleration()); if (!this->enableContactDetection(false)) { - sError << "Failed to enable contact detection" << std::endl; + sError << "Failed to initialize contact detection" << std::endl; return false; } return true; } +bool Link::insertLinkPlugin(const std::string& libName, + const std::string& className, + const std::string& context) +{ + return utils::insertPluginToGazeboEntity( + *this, libName, className, context); +} + bool Link::valid() const { return this->validEntity() && pImpl->link.Valid(*m_ecm); @@ -300,8 +303,16 @@ bool Link::contactsEnabled() const ignition::gazebo::components::Collision(), ignition::gazebo::components::ParentEntity(m_entity)); - // Create the contact sensor data component that enables the Physics - // system to extract contact information from the physics engine + // If the link has no collision elements, we return true regardless. + // To prevent surprises, e.g. users expecting that calling Link::inContact + // for such links would return true, we print a debug message. + if (collisionEntities.empty()) { + sDebug << "The link '" << this->name() << "' has no collision elements " + << "and contacts cannot be detected" << std::endl; + return true; + } + + // Iterate through all link's collisions for (const auto collisionEntity : collisionEntities) { const bool hasContactSensorData = m_ecm->EntityHasComponentType( collisionEntity, @@ -344,6 +355,12 @@ bool Link::enableContactDetection(const bool enable) ignition::gazebo::components::Collision(), ignition::gazebo::components::ParentEntity(m_entity)); + // Links with no collision elements already print a sDebug in the + // contactsEnabled method, and not further action is needed + if (collisionEntities.empty()) { + return true; + } + // Delete the contact sensor data component for (const auto collisionEntity : collisionEntities) { m_ecm->RemoveComponent< @@ -351,6 +368,11 @@ bool Link::enableContactDetection(const bool enable) collisionEntity); } + if (this->contactsEnabled()) { + sError << "Failed to disable contact detection" << std::endl; + return false; + } + return true; } @@ -369,6 +391,7 @@ std::vector Link::contacts() const ignition::gazebo::components::ParentEntity(m_entity), ignition::gazebo::components::Collision()); + // Return early if the link has no collision elements if (collisionEntities.empty()) { return {}; } @@ -508,7 +531,7 @@ bool Link::applyWorldWrench(const std::array& force, assert(entityWithSimTime != ignition::gazebo::kNullEntity); // Get the current simulated time - auto& now = utils::getExistingComponentData< + const auto& now = utils::getExistingComponentData< ignition::gazebo::components::SimulatedTime>(m_ecm, entityWithSimTime); // Create a new wrench with duration diff --git a/cpp/scenario/gazebo/src/Model.cpp b/scenario/src/gazebo/src/Model.cpp similarity index 92% rename from cpp/scenario/gazebo/src/Model.cpp rename to scenario/src/gazebo/src/Model.cpp index 7f1101713..ce17f7432 100644 --- a/cpp/scenario/gazebo/src/Model.cpp +++ b/scenario/src/gazebo/src/Model.cpp @@ -34,15 +34,17 @@ #include "scenario/gazebo/components/BaseWorldVelocityTarget.h" #include "scenario/gazebo/components/JointControllerPeriod.h" #include "scenario/gazebo/components/Timestamp.h" -#include "scenario/gazebo/components/WorldVelocityCmd.h" #include "scenario/gazebo/exceptions.h" #include "scenario/gazebo/helpers.h" +#include "scenario/gazebo/utils.h" #include #include #include +#include #include #include +#include #include #include #include @@ -191,40 +193,8 @@ bool Model::insertModelPlugin(const std::string& libName, const std::string& className, const std::string& context) { - // Create a new element without context - sdf::ElementPtr pluginElement = - utils::getPluginSDFElement(libName, className); - - // Insert the context into the plugin element - if (!context.empty()) { - - std::shared_ptr contextRoot = - utils::getSdfRootFromString(context); - - if (!contextRoot) { - return false; - } - - // Get the first element of the context - // (stripping out the container) - auto contextNextElement = contextRoot->Element()->GetFirstElement(); - - // Insert the plugin context elements - while (contextNextElement) { - pluginElement->InsertElement(contextNextElement); - contextNextElement = contextNextElement->GetNextElement(); - } - } - - // The plugin element must be wrapped in another element, otherwise - // who receives it does not get the additional context - const auto wrapped = sdf::SDF::WrapInRoot(pluginElement); - - // Trigger the plugin loading - m_eventManager->Emit(m_entity, - wrapped); - - return true; + return utils::insertPluginToGazeboEntity( + *this, libName, className, context); } bool Model::resetJointPositions(const std::vector& positions, @@ -300,49 +270,13 @@ bool Model::resetBaseOrientation(const std::array& orientation) bool Model::resetBaseWorldLinearVelocity(const std::array& linear) { - // Check if the velocity was not already reset in this simulation run, - // otherwise the previous target would get overridden - if (!this->m_ecm->EntityHasComponentType( - this->m_entity, - ignition::gazebo::components::WorldVelocityCmd::typeId)) { - - return this->resetBaseWorldVelocity(linear, - this->baseWorldAngularVelocity()); - } - - // Get the existing cmd - const auto& velocityCmd = utils::getExistingComponentData< - ignition::gazebo::components::WorldVelocityCmd>(m_ecm, m_entity); - - // Override only the linear velocity - return this->resetBaseWorldVelocity( - linear, utils::fromIgnitionVector(velocityCmd.angular)); -} - -bool Model::resetBaseWorldAngularVelocity(const std::array& angular) -{ - // Check if the velocity was not already reset in this simulation run, - // otherwise the previous target would get overridden - if (!this->m_ecm->EntityHasComponentType( - this->m_entity, - ignition::gazebo::components::WorldVelocityCmd::typeId)) { + // Note: there could be a rigid transformation between the base frame and + // the canonical frame. The Physics system processes velocity commands + // in the canonical frame, but this method receives velocity commands + // of in the base frame (all expressed in world coordinates). + // Therefore, we need to compute the base linear velocity from the + // base frame to the canonical frame. - return this->resetBaseWorldVelocity(this->baseWorldLinearVelocity(), - angular); - } - - // Get the existing cmd - const auto& velocityCmd = utils::getExistingComponentData< - ignition::gazebo::components::WorldVelocityCmd>(m_ecm, m_entity); - - // Override only the angular velocity - return this->resetBaseWorldVelocity( - utils::fromIgnitionVector(velocityCmd.linear), angular); -} - -bool Model::resetBaseWorldVelocity(const std::array& linear, - const std::array& angular) -{ // Get the entity of the canonical (base) link const auto canonicalLinkEntity = m_ecm->EntityByComponents( ignition::gazebo::components::Link(), @@ -359,23 +293,40 @@ bool Model::resetBaseWorldVelocity(const std::array& linear, const auto& W_R_B = utils::toIgnitionQuaternion( this->getLink(this->baseFrame())->orientation()); - // Create the new model velocity - ignition::gazebo::WorldVelocity baseWorldVelocity; - - // Compute the mixed velocity of the base link - std::tie(baseWorldVelocity.linear, baseWorldVelocity.angular) = - utils::fromModelToBaseVelocity(utils::toIgnitionVector3(linear), - utils::toIgnitionVector3(angular), - M_H_B, - W_R_B); + // Compute the linear part of the base link mixed velocity + const ignition::math::Vector3d baseLinearWorldVelocity = + utils::fromModelToBaseLinearVelocity( + utils::toIgnitionVector3(linear), + utils::toIgnitionVector3(this->baseWorldAngularVelocity()), + M_H_B, + W_R_B); // Store the new velocity - utils::setComponentData( - m_ecm, m_entity, baseWorldVelocity); + utils::setComponentData( + m_ecm, m_entity, baseLinearWorldVelocity); + + return true; +} + +bool Model::resetBaseWorldAngularVelocity(const std::array& angular) +{ + // Note: the angular part of the velocity does not change between the base + // link and the canonical link (as the linear part). + // In fact, the angular velocity is invariant if there's a rigid + // transformation between the two frames, like in this case. + utils::setComponentData( + m_ecm, m_entity, utils::toIgnitionVector3(angular)); return true; } +bool Model::resetBaseWorldVelocity(const std::array& linear, + const std::array& angular) +{ + return this->resetBaseWorldLinearVelocity(linear) + && this->resetBaseWorldAngularVelocity(angular); +} + bool Model::valid() const { return this->validEntity() && pImpl->model.Valid(*m_ecm); @@ -674,6 +625,8 @@ std::vector Model::historyOfAppliedJointForces( bool Model::contactsEnabled() const { for (auto& link : this->links()) { + // Note: links with no collision elements return true even though no + // contacts can be detected. if (!link->contactsEnabled()) { return false; } @@ -688,6 +641,8 @@ bool Model::enableContacts(const bool enable) bool ok = true; for (auto& link : this->links()) { + // Note: links with no collision elements return true even though no + // contacts can be detected. ok = ok && link->enableContactDetection(enable); } @@ -1050,7 +1005,7 @@ std::array Model::baseWorldLinearVelocity() const this->getLink(this->baseFrame())->worldAngularVelocity()); // Convert the base velocity to the model mixed velocity - auto [modelLinearVelocity, _] = utils::fromBaseToModelVelocity( // + const auto& modelLinearVelocity = utils::fromBaseToModelLinearVelocity( // canonicalLinkLinearVelocity, canonicalLinkAngularVelocity, M_H_B, diff --git a/cpp/scenario/gazebo/src/World.cpp b/scenario/src/gazebo/src/World.cpp similarity index 80% rename from cpp/scenario/gazebo/src/World.cpp rename to scenario/src/gazebo/src/World.cpp index db7ca5b86..bbdc63ee4 100644 --- a/cpp/scenario/gazebo/src/World.cpp +++ b/scenario/src/gazebo/src/World.cpp @@ -31,6 +31,7 @@ #include "scenario/gazebo/components/Timestamp.h" #include "scenario/gazebo/exceptions.h" #include "scenario/gazebo/helpers.h" +#include "scenario/gazebo/utils.h" #include #include @@ -39,9 +40,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -72,23 +76,16 @@ class World::Impl const std::string& overrideModelName, World& world) { - if (modelSdfRoot->ModelCount() != 1) { - sError << "The SDF file contains more than one model" << std::endl; - return false; - } - - constexpr size_t ModelIndex = 0; + // NOTE: sdf::Root objects could only contain one sdf::Model starting + // from sdformat11. - // Every SDF model has a name. In order to insert multiple models from - // the same SDF file, the modelData struct allows providing a scoped - // name. + // Name of the model to insert (allowing renaming from SDF) std::string finalModelEntityName; // Get the final name of the model if (overrideModelName.empty()) { - assert(modelSdfRoot->ModelByIndex(ModelIndex)); - finalModelEntityName = - modelSdfRoot->ModelByIndex(ModelIndex)->Name(); + assert(modelSdfRoot->Model()); + finalModelEntityName = modelSdfRoot->Model()->Name(); } else { finalModelEntityName = overrideModelName; @@ -107,17 +104,15 @@ class World::Impl // Rename the model. // NOTE: The following is not enough because the name is not serialized - // to - // string. We need also to operate directly on the raw element. - const_cast(modelSdfRoot->ModelByIndex(ModelIndex)) + // to string. We need also to operate directly on the raw element. + const_cast(modelSdfRoot->Model()) ->SetName(finalModelEntityName); // Update the name in the sdf model. This is necessary because model // plugins are loaded right before the creation of the model entity and, // instead of receiving the model entity name, they receive the model // sdf name. - if (!utils::renameSDFModel( - *modelSdfRoot, finalModelEntityName, ModelIndex)) { + if (!utils::renameSDFModel(*modelSdfRoot, finalModelEntityName)) { sError << "Failed to rename SDF model" << std::endl; return false; } @@ -128,18 +123,15 @@ class World::Impl } // Create the model entity - ignition::gazebo::Entity modelEntity; - modelEntity = this->sdfEntityCreator->CreateEntities( - modelSdfRoot->ModelByIndex(ModelIndex)); + const ignition::gazebo::Entity modelEntity = + this->sdfEntityCreator->CreateEntities(modelSdfRoot->Model()); // Attach the model entity to the world entity this->sdfEntityCreator->SetParent(modelEntity, world.m_entity); { // Check that the model name is correct - assert(modelSdfRoot->ModelCount() == 1); - std::string modelNameSDF = - modelSdfRoot->ModelByIndex(ModelIndex)->Name(); + std::string modelNameSDF = modelSdfRoot->Model()->Name(); std::string modelNameEntity = utils::getExistingComponentData< // ignition::gazebo::components::Name>(world.m_ecm, modelEntity); assert(modelNameSDF == modelNameEntity); @@ -170,7 +162,7 @@ class World::Impl // We directly override the Pose component instead of using // Model::resetBasePose because it would just store a pose command that // needs to be processed by the Physics system. Overriding the - // component, instead, has direct effect. + // component, instead, has instantaneous effect. if (pose != core::Pose::Identity()) { utils::setComponentData( world.m_ecm, modelEntity, utils::toIgnitionPose(pose)); @@ -227,6 +219,24 @@ bool World::createECMResources() m_ecm, m_entity, std::chrono::steady_clock::duration::zero()); } + // Print the active physics profile + const auto& physics = utils::getExistingComponentData< // + ignition::gazebo::components::Physics>(m_ecm, m_entity); + sDebug << "Initializing world '" << this->name() + << "' with physics parameters:" << std::endl + << "rtf=" << physics.RealTimeFactor() << std::endl + << "step=" << physics.MaxStepSize() << std::endl + << "type=" << physics.EngineType() << std::endl; + + // Create required model resources + sMessage << "Models:" << std::endl; + for (const auto& model : this->models()) { + if (!std::static_pointer_cast(model)->createECMResources()) { + sError << "Failed to initialize ECM model resources" << std::endl; + return false; + } + } + return true; } @@ -234,54 +244,37 @@ bool World::insertWorldPlugin(const std::string& libName, const std::string& className, const std::string& context) { - // Create a new element without context - sdf::ElementPtr pluginElement = - utils::getPluginSDFElement(libName, className); - - // Insert the context into the plugin element - if (!context.empty()) { - - // Try to get the sdf::Root (it will alredy print an error if it fails) - auto contextRoot = utils::getSdfRootFromString(context); + return utils::insertPluginToGazeboEntity( + *this, libName, className, context); +} - if (!contextRoot) { - return false; +bool World::setPhysicsEngine(const PhysicsEngine engine) +{ + // Get the name of the physics plugin + const std::string pluginLib = [&engine]() -> std::string { + switch (engine) { + case PhysicsEngine::Dart: + return "ignition-physics" + + std::to_string(IGNITION_PHYSICS_MAJOR_VERSION) + + "-dartsim-plugin"; } + return ""; + }(); - // Get the first element of the context - // (stripping out the container) - auto contextNextElement = contextRoot->Element()->GetFirstElement(); - - // Insert the plugin context elements - while (contextNextElement) { - pluginElement->InsertElement(contextNextElement); - contextNextElement = contextNextElement->GetNextElement(); - } + if (pluginLib.empty()) { + sError << "Failed to retrieve the name of physics plugin library"; + return false; } - // The plugin element must be wrapped in another element, otherwise - // who receives it does not get the additional context - const auto wrapped = sdf::SDF::WrapInRoot(pluginElement); + // This component is read by the Physics system during its configuration + utils::setComponentData( + m_ecm, m_entity, pluginLib); - // Trigger the plugin loading - m_eventManager->Emit(m_entity, - wrapped); - - return true; -} - -bool World::setPhysicsEngine(const PhysicsEngine engine) -{ - std::string libName; - std::string className; - - switch (engine) { - case PhysicsEngine::Dart: - libName = "PhysicsSystem"; - className = "scenario::plugins::gazebo::Physics"; - break; - } + // Vendored Physics system + const std::string libName = "PhysicsSystem"; + const std::string className = "scenario::plugins::gazebo::Physics"; + // Load the Physics system if (!this->insertWorldPlugin(libName, className)) { sError << "Failed to insert the physics plugin" << std::endl; return false; @@ -391,6 +384,21 @@ scenario::core::ModelPtr World::getModel(const std::string& modelName) const return pImpl->models[modelName]; } +std::vector +World::models(const std::vector& modelNames) const +{ + const std::vector& modelSerialization = + modelNames.empty() ? this->modelNames() : modelNames; + + std::vector models; + + for (const auto& modelName : modelSerialization) { + models.push_back(this->getModel(modelName)); + } + + return models; +} + bool World::insertModel(const std::string& modelFile, const core::Pose& pose, const std::string& overrideModelName) @@ -409,8 +417,7 @@ bool World::insertModelFromFile(const std::string& path, return false; } - return pImpl.get()->insertModel( - modelSdfRoot, pose, overrideModelName, *this); + return pImpl->insertModel(modelSdfRoot, pose, overrideModelName, *this); } bool World::insertModelFromString(const std::string& sdfString, diff --git a/cpp/scenario/gazebo/src/helpers.cpp b/scenario/src/gazebo/src/helpers.cpp similarity index 90% rename from cpp/scenario/gazebo/src/helpers.cpp rename to scenario/src/gazebo/src/helpers.cpp index 5ef22dea2..843237151 100644 --- a/cpp/scenario/gazebo/src/helpers.cpp +++ b/scenario/src/gazebo/src/helpers.cpp @@ -320,20 +320,20 @@ sdf::World utils::renameSDFWorld(const sdf::World& world, return renamedWorld; } -bool utils::renameSDFModel(sdf::Root& sdfRoot, - const std::string& newModelName, - const size_t modelIndex) +bool utils::renameSDFModel(sdf::Root& sdfRoot, const std::string& newModelName) { - const size_t initialNrOfModels = sdfRoot.ModelCount(); - // Create a new model with the scoped name auto renamedModel = std::make_shared(); renamedModel->SetName("model"); renamedModel->AddAttribute("name", "string", newModelName, true); + if (!sdfRoot.Model()) { + sError << "The sdf Root does not contain any model" << std::endl; + return false; + } + // Get the first child of the original model element - sdf::ElementPtr child = - sdfRoot.ModelByIndex(modelIndex)->Element()->GetFirstElement(); + sdf::ElementPtr child = sdfRoot.Model()->Element()->GetFirstElement(); // Add all the children to the renamed model element while (child) { @@ -343,19 +343,14 @@ bool utils::renameSDFModel(sdf::Root& sdfRoot, } // Remove the old model - auto originalModelElement = sdfRoot.ModelByIndex(modelIndex)->Element(); + auto originalModelElement = sdfRoot.Model()->Element(); originalModelElement->RemoveFromParent(); // Insert the renamed model renamedModel->SetParent(sdfRoot.Element()); sdfRoot.Element()->InsertElement(renamedModel); - if (sdfRoot.ModelCount() != initialNrOfModels) { - sError << "Failed to rename SDF model" << std::endl; - return false; - } - - if (!sdfRoot.ModelNameExists(newModelName)) { + if (sdfRoot.Model()->Name() != newModelName) { sError << "Failed to insert renamed model in SDF root" << std::endl; return false; } @@ -504,46 +499,41 @@ scenario::core::JointType utils::fromSdf(const sdf::JointType sdfType) return type; } -std::pair -utils::fromModelToBaseVelocity(const ignition::math::Vector3d& linModelVelocity, - const ignition::math::Vector3d& angModelVelocity, - const ignition::math::Pose3d& M_H_B, - const ignition::math::Quaterniond& W_R_B) +ignition::math::Vector3d utils::fromModelToBaseLinearVelocity( + const ignition::math::Vector3d& linModelVelocity, + const ignition::math::Vector3d& angModelVelocity, + const ignition::math::Pose3d& M_H_B, + const ignition::math::Quaterniond& W_R_B) { - ignition::math::Vector3d linBaseVelocity; - const ignition::math::Vector3d& angBaseVelocity = angModelVelocity; - // Extract the rotation and the position of the model wrt to the base auto B_R_M = M_H_B.Rot().Inverse(); auto M_o_B = M_H_B.Pos(); auto B_o_M = -B_R_M * M_o_B; // Compute the base linear velocity - linBaseVelocity = linModelVelocity - angModelVelocity.Cross(W_R_B * B_o_M); + const ignition::math::Vector3d linBaseVelocity = + linModelVelocity - angModelVelocity.Cross(W_R_B * B_o_M); - // Return the mixed velocity of the base - return {linBaseVelocity, angBaseVelocity}; + // Return the linear part of the mixed velocity of the base + return linBaseVelocity; } -std::pair -utils::fromBaseToModelVelocity(const ignition::math::Vector3d& linBaseVelocity, - const ignition::math::Vector3d& angBaseVelocity, - const ignition::math::Pose3d& M_H_B, - const ignition::math::Quaterniond& W_R_B) +ignition::math::Vector3d utils::fromBaseToModelLinearVelocity( + const ignition::math::Vector3d& linBaseVelocity, + const ignition::math::Vector3d& angBaseVelocity, + const ignition::math::Pose3d& M_H_B, + const ignition::math::Quaterniond& W_R_B) { - ignition::math::Vector3d linModelVelocity; - const ignition::math::Vector3d& angModelVelocity = angBaseVelocity; - // Extract the rotation and the position of the model wrt to the base auto B_R_M = M_H_B.Rot().Inverse(); auto M_o_B = M_H_B.Pos(); // Compute the model linear velocity - linModelVelocity = + const ignition::math::Vector3d linModelVelocity = linBaseVelocity - angBaseVelocity.Cross(W_R_B * B_R_M * M_o_B); - // Return the mixed velocity of the model - return {linModelVelocity, angModelVelocity}; + // Return the linear part of the mixed velocity of the model + return linModelVelocity; } std::shared_ptr utils::getParentWorld(const GazeboEntity& gazeboEntity) diff --git a/cpp/scenario/gazebo/src/utils.cpp b/scenario/src/gazebo/src/utils.cpp similarity index 81% rename from cpp/scenario/gazebo/src/utils.cpp rename to scenario/src/gazebo/src/utils.cpp index b14855539..2405ed12d 100644 --- a/cpp/scenario/gazebo/src/utils.cpp +++ b/scenario/src/gazebo/src/utils.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -106,8 +107,7 @@ std::string utils::getSdfString(const std::string& fileName) return root->Element()->ToString(""); } -std::string utils::getModelNameFromSdf(const std::string& fileName, - const size_t modelIndex) +std::string utils::getModelNameFromSdf(const std::string& fileName) { std::string absFileName = findSdfFile(fileName); @@ -116,25 +116,18 @@ std::string utils::getModelNameFromSdf(const std::string& fileName, return {}; } - auto root = utils::getSdfRootFromFile(absFileName); + const auto root = utils::getSdfRootFromFile(absFileName); if (!root) { return {}; } - if (root->ModelCount() == 0) { - sError << "Didn't find any model in file " << fileName << std::endl; - return {}; + if (const auto model = root->Model()) { + return model->Name(); } - if (modelIndex >= root->ModelCount()) { - sError << "Model with index " << modelIndex - << " not found. The model has only " << root->ModelCount() - << " model(s)" << std::endl; - return {}; - } - - return root->ModelByIndex(modelIndex)->Name(); + sError << "No model found in file " << fileName << std::endl; + return {}; } std::string utils::getWorldNameFromSdf(const std::string& fileName, @@ -170,16 +163,17 @@ std::string utils::getWorldNameFromSdf(const std::string& fileName, std::string utils::getEmptyWorld() { + // The empty world matches default.sdf from upstream without models const std::string world = R""""( - + true 0 0 10 0 0 0 - 1 1 1 1 - 0.5 0.5 0.5 1 + 0.8 0.8 0.8 1 + 0.2 0.2 0.2 1 1000 0.9 @@ -374,3 +368,59 @@ std::vector utils::denormalize(const std::vector& input, return output; } + +bool utils::insertPluginToGazeboEntity(const GazeboEntity& gazeboEntity, + const std::string& libName, + const std::string& className, + const std::string& context) +{ + if (!gazeboEntity.validEntity()) { + sError << "The Gazebo Entity is not valid" << std::endl; + return false; + } + + if (libName.empty() || className.empty()) { + sError << "Either the library name or the class name are empty strings" + << std::endl; + return false; + } + + sLog << "Triggering plugin loading:" << std::endl; + sLog << className << " from " << libName << " for entity [" + << gazeboEntity.entity() << "]" << std::endl; + + // Create a new element without context + sdf::ElementPtr pluginElement = + utils::getPluginSDFElement(libName, className); + + // Insert the context into the plugin element + if (!context.empty()) { + + std::shared_ptr contextRoot = + utils::getSdfRootFromString(context); + + if (!contextRoot) { + return false; + } + + // Get the first element of the context + // (stripping out the container) + auto contextNextElement = contextRoot->Element()->GetFirstElement(); + + // Insert the plugin context elements + while (contextNextElement) { + pluginElement->InsertElement(contextNextElement); + contextNextElement = contextNextElement->GetNextElement(); + } + } + + // The plugin element must be wrapped in another element, otherwise + // who receives it does not get the additional context + const auto wrapped = sdf::SDF::WrapInRoot(pluginElement); + + // Trigger the plugin loading + gazeboEntity.eventManager()->Emit( + gazeboEntity.entity(), wrapped); + + return true; +} diff --git a/cpp/scenario/plugins/CMakeLists.txt b/scenario/src/plugins/CMakeLists.txt similarity index 72% rename from cpp/scenario/plugins/CMakeLists.txt rename to scenario/src/plugins/CMakeLists.txt index edabf66fc..14bb07213 100644 --- a/cpp/scenario/plugins/CMakeLists.txt +++ b/scenario/src/plugins/CMakeLists.txt @@ -23,17 +23,5 @@ # limitations under the License. add_subdirectory(Physics) -add_subdirectory(ECMProvider) add_subdirectory(JointController) add_subdirectory(ControllerRunner) - -install_basic_package_files(ScenarioGazeboPlugins - COMPONENT ScenarioGazeboPlugins - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion - EXPORT ScenarioGazeboPluginsExport - DEPENDENCIES ${ignition-gazebo} - NAMESPACE ScenarioGazeboPlugins:: - NO_CHECK_REQUIRED_COMPONENTS_MACRO - INSTALL_DESTINATION - ${SCENARIO_INSTALL_LIBDIR}/cmake/ScenarioGazeboPlugins) diff --git a/cpp/scenario/plugins/ControllerRunner/CMakeLists.txt b/scenario/src/plugins/ControllerRunner/CMakeLists.txt similarity index 100% rename from cpp/scenario/plugins/ControllerRunner/CMakeLists.txt rename to scenario/src/plugins/ControllerRunner/CMakeLists.txt diff --git a/cpp/scenario/plugins/ControllerRunner/ControllerRunner.cpp b/scenario/src/plugins/ControllerRunner/ControllerRunner.cpp similarity index 100% rename from cpp/scenario/plugins/ControllerRunner/ControllerRunner.cpp rename to scenario/src/plugins/ControllerRunner/ControllerRunner.cpp diff --git a/cpp/scenario/plugins/ControllerRunner/ControllerRunner.h b/scenario/src/plugins/ControllerRunner/ControllerRunner.h similarity index 100% rename from cpp/scenario/plugins/ControllerRunner/ControllerRunner.h rename to scenario/src/plugins/ControllerRunner/ControllerRunner.h diff --git a/cpp/scenario/plugins/ControllerRunner/ControllersFactory.cpp b/scenario/src/plugins/ControllerRunner/ControllersFactory.cpp similarity index 100% rename from cpp/scenario/plugins/ControllerRunner/ControllersFactory.cpp rename to scenario/src/plugins/ControllerRunner/ControllersFactory.cpp diff --git a/cpp/scenario/plugins/ControllerRunner/ControllersFactory.h b/scenario/src/plugins/ControllerRunner/ControllersFactory.h similarity index 100% rename from cpp/scenario/plugins/ControllerRunner/ControllersFactory.h rename to scenario/src/plugins/ControllerRunner/ControllersFactory.h diff --git a/cpp/scenario/plugins/JointController/CMakeLists.txt b/scenario/src/plugins/JointController/CMakeLists.txt similarity index 100% rename from cpp/scenario/plugins/JointController/CMakeLists.txt rename to scenario/src/plugins/JointController/CMakeLists.txt diff --git a/cpp/scenario/plugins/JointController/JointController.cpp b/scenario/src/plugins/JointController/JointController.cpp similarity index 100% rename from cpp/scenario/plugins/JointController/JointController.cpp rename to scenario/src/plugins/JointController/JointController.cpp diff --git a/cpp/scenario/plugins/JointController/JointController.h b/scenario/src/plugins/JointController/JointController.h similarity index 100% rename from cpp/scenario/plugins/JointController/JointController.h rename to scenario/src/plugins/JointController/JointController.h diff --git a/cpp/scenario/plugins/Physics/CMakeLists.txt b/scenario/src/plugins/Physics/CMakeLists.txt similarity index 89% rename from cpp/scenario/plugins/Physics/CMakeLists.txt rename to scenario/src/plugins/Physics/CMakeLists.txt index 80fca4065..e7dfe0c03 100644 --- a/cpp/scenario/plugins/Physics/CMakeLists.txt +++ b/scenario/src/plugins/Physics/CMakeLists.txt @@ -27,8 +27,10 @@ # ============= add_library(PhysicsSystem SHARED - Physics.h - Physics.cpp) + Physics.hh + EntityFeatureMap.hh + CanonicalLinkModelTracker.hh + Physics.cc) target_link_libraries(PhysicsSystem PUBLIC @@ -41,6 +43,10 @@ target_link_libraries(PhysicsSystem target_include_directories(PhysicsSystem PRIVATE $) +if(ENABLE_PROFILER) + target_compile_definitions(PhysicsSystem PRIVATE "IGN_PROFILER_ENABLE=1") +endif() + # =================== # Install the targets # =================== diff --git a/scenario/src/plugins/Physics/CanonicalLinkModelTracker.hh b/scenario/src/plugins/Physics/CanonicalLinkModelTracker.hh new file mode 100644 index 000000000..4f6f01443 --- /dev/null +++ b/scenario/src/plugins/Physics/CanonicalLinkModelTracker.hh @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#ifndef IGNITION_GAZEBO_SYSTEMS_PHYSICS_CANONICAL_LINK_MODEL_TRACKER_HH_ +#define IGNITION_GAZEBO_SYSTEMS_PHYSICS_CANONICAL_LINK_MODEL_TRACKER_HH_ + +#include +#include + +#include "ignition/gazebo/Entity.hh" +#include "ignition/gazebo/EntityComponentManager.hh" +#include "ignition/gazebo/components/CanonicalLink.hh" +#include "ignition/gazebo/components/Model.hh" +#include "ignition/gazebo/config.hh" + +namespace ignition::gazebo +{ +inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { +namespace systems::physics_system +{ + /// \brief Helper class that keeps track of which models have a particular + /// canonical link. This is useful in the physics system for updating model + /// poses - if a canonical link moved in the most recent physics step, then + /// all of the models that have this canonical link should be updated. It's + /// important to preserve topological ordering of the models in case there's + /// a nested model that shares the same canonical link (in a case like this, + /// the parent model pose needs to be updated before updating the child model + /// pose - see the documentation that explains how model pose updates are + /// calculated in PhysicsPrivate::UpdateSim to understand why nested model + /// poses need to be updated in topological order). + /// + /// It's possible to loop through all of the models and to update poses if the + /// model moved using something like EntityComponentManager::Each, but the + /// performance of this approach is worse than using just the moved canonical + /// links to determine which model poses should be updated (consider the case + /// where there are a lot of non-static models in a world, but only a few move + /// frequently - if using EntityComponentManager::Each, we still need to check + /// every single non-static model after a physics update to make sure that the + /// model did not move. If we instead use the updated canonical link + /// information, then we can skip iterating over/checking the models that + /// don't need to be updated). + class CanonicalLinkModelTracker + { + /// \brief Save mappings for new models and their canonical links + /// \param[in] _ecm EntityComponentManager + public: void AddNewModels(const EntityComponentManager &_ecm); + + /// \brief Get a topological ordering of models that have a particular + /// canonical link + /// \param[in] _canonicalLink The canonical link + /// \return The models that have this link as their canonical link, in + /// topological order + public: const std::set &CanonicalLinkModels( + const Entity _canonicalLink) const; + + /// \brief Remove a link from the mapping. This method should be called when + /// a link is removed from simulation + /// \param[in] _link The link to remove + public: void RemoveLink(const Entity &_link); + + /// \brief A mapping of canonical links to the models that have this + /// canonical link. The key is the canonical link entity, and the value is + /// the model entities that have this canonical link. The models in the + /// value are in topological order + private: std::unordered_map> linkModelMap; + + /// \brief An empty set of models that is returned from the + /// CanonicalLinkModels method for links that map to no models + private: const std::set emptyModelOrdering{}; + }; + + void CanonicalLinkModelTracker::AddNewModels( + const EntityComponentManager &_ecm) + { + _ecm.EachNew( + [this](const Entity &_model, const components::Model *, + const components::ModelCanonicalLink *_canonicalLinkComp) + { + this->linkModelMap[_canonicalLinkComp->Data()].insert(_model); + return true; + }); + } + + const std::set &CanonicalLinkModelTracker::CanonicalLinkModels( + const Entity _canonicalLink) const + { + auto it = this->linkModelMap.find(_canonicalLink); + if (it != this->linkModelMap.end()) + return it->second; + + // if an invalid entity was given, it maps to no models + return this->emptyModelOrdering; + } + + void CanonicalLinkModelTracker::RemoveLink(const Entity &_link) + { + this->linkModelMap.erase(_link); + } +} +} +} + +#endif diff --git a/scenario/src/plugins/Physics/EntityFeatureMap.hh b/scenario/src/plugins/Physics/EntityFeatureMap.hh new file mode 100644 index 000000000..933accc9f --- /dev/null +++ b/scenario/src/plugins/Physics/EntityFeatureMap.hh @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef IGNITION_GAZEBO_SYSTEMS_PHYSICS_ENTITY_FEATURE_MAP_HH_ +#define IGNITION_GAZEBO_SYSTEMS_PHYSICS_ENTITY_FEATURE_MAP_HH_ + +#include +#include +#include + +#include +#include +#include +#include + +#include "ignition/gazebo/Entity.hh" + +namespace ignition::gazebo +{ +inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { +namespace systems::physics_system +{ + // \brief Helper class that associates Gazebo entities with Physics entities + // with required and optional features. It can be used to cast a physics + // entity with the required features to another physics entity with one of + // the optional features. This class was created to keep all physics entities + // in one place so that when a gazebo entity is removed, all the mapped + // physics entitities can be removed at the same time. This ensures that + // reference counts are properly zeroed out in the underlying physics engines + // and the memory associated with the physics entities can be freed. + // + // DEV WARNING: There is an implicit conversion between physics EntityPtr and + // std::size_t in ign-physics. This seems also implicitly convert between + // EntityPtr and gazebo Entity. Therefore, any member function that takes a + // gazebo Entity can accidentally take an EntityPtr. To prevent this, for + // every function that takes a gazebo Entity, we should always have an + // overload that also takes an EntityPtr with required features. We can do + // this because there's a 1:1 mapping between the two in maps contained in + // this class. + // + // \tparam PhysicsEntityT Type of entity, such as World, Model, or Link + // \tparam PolicyT Policy of the physics engine (2D, 3D) + // \tparam RequiredFeatureList Required features of the physics entity + // \tparam OptionalFeatureLists One or more optional feature lists of the + // physics entity. + template