diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..887a2c1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# SCM syntax highlighting & preventing 3-way merges +pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/.gitignore b/.gitignore index 7068e57..f5c8331 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,6 @@ dmypy.json # Generated examples # examples/ +# pixi environments +.pixi/* +!.pixi/config.toml diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..13bac6d --- /dev/null +++ b/pixi.lock @@ -0,0 +1,2134 @@ +version: 6 +environments: + default: + channels: + - url: https://prefix.dev/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://prefix.dev/conda-forge/noarch/black-25.1.0-pyh866005b_0.conda + - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.2.0-py314hdfeb8a1_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/catkin_pkg-1.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/click-8.3.0-pyh707e725_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/docutils-0.22.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 + - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/noarch/license-expression-30.4.4-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mccabe-0.7.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://prefix.dev/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycodestyle-2.14.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pyflakes-3.4.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rosdistro-1.0.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rospkg-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml-0.17.17-py314h5bd0f2a_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py314h5bd0f2a_0.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda + - conda: https://prefix.dev/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.25.0-py314h31f8a6b_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - pypi: ./ + osx-64: + - conda: https://prefix.dev/conda-forge/noarch/black-25.1.0-pyh866005b_0.conda + - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.2.0-py314hd4d9bf7_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/catkin_pkg-1.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/cffi-2.0.0-py314h8ca4d5a_1.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/click-8.3.0-pyh707e725_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/docutils-0.22.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 + - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libcxx-21.1.4-h3d58e20_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://prefix.dev/conda-forge/noarch/license-expression-30.4.4-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mccabe-0.7.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://prefix.dev/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycodestyle-2.14.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pyflakes-3.4.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rosdistro-1.0.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rospkg-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml-0.17.17-py314h6482030_4.conda + - conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml.clib-0.2.14-py314h6482030_0.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda + - conda: https://prefix.dev/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.25.0-py314h12c88b1_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + - pypi: ./ + osx-arm64: + - conda: https://prefix.dev/conda-forge/noarch/black-25.1.0-pyh866005b_0.conda + - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.2.0-py314h95ef04c_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/catkin_pkg-1.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/click-8.3.0-pyh707e725_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/docutils-0.22.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 + - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.4-hf598326_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://prefix.dev/conda-forge/noarch/license-expression-30.4.4-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mccabe-0.7.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://prefix.dev/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycodestyle-2.14.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pyflakes-3.4.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.0-h40d2674_102_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rosdistro-1.0.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rospkg-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml-0.17.17-py314h0612a62_4.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.14-py314h0612a62_0.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda + - conda: https://prefix.dev/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.25.0-py314h163e31d_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - pypi: ./ + win-64: + - conda: https://prefix.dev/conda-forge/noarch/black-25.1.0-pyh866005b_0.conda + - conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.2.0-py314ha608bb1_0.conda + - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/noarch/catkin_pkg-1.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda + - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/click-8.3.0-pyh7428d3b_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/docutils-0.22.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 + - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda + - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://prefix.dev/conda-forge/noarch/license-expression-30.4.4-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mccabe-0.7.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda + - conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycodestyle-2.14.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pyflakes-3.4.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.14.0-h4b44e0e_102_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://prefix.dev/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda + - conda: https://prefix.dev/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rosdistro-1.0.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/rospkg-1.6.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml-0.17.17-py314h5a2d7ad_4.conda + - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml.clib-0.2.14-py314h5a2d7ad_0.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + - conda: https://prefix.dev/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + - conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.25.0-py314h4667ab5_0.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - pypi: ./ +packages: +- conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + purls: [] + size: 2562 + timestamp: 1578324546067 +- conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + build_number: 16 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23621 + timestamp: 1650670423406 +- conda: https://prefix.dev/conda-forge/noarch/black-25.1.0-pyh866005b_0.conda + sha256: c68f110cd491dc839a69e340930862e54c00fb02cede5f1831fcf8a253bd68d2 + md5: b9b0c42e7316aa6043bdfd49883955b8 + depends: + - click >=8.0.0 + - mypy_extensions >=0.4.3 + - packaging >=22.0 + - pathspec >=0.9 + - platformdirs >=2 + - python >=3.11 + license: MIT + license_family: MIT + purls: + - pkg:pypi/black?source=hash-mapping + size: 172678 + timestamp: 1742502887437 +- conda: https://prefix.dev/conda-forge/noarch/boolean.py-5.0-pyhd8ed1ab_0.conda + sha256: 6195e09f7d8a3a5e2fc0dddd6d1e87198e9c3d2a1982ff04624957a6c6466e54 + md5: 26c3480f80364e9498a48bb5c3e35f85 + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/boolean-py?source=hash-mapping + size: 29946 + timestamp: 1743687383956 +- conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.2.0-py314hdfeb8a1_0.conda + sha256: 9f6d339fb78b647be35e3564dac453d8d2f1b865ba72fb961eaac41061368699 + md5: 3ef9d2a701760467b9db2338b6cd926f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 h09219d5_0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 368319 + timestamp: 1761592337171 +- conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.2.0-py314hd4d9bf7_0.conda + sha256: cb5a9558123eade3beda7770a5a373a941928b65ac39dcab2b1c7c92c2556a85 + md5: 37525e28a91deef6c47690878d7338b6 + depends: + - __osx >=10.13 + - libcxx >=19 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 h105ed1c_0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 389830 + timestamp: 1761593069187 +- conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.2.0-py314h95ef04c_0.conda + sha256: 231c3e2d0a2635f51e4e0fd56ba0def25b21a7c484d31e863f261823af5351e3 + md5: 5f71e1aa8d7982bda0a87b6bfd5c71fd + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 h87ba0bc_0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 359535 + timestamp: 1761592749203 +- conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.2.0-py314ha608bb1_0.conda + sha256: 5ec31c4e54ed352ff76b80bfa0ecb42295c38ec7eec351defd4ceaa0bdb7460e + md5: 90852a7a63c0916867c47997dfc74dd9 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hc82b238_0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335525 + timestamp: 1761592951484 +- conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 + md5: 51a19bba1b8ebfb60df25cde030b7ebc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260341 + timestamp: 1757437258798 +- conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c + md5: 97c4b3bd8a90722104798175a1bdddbf + depends: + - __osx >=10.13 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 132607 + timestamp: 1757437730085 +- conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + sha256: b456200636bd5fecb2bec63f7e0985ad2097cf1b83d60ce0b6968dffa6d02aa1 + md5: 58fd217444c2a5701a44244faf518206 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 125061 + timestamp: 1757437486465 +- conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda + sha256: d882712855624641f48aa9dc3f5feea2ed6b4e6004585d3616386a18186fe692 + md5: 1077e9333c41ff0be8edd1a5ec0ddace + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 55977 + timestamp: 1757437738856 +- conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda + sha256: bfb7f9f242f441fdcd80f1199edd2ecf09acea0f2bcef6f07d7cbb1a8131a345 + md5: e54200a1cd1fe33d61c9df8d3b00b743 + depends: + - __win + license: ISC + purls: [] + size: 156354 + timestamp: 1759649104842 +- conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 + md5: f9e5fbc24009179e8b0409624691758a + depends: + - __unix + license: ISC + purls: [] + size: 155907 + timestamp: 1759649036195 +- conda: https://prefix.dev/conda-forge/noarch/catkin_pkg-1.1.0-pyhd8ed1ab_0.conda + sha256: fe602164dc1920551e1452543e22338d55d8a879959f12598c9674cf295c6341 + md5: 3e500faf80e42f26d422d849877d48c4 + depends: + - docutils + - packaging + - pyparsing >=1.5.7 + - python >=3.10 + - python-dateutil + - setuptools + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/catkin-pkg?source=hash-mapping + size: 54106 + timestamp: 1757558592553 +- conda: https://prefix.dev/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda + sha256: 955bac31be82592093f6bc006e09822cd13daf52b28643c9a6abd38cd5f4a306 + md5: 257ae203f1d204107ba389607d375ded + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=hash-mapping + size: 160248 + timestamp: 1759648987029 +- conda: https://prefix.dev/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda + sha256: c6339858a0aaf5d939e00d345c98b99e4558f285942b27232ac098ad17ac7f8e + md5: cf45f4278afd6f4e6d03eda0f435d527 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 300271 + timestamp: 1761203085220 +- conda: https://prefix.dev/conda-forge/osx-64/cffi-2.0.0-py314h8ca4d5a_1.conda + sha256: e2c58cc2451cc96db2a3c8ec34e18889878db1e95cc3e32c85e737e02a7916fb + md5: 71c2caaa13f50fe0ebad0f961aee8073 + depends: + - __osx >=10.13 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 293633 + timestamp: 1761203106369 +- conda: https://prefix.dev/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda + sha256: 5b5ee5de01eb4e4fd2576add5ec9edfc654fbaf9293e7b7ad2f893a67780aa98 + md5: 10dd19e4c797b8f8bdb1ec1fbb6821d7 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 292983 + timestamp: 1761203354051 +- conda: https://prefix.dev/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda + sha256: 924f2f01fa7a62401145ef35ab6fc95f323b7418b2644a87fea0ea68048880ed + md5: c360170be1c9183654a240aadbedad94 + depends: + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 294731 + timestamp: 1761203441365 +- conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + sha256: b32f8362e885f1b8417bac2b3da4db7323faa12d5db62b7fd6691c02d60d6f59 + md5: a22d1fd9bf98827e280a02875d9a007a + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=hash-mapping + size: 50965 + timestamp: 1760437331772 +- conda: https://prefix.dev/conda-forge/noarch/click-8.3.0-pyh707e725_0.conda + sha256: c6567ebc27c4c071a353acaf93eb82bb6d9a6961e40692a359045a89a61d02c0 + md5: e76c4ba9e1837847679421b8d549b784 + depends: + - __unix + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/click?source=compressed-mapping + size: 91622 + timestamp: 1758270534287 +- conda: https://prefix.dev/conda-forge/noarch/click-8.3.0-pyh7428d3b_0.conda + sha256: 0a008359973e833b568d0a18cf04556b12a4f5182e745dfc8ade32c38fa1fca5 + md5: 4601476ee4ad7ad522e5ffa5a579a48e + depends: + - __win + - colorama + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/click?source=hash-mapping + size: 92148 + timestamp: 1758270588199 +- conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + size: 27011 + timestamp: 1733218222191 +- conda: https://prefix.dev/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + sha256: 5603c7d0321963bb9b4030eadabc3fd7ca6103a38475b4e0ed13ed6d97c86f4e + md5: 0a2014fd9860f8b1eaa0b1f3d3771a08 + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/distro?source=hash-mapping + size: 41773 + timestamp: 1734729953882 +- conda: https://prefix.dev/conda-forge/noarch/docutils-0.22.2-pyhd8ed1ab_0.conda + sha256: dd02330f2ecca4a489a001e5ec66ee8aa50773dc2c621c8fc7053b454d9a27b2 + md5: ba6a7a1c262587d333761b0cda2bbd28 + depends: + - python >=3.10 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + purls: + - pkg:pypi/docutils?source=hash-mapping + size: 437394 + timestamp: 1758409808966 +- conda: https://prefix.dev/conda-forge/noarch/empy-3.3.4-pyh9f0ad1d_1.tar.bz2 + sha256: 75e04755df8d8db7a7711dddaf68963c11258b755c9c24565bfefa493ee383e3 + md5: e4be10fd1a907b223da5be93f06709d2 + depends: + - python + license: LGPL-2.1 + license_family: GPL + purls: + - pkg:pypi/empy?source=hash-mapping + size: 40210 + timestamp: 1586444722817 +- conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca + md5: 72e42d28960d875c7654614f8b50939a + depends: + - python >=3.9 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 21284 + timestamp: 1746947398083 +- conda: https://prefix.dev/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda + sha256: a32e511ea71a9667666935fd9f497f00bcc6ed0099ef04b9416ac24606854d58 + md5: 04a55140685296b25b79ad942264c0ef + depends: + - mccabe >=0.7.0,<0.8.0 + - pycodestyle >=2.14.0,<2.15.0 + - pyflakes >=3.4.0,<3.5.0 + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/flake8?source=hash-mapping + size: 111916 + timestamp: 1750968083921 +- conda: https://prefix.dev/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.1,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=compressed-mapping + size: 95967 + timestamp: 1756364871835 +- conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 30731 + timestamp: 1737618390337 +- conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + size: 17397 + timestamp: 1737618427549 +- conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda + sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620 + md5: 5eb22c1d7b3fc4abb50d92d621583137 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 11857802 + timestamp: 1720853997952 +- conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 + md5: 53abe63df7e10a6ba605dc5f9f961d36 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=hash-mapping + size: 50721 + timestamp: 1760286526795 +- conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 + md5: 9614359868482abba1bd15ce465e3c42 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/iniconfig?source=compressed-mapping + size: 13387 + timestamp: 1760831448842 +- conda: https://prefix.dev/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af + md5: 446bd6c8cb26050d528881df495ce646 + depends: + - markupsafe >=2.0 + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=hash-mapping + size: 112714 + timestamp: 1741263433881 +- conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda + sha256: 96b6900ca0489d9e5d0318a6b49f8eff43fd85fef6e07cb0c25344ee94cd7a3a + md5: c94ab6ff54ba5172cf1c58267005670f + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.44 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 742501 + timestamp: 1761335175964 +- conda: https://prefix.dev/conda-forge/osx-64/libcxx-21.1.4-h3d58e20_0.conda + sha256: 64f58f7ad9076598ae4a19f383f6734116d96897032c77de599660233f2924f9 + md5: 17c4292004054f6783b16b55b499f086 + depends: + - __osx >=10.13 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 571252 + timestamp: 1761043932993 +- conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.4-hf598326_0.conda + sha256: df55e80dda21f2581366f66cf18a6c11315d611f6fb01e56011c5199f983c0d9 + md5: 6002a2ba796f1387b6a5c6d77051d1db + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 567892 + timestamp: 1761043967532 +- conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 + md5: 4211416ecba1866fab0c6470986c22d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + purls: [] + size: 74811 + timestamp: 1752719572741 +- conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda + sha256: 689862313571b62ee77ee01729dc093f2bf25a2f99415fcfe51d3a6cd31cce7b + md5: 9fdeae0b7edda62e989557d645769515 + depends: + - __osx >=10.13 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + purls: [] + size: 72450 + timestamp: 1752719744781 +- conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + sha256: 8fbb17a56f51e7113ed511c5787e0dec0d4b10ef9df921c4fd1cccca0458f648 + md5: b1ca5f21335782f71a8bd69bdc093f67 + depends: + - __osx >=11.0 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + purls: [] + size: 65971 + timestamp: 1752719657566 +- conda: https://prefix.dev/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda + sha256: 8432ca842bdf8073ccecf016ccc9140c41c7114dc4ec77ca754551c01f780845 + md5: 3608ffde260281fa641e70d6e34b1b96 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + purls: [] + size: 141322 + timestamp: 1752719767870 +- conda: https://prefix.dev/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 57821 + timestamp: 1760295480630 +- conda: https://prefix.dev/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda + sha256: 277dc89950f5d97f1683f26e362d6dca3c2efa16cb2f6fdb73d109effa1cd3d0 + md5: d214916b24c625bcc459b245d509f22e + depends: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 52573 + timestamp: 1760295626449 +- conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + sha256: 9b8acdf42df61b7bfe8bdc545c016c29e61985e79748c64ad66df47dbc2e295f + md5: 411ff7cd5d1472bba0f55c0faf04453b + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 40251 + timestamp: 1760295839166 +- conda: https://prefix.dev/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda + sha256: ddff25aaa4f0aa535413f5d831b04073789522890a4d8626366e43ecde1534a3 + md5: ba4ad812d2afc22b9a34ce8327a0930f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 44866 + timestamp: 1760295760649 +- conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 + md5: c0374badb3a5d4b1372db28d19462c53 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgomp 15.2.0 h767d61c_7 + - libgcc-ng ==15.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 822552 + timestamp: 1759968052178 +- conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca + md5: f7b4d76975aac7e5d9e6ad13845f92fe + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 447919 + timestamp: 1759967942498 +- conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 + md5: 1a580f7796c7bf6393fddb8bbbde58dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 112894 + timestamp: 1749230047870 +- conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + sha256: 7e22fd1bdb8bf4c2be93de2d4e718db5c548aa082af47a7430eb23192de6bb36 + md5: 8468beea04b9065b9807fc8b9cdc5894 + depends: + - __osx >=10.13 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 104826 + timestamp: 1749230155443 +- conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + sha256: 0cb92a9e026e7bd4842f410a5c5c665c89b2eb97794ffddba519a626b8ce7285 + md5: d6df911d4564d77c4374b02552cb17d1 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 92286 + timestamp: 1749230283517 +- conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + sha256: 55764956eb9179b98de7cc0e55696f2eff8f7b83fc3ebff5e696ca358bca28cc + md5: c15148b2e18da456f5108ccb5e411446 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 104935 + timestamp: 1749230611612 +- conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee + md5: c7e925f37e3b40d893459e625f6a53f1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 91183 + timestamp: 1748393666725 +- conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda + sha256: 98299c73c7a93cd4f5ff8bb7f43cd80389f08b5a27a296d806bdef7841cc9b9e + md5: 18b81186a6adb43f000ad19ed7b70381 + depends: + - __osx >=10.13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 77667 + timestamp: 1748393757154 +- conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 + md5: 85ccccb47823dd9f7a99d2c7f530342f + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 71829 + timestamp: 1748393749336 +- conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda + sha256: fc529fc82c7caf51202cc5cec5bb1c2e8d90edbac6d0a4602c966366efe3c7bf + md5: 74860100b2029e2523cf480804c76b9b + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 88657 + timestamp: 1723861474602 +- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda + sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da + md5: 0b367fad34931cb79e0d6b7e5c06bb1c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 932581 + timestamp: 1753948484112 +- conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda + sha256: 466366b094c3eb4b1d77320530cbf5400e7a10ab33e4824c200147488eebf7a6 + md5: 156bfb239b6a67ab4a01110e6718cbc4 + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 980121 + timestamp: 1753948554003 +- conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda + sha256: 802ebe62e6bc59fc26b26276b793e0542cfff2d03c086440aeaf72fb8bbcec44 + md5: 1dcb0468f5146e38fae99aef9656034b + depends: + - __osx >=11.0 + - icu >=75.1,<76.0a0 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 902645 + timestamp: 1753948599139 +- conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda + sha256: 5dc4f07b2d6270ac0c874caec53c6984caaaa84bc0d3eb593b0edf3dc8492efa + md5: ccb20d946040f86f0c05b644d5eadeca + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1288499 + timestamp: 1753948889360 +- conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 + md5: 5b767048b1b3ee9a954b06f4084f93dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 h767d61c_7 + constrains: + - libstdcxx-ng ==15.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 3898269 + timestamp: 1759968103436 +- conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 + md5: 80c07c68d2f6870250959dcc95b209d1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 37135 + timestamp: 1758626800002 +- conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 + md5: edb0dca6bc32e4f4789199455a1dbeb8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 60963 + timestamp: 1727963148474 +- conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09 + md5: 003a54a4e32b02f7355b50a837e699da + depends: + - __osx >=10.13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 57133 + timestamp: 1727963183990 +- conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b + md5: 369964e85dc26bfe78f41399b366c435 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 46438 + timestamp: 1727963202283 +- conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 + md5: 41fbfac52c601159df6c01f875de31b9 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 55476 + timestamp: 1727963768015 +- conda: https://prefix.dev/conda-forge/noarch/license-expression-30.4.4-pyhe01879c_0.conda + sha256: c0fc62fa5c7552b5ec42324fdc6c9fef05eaa239a434c721df074b42e7a52611 + md5: c9b00d4ac670f5401b9ed080c96eb9f1 + depends: + - boolean.py >=4.0.0 + - python >=3.9 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/license-expression?source=hash-mapping + size: 120884 + timestamp: 1753294907822 +- conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + sha256: 7b1da4b5c40385791dbc3cc85ceea9fad5da680a27d5d3cb8bfaa185e304a89e + md5: 5b5203189eb668f042ac2b0826244964 + depends: + - mdurl >=0.1,<1 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/markdown-it-py?source=hash-mapping + size: 64736 + timestamp: 1754951288511 +- conda: https://prefix.dev/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda + sha256: e0cbfea51a19b3055ca19428bd9233a25adca956c208abb9d00b21e7259c7e03 + md5: fab1be106a50e20f10fe5228fd1d1651 + depends: + - python >=3.10 + constrains: + - jinja2 >=3.0.0 + track_features: + - markupsafe_no_compile + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 15499 + timestamp: 1759055275624 +- conda: https://prefix.dev/conda-forge/noarch/mccabe-0.7.0-pyhd8ed1ab_1.conda + sha256: 9b0037171dad0100f0296699a11ae7d355237b55f42f9094aebc0f41512d96a1 + md5: 827064ddfe0de2917fb29f1da4f8f533 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mccabe?source=hash-mapping + size: 12934 + timestamp: 1733216573915 +- conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 + md5: 592132998493b3ff25fd7479396e8351 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mdurl?source=hash-mapping + size: 14465 + timestamp: 1733255681319 +- conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + sha256: 6ed158e4e5dd8f6a10ad9e525631e35cee8557718f83de7a4e3966b1f772c4b1 + md5: e9c622e0d00fa24a6292279af3ab6d06 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mypy-extensions?source=hash-mapping + size: 11766 + timestamp: 1745776666688 +- conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause + purls: [] + size: 891641 + timestamp: 1738195959188 +- conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + sha256: ea4a5d27ded18443749aefa49dc79f6356da8506d508b5296f60b8d51e0c4bd9 + md5: ced34dd9929f491ca6dab6a2927aff25 + depends: + - __osx >=10.13 + license: X11 AND BSD-3-Clause + purls: [] + size: 822259 + timestamp: 1738196181298 +- conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 + md5: 068d497125e4bf8a66bf707254fff5ae + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + size: 797030 + timestamp: 1738196177597 +- conda: https://prefix.dev/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda + sha256: 02019191a2597865940394ff42418b37bc585a03a1c643d7cea9981774de2128 + md5: 16bff3d37a4f99e3aa089c36c2b8d650 + depends: + - python >=3.11 + - python + constrains: + - numpy >=1.25 + - scipy >=1.11.2 + - matplotlib >=3.8 + - pandas >=2.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/networkx?source=hash-mapping + size: 1564462 + timestamp: 1749078300258 +- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda + sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 + md5: 14edad12b59ccbfa3910d42c72adc2a0 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3119624 + timestamp: 1759324353651 +- conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda + sha256: 3ce8467773b2472b2919412fd936413f05a9b10c42e52c27bbddc923ef5da78a + md5: 075eaad78f96bbf5835952afbe44466e + depends: + - __osx >=10.13 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2747108 + timestamp: 1759326402264 +- conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda + sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07 + md5: 71118318f37f717eefe55841adb172fd + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3067808 + timestamp: 1759324763146 +- conda: https://prefix.dev/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda + sha256: 5ddc1e39e2a8b72db2431620ad1124016f3df135f87ebde450d235c212a61994 + md5: f28ffa510fe055ab518cbd9d6ddfea23 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9218823 + timestamp: 1759326176247 +- conda: https://prefix.dev/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 + md5: 58335b26c38bf4a20f399384c33cbcf9 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + size: 62477 + timestamp: 1745345660407 +- conda: https://prefix.dev/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda + sha256: 9f64009cdf5b8e529995f18e03665b03f5d07c0b17445b8badef45bde76249ee + md5: 617f15191456cc6a13db418a275435e5 + depends: + - python >=3.9 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/pathspec?source=hash-mapping + size: 41075 + timestamp: 1733233471940 +- conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + sha256: 7efd51b48d908de2d75cbb3c4a2e80dd9454e1c5bb8191b261af3136f7fa5888 + md5: 5c7a868f8241e64e1cf5fdf4962f23e2 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=hash-mapping + size: 23625 + timestamp: 1759953252315 +- conda: https://prefix.dev/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + sha256: a8eb555eef5063bbb7ba06a379fa7ea714f57d9741fe0efdb9442dbbc2cccbcc + md5: 7da7ccd349dbf6487a7778579d2bb971 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pluggy?source=hash-mapping + size: 24246 + timestamp: 1747339794916 +- conda: https://prefix.dev/conda-forge/noarch/pycodestyle-2.14.0-pyhd8ed1ab_0.conda + sha256: 1950f71ff44e64163e176b1ca34812afc1a104075c3190de50597e1623eb7d53 + md5: 85815c6a22905c080111ec8d56741454 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pycodestyle?source=hash-mapping + size: 35182 + timestamp: 1750616054854 +- conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 + md5: 12c566707c80111f9799308d9e265aef + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 110100 + timestamp: 1733195786147 +- conda: https://prefix.dev/conda-forge/noarch/pyflakes-3.4.0-pyhd8ed1ab_0.conda + sha256: 4b6fb3f7697b4e591c06149671699777c71ca215e9ec16d5bd0767425e630d65 + md5: dba204e749e06890aeb3756ef2b1bf35 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyflakes?source=hash-mapping + size: 59592 + timestamp: 1750492011671 +- conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a + md5: 6b6ece66ebcae2d5f326c77ef2c5a066 + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=hash-mapping + size: 889287 + timestamp: 1750615908735 +- conda: https://prefix.dev/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + sha256: 6814b61b94e95ffc45ec539a6424d8447895fef75b0fec7e1be31f5beee883fb + md5: 6c8979be6d7a17692793114fa26916e8 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyparsing?source=hash-mapping + size: 104044 + timestamp: 1758436411254 +- conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 + depends: + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21784 + timestamp: 1733217448189 +- conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21085 + timestamp: 1733217331982 +- conda: https://prefix.dev/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda + sha256: 41053d9893e379a3133bb9b557b98a3d2142fca474fb6b964ba5d97515f78e2d + md5: 1f987505580cb972cf28dc5f74a0f81b + depends: + - colorama >=0.4 + - exceptiongroup >=1 + - iniconfig >=1 + - packaging >=20 + - pluggy >=1.5,<2 + - pygments >=2.7.2 + - python >=3.10 + - tomli >=1 + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest?source=hash-mapping + size: 276734 + timestamp: 1757011891753 +- conda: https://prefix.dev/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda + build_number: 102 + sha256: 76d750045b94fded676323bfd01975a26a474023635735773d0e4d80aaa72518 + md5: 0a19d2cc6eb15881889b0c6fa7d6a78d + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 36681389 + timestamp: 1761176838143 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://prefix.dev/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda + build_number: 102 + sha256: 2470866eee70e75d6be667aa537424b63f97c397a0a90f05f2bab347b9ed5a51 + md5: 7917d1205eed3e72366a3397dca8a2af + depends: + - __osx >=10.13 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 14427639 + timestamp: 1761177864469 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://prefix.dev/conda-forge/osx-arm64/python-3.14.0-h40d2674_102_cp314.conda + build_number: 102 + sha256: 3ca1da026fe5df8a479d60e1d3ed02d9bc50fcbafd5f125d86abe70d21a34cc7 + md5: a9ff09231c555da7e30777747318321b + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 13590581 + timestamp: 1761177195716 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://prefix.dev/conda-forge/win-64/python-3.14.0-h4b44e0e_102_cp314.conda + build_number: 102 + sha256: 2b8c8fcafcc30690b4c5991ee28eb80c962e50e06ce7da03b2b302e2d39d6a81 + md5: 3e1ce2fb0f277cebcae01a3c418eb5e2 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 16706286 + timestamp: 1761175439068 + python_site_packages_path: Lib/site-packages +- conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 + depends: + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + size: 233310 + timestamp: 1751104122689 +- conda: https://prefix.dev/conda-forge/noarch/python_abi-3.14-8_cp314.conda + build_number: 8 + sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 + md5: 0539938c55b6b1a59b560e843ad864a4 + constrains: + - python 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6989 + timestamp: 1752805904792 +- conda: https://prefix.dev/conda-forge/noarch/pyyaml-6.0.3-pyh7db6752_0.conda + sha256: 828af2fd7bb66afc9ab1c564c2046be391aaf66c0215f05afaf6d7a9a270fe2a + md5: b12f41c0d7fb5ab81709fcc86579688f + depends: + - python >=3.10.* + - yaml + track_features: + - pyyaml_no_compile + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 45223 + timestamp: 1758891992558 +- conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 + depends: + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 282480 + timestamp: 1740379431762 +- conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda + sha256: 53017e80453c4c1d97aaf78369040418dea14cf8f46a2fa999f31bd70b36c877 + md5: 342570f8e02f2f022147a7f841475784 + depends: + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 256712 + timestamp: 1740379577668 +- conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + sha256: 7db04684d3904f6151eff8673270922d31da1eea7fa73254d01c437f49702e34 + md5: 63ef3f6e6d6d5c589e64f11263dc5676 + depends: + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 252359 + timestamp: 1740379663071 +- conda: https://prefix.dev/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + sha256: 8dc54e94721e9ab545d7234aa5192b74102263d3e704e6d0c8aa7008f2da2a7b + md5: db0c6b99149880c8ba515cf4abe93ee4 + depends: + - certifi >=2017.4.17 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - python >=3.9 + - urllib3 >=1.21.1,<3 + constrains: + - chardet >=3.0.2,<6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + size: 59263 + timestamp: 1755614348400 +- conda: https://prefix.dev/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + sha256: edfb44d0b6468a8dfced728534c755101f06f1a9870a7ad329ec51389f16b086 + md5: a247579d8a59931091b16a1e932bbed6 + depends: + - markdown-it-py >=2.2.0 + - pygments >=2.13.0,<3.0.0 + - python >=3.10 + - typing_extensions >=4.0.0,<5.0.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rich?source=compressed-mapping + size: 200840 + timestamp: 1760026188268 +- conda: https://prefix.dev/conda-forge/noarch/rosdistro-1.0.1-pyhd8ed1ab_0.conda + sha256: bff3b2fe7afe35125669ffcb7d6153db78070a753e1e4ac3b3d8d198eb6d6982 + md5: b7ed380a9088b543e06a4f73985ed03a + depends: + - catkin_pkg + - python >=3.9 + - pyyaml + - rospkg + - setuptools + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/rosdistro?source=hash-mapping + size: 47691 + timestamp: 1747826651335 +- conda: https://prefix.dev/conda-forge/noarch/rospkg-1.6.0-pyhd8ed1ab_0.conda + sha256: 236e8b53b0fab599d63f346b0e84fbe9bd8d160e0dd1e591e39f23ff6924941e + md5: 80daa4ba1f1944b8ac1f90a66fc9ef27 + depends: + - catkin_pkg + - distro + - python >=3.9 + - pyyaml + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/rospkg?source=hash-mapping + size: 31293 + timestamp: 1737835793379 +- conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml-0.17.17-py314h5bd0f2a_4.conda + sha256: d916260846d379817d9341bef02761737a6b55f7cf256a4a874aea4869461f06 + md5: d824a7249a8f1ce1817c05090338d8ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ruamel.yaml.clib >=0.1.2 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 256115 + timestamp: 1757164524523 +- conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml-0.17.17-py314h6482030_4.conda + sha256: 1fc0b29f1203b907235b77cc52553e6a209297d05a052b8acfc61bb83037c3e5 + md5: 7afd27428a330e9d3d50eb442042b77e + depends: + - __osx >=10.13 + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ruamel.yaml.clib >=0.1.2 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 256637 + timestamp: 1757164715902 +- conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml-0.17.17-py314h0612a62_4.conda + sha256: 2d2d7d3dcf5ef2c3e543794d2f43b109e7f3affec7c9d0d17370c89de19c9123 + md5: 62176b47946d999e084ccae88b4142e9 + depends: + - __osx >=11.0 + - python >=3.14.0rc2,<3.15.0a0 + - python >=3.14.0rc2,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + - ruamel.yaml.clib >=0.1.2 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 255936 + timestamp: 1757164815939 +- conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml-0.17.17-py314h5a2d7ad_4.conda + sha256: f313c788da41d77b15acd3c096a7c8763de45a93d5e6f47dcc844ac456639063 + md5: 9edc657959c912dd5f74d2dd24d801ad + depends: + - python >=3.14.0rc2,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ruamel.yaml.clib >=0.1.2 + - setuptools + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml?source=hash-mapping + size: 251902 + timestamp: 1757164619352 +- conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py314h5bd0f2a_0.conda + sha256: 12a6e9df8cfef6b24776071963b95fa8a6826f5f4879b89b922c661c72284152 + md5: 35b2429025596d2835b2f885f3829164 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 139813 + timestamp: 1760564353596 +- conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml.clib-0.2.14-py314h6482030_0.conda + sha256: 91ad0dea0a56f3e126ac1df73e1f08423ff1232710e2bcbd743246e6f397398e + md5: 5c4084370562febabef33ed1c32c6e2d + depends: + - __osx >=10.13 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 122386 + timestamp: 1760564663731 +- conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.14-py314h0612a62_0.conda + sha256: 403c389fcfc39f218b82d9e39d89f10271456e0dc006a271b8b83e43b7195261 + md5: b5ad747a92d9c9f846db5dc4b2ae88ae + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 116736 + timestamp: 1760564591292 +- conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml.clib-0.2.14-py314h5a2d7ad_0.conda + sha256: 3bc63a209808334e31c2ec39e5d6881df94cff53799d6a62c928d487dbf04009 + md5: 9161bd1efb3193f3e68a993138e339cd + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruamel-yaml-clib?source=hash-mapping + size: 105692 + timestamp: 1760564690504 +- conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + sha256: 972560fcf9657058e3e1f97186cc94389144b46dbdf58c807ce62e83f977e863 + md5: 4de79c071274a53dcaf2a8c749d1499e + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=hash-mapping + size: 748788 + timestamp: 1748804951958 +- conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + size: 18455 + timestamp: 1753199211006 +- conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda + sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 + md5: a0116df4f4ed05c303811a837d5b39d8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3285204 + timestamp: 1748387766691 +- conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda + sha256: b24468006a96b71a5f4372205ea7ec4b399b0f2a543541e86f883de54cd623fc + md5: 9864891a6946c2fe037c02fca7392ab4 + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3259809 + timestamp: 1748387843735 +- conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda + sha256: cb86c522576fa95c6db4c878849af0bccfd3264daf0cc40dd18e7f4a7bfced0e + md5: 7362396c170252e7b7b0c8fb37fe9c78 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3125538 + timestamp: 1748388189063 +- conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 + md5: ebd0e761de9aa879a51d22cc721bd095 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL + license_family: BSD + purls: [] + size: 3466348 + timestamp: 1748388121356 +- conda: https://prefix.dev/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + sha256: cb77c660b646c00a48ef942a9e1721ee46e90230c7c570cdeb5a893b5cce9bff + md5: d2732eb636c264dc9aa4cbee404b1a53 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=compressed-mapping + size: 20973 + timestamp: 1760014679845 +- conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a + license: LicenseRef-Public-Domain + purls: [] + size: 122968 + timestamp: 1742727099393 +- conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- conda: https://prefix.dev/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + sha256: 4fb9789154bd666ca74e428d973df81087a697dbb987775bc3198d2215f240f8 + md5: 436c165519e140cb08d246a4472a9d6a + depends: + - brotli-python >=1.0.9 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.9 + - zstandard >=0.18.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 101735 + timestamp: 1750271478254 +- conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + sha256: 82250af59af9ff3c6a635dd4c4764c631d854feb334d6747d356d949af44d7cf + md5: ef02bbe151253a72b8eda264a935db66 + depends: + - vc14_runtime >=14.42.34433 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18861 + timestamp: 1760418772353 +- conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + sha256: e3a3656b70d1202e0d042811ceb743bd0d9f7e00e2acdf824d231b044ef6c0fd + md5: 378d5dcec45eaea8d303da6f00447ac0 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_32 + constrains: + - vs2015_runtime 14.44.35208.* *_32 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 682706 + timestamp: 1760418629729 +- conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + sha256: f3790c88fbbdc55874f41de81a4237b1b91eab75e05d0e58661518ff04d2a8a1 + md5: 58f67b437acbf2764317ba273d731f1d + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_32 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 114846 + timestamp: 1760418593847 +- pypi: ./ + name: vinca + version: 0.1.0 + sha256: d2bc6544875054cbc0fee273120911dd4d686c31c52c8978364aa8f26c592fbb + requires_dist: + - catkin-pkg>=0.4.16 + - ruamel-yaml>=0.16.6,<0.18.0 + - rosdistro>=0.8.0 + - empy>=3.3.4,<4.0.0 + - requests>=2.24.0 + - networkx>=2.5 + - rich>=10 + - jinja2>=3.0.0 + - license-expression>=30.0.0 + requires_python: '>=3.6' + editable: true +- conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + purls: + - pkg:pypi/win-inet-pton?source=hash-mapping + size: 9555 + timestamp: 1733130678956 +- conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad + md5: a77f85f77be52ff59391544bfe73390a + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 85189 + timestamp: 1753484064210 +- conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda + sha256: a335161bfa57b64e6794c3c354e7d49449b28b8d8a7c4ed02bf04c3f009953f9 + md5: a645bb90997d3fc2aea0adf6517059bd + depends: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 79419 + timestamp: 1753484072608 +- conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac + md5: 78a0fe9e9c50d2c381e8ee47e3ea437d + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 83386 + timestamp: 1753484079473 +- conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 + md5: 433699cba6602098ae8957a323da2664 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + size: 63944 + timestamp: 1753484092156 +- conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.25.0-py314h31f8a6b_0.conda + sha256: ec4e66b4e042ea9554b9db92b509358f75390f7dcbafb8eead940a2880486a63 + md5: 68bd13651618354987763f746ee1fadc + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 127864 + timestamp: 1757930108791 +- conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.25.0-py314h12c88b1_0.conda + sha256: e66dbbac3e2b999336620d6d2f6ea21f234989e5672c1ba708b22f3483e91a5b + md5: 60b35d160b3095b1c754ee6567c43f6d + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - __osx >=10.13 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 123692 + timestamp: 1757930114277 +- conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.25.0-py314h163e31d_0.conda + sha256: 5b707d7b80d9b410fce776a439273213745ffc3fa4553ec31f264bbaf63a6ec6 + md5: c824d8cd887ce1d7af8963ca4087a764 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - __osx >=11.0 + - python 3.14.* *_cp314 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 125883 + timestamp: 1757930173407 +- conda: https://prefix.dev/conda-forge/win-64/zstandard-0.25.0-py314h4667ab5_0.conda + sha256: 285890e987cb14b2ac27f74a4ae844eb80e73654ab3321f085e13538c6ac7d23 + md5: 765406da1e9f31231a56ea4d2e191d7c + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 285971 + timestamp: 1757930137171 +- conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb + md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 567578 + timestamp: 1742433379869 +- conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + sha256: c171c43d0c47eed45085112cb00c8c7d4f0caa5a32d47f2daca727e45fb98dca + md5: cd60a4a5a8d6a476b30d8aa4bb49251a + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 485754 + timestamp: 1742433356230 +- conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + sha256: 0d02046f57f7a1a3feae3e9d1aa2113788311f3cf37a3244c71e61a93177ba67 + md5: e6f69c7bcccdefa417f056fa593b40f0 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 399979 + timestamp: 1742433432699 +- conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + sha256: bc64864377d809b904e877a98d0584f43836c9f2ef27d3d2a1421fa6eae7ca04 + md5: 21f56217d6125fb30c3c3f10c786d751 + depends: + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 354697 + timestamp: 1742433568506 diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..cffa0fa --- /dev/null +++ b/pixi.toml @@ -0,0 +1,35 @@ +[workspace] +channels = ["https://prefix.dev/conda-forge"] +name = "vinca" +platforms = ["win-64", "linux-64", "osx-arm64", "osx-64"] +version = "0.1.0" + +[tasks] +# Testing +test = "pytest vinca/" + +# Code quality +format = "black --safe --quiet ." +lint = "flake8 vinca/" + +[dependencies] +python = ">=3.14.0,<3.15" + +# Core dependencies +catkin_pkg = ">=0.4.16" +"ruamel.yaml" = ">=0.16.6,<0.18.0" +rosdistro = ">=0.8.0" +empy = ">=3.3.4,<4.0.0" +requests = ">=2.24.0" +networkx = ">=2.5" +rich = ">=10" +jinja2 = ">=3.0.0" +license-expression = ">=30.0.0" + +# Development dependencies +pytest = "*" +black = "*" +flake8 = "*" + +[pypi-dependencies] +vinca = { path = ".", editable = true} diff --git a/setup.cfg b/setup.cfg index ce690d8..8abdd72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ install_requires = networkx >=2.5 rich >=10 jinja2 >=3.0.0 + license-expression >=30.0.0 packages = find: zip_safe = false diff --git a/vinca/distro.py b/vinca/distro.py index 236c010..e27685a 100644 --- a/vinca/distro.py +++ b/vinca/distro.py @@ -7,7 +7,13 @@ class Distro(object): - def __init__(self, distro_name, python_version=None, snapshot=None, additional_packages_snapshot=None): + def __init__( + self, + distro_name, + python_version=None, + snapshot=None, + additional_packages_snapshot=None, + ): index = get_index(get_index_url()) self._distro = get_cached_distribution(index, distro_name) self.distro_name = distro_name @@ -49,20 +55,30 @@ def get_depends(self, pkg, ignore_pkgs=None): return dependencies # if pkg comes from additional_packages_snapshot, extract from its package.xml - if self.additional_packages_snapshot and pkg in self.additional_packages_snapshot: + if ( + self.additional_packages_snapshot + and pkg in self.additional_packages_snapshot + ): pkg_info = self.additional_packages_snapshot[pkg] xml_str = self.get_package_xml_for_additional_package(pkg_info) # parse XML import xml.etree.ElementTree as ET + root = ET.fromstring(xml_str) # collect direct dependencies tags from package.xml dep_tags = [ - 'depend', 'build_depend', 'buildtool_depend', 'buildtool_export_depend', - 'exec_depend', 'run_depend', 'test_depend', 'build_export_depend' + "depend", + "build_depend", + "buildtool_depend", + "buildtool_export_depend", + "exec_depend", + "run_depend", + "test_depend", + "build_export_depend", ] direct = set() for tag in dep_tags: - for elem in root.findall(f'.//{tag}'): + for elem in root.findall(f".//{tag}"): if elem.text: name = elem.text.strip() direct.add(name) @@ -111,18 +127,26 @@ def get_released_repo(self, pkg_name): pkg = self._distro.release_packages[pkg_name] repo = self._distro.repositories[pkg.repository_name].release_repository release_tag = get_release_tag(repo, pkg_name) - return repo.url, release_tag, 'tag' + return repo.url, release_tag, "tag" def check_package(self, pkg_name): # If the package is in the additional_packages_snapshot, it is always considered valid # even if it is not in the released packages, as it is an additional # package specified in rosdistro_additional_recipes.yaml - if self.additional_packages_snapshot and pkg_name in self.additional_packages_snapshot: + if ( + self.additional_packages_snapshot + and pkg_name in self.additional_packages_snapshot + ): return True # the .replace('_', '-') is needed for packages like 'hpp-fcl' that have hypen and not underscore # in the rosdistro metadata - if pkg_name in self._distro.release_packages or pkg_name.replace('_', '-') in self._distro.release_packages: - return self.snapshot is None or (pkg_name in self.snapshot or pkg_name.replace('_', '-') in self.snapshot) + if ( + pkg_name in self._distro.release_packages + or pkg_name.replace("_", "-") in self._distro.release_packages + ): + return self.snapshot is None or ( + pkg_name in self.snapshot or pkg_name.replace("_", "-") in self.snapshot + ) elif pkg_name in self.build_packages: return True else: @@ -137,7 +161,10 @@ def get_version(self, pkg_name): return repo.version.split("-")[0] def get_release_package_xml(self, pkg_name): - if self.additional_packages_snapshot and pkg_name in self.additional_packages_snapshot: + if ( + self.additional_packages_snapshot + and pkg_name in self.additional_packages_snapshot + ): pkg_info = self.additional_packages_snapshot[pkg_name] return self.get_package_xml_for_additional_package(pkg_info) return self._distro.get_release_package_xml(pkg_name) @@ -171,6 +198,6 @@ def get_package_xml_for_additional_package(self, pkg_info): raw_url = f"https://raw.githubusercontent.com/{owner_repo}/{ref}/{additional_folder}{xml_name}" try: with urllib.request.urlopen(raw_url) as resp: - return resp.read().decode('utf-8') + return resp.read().decode("utf-8") except Exception as e: raise RuntimeError(f"Failed to fetch package.xml from {raw_url}: {e}") diff --git a/vinca/generate_azure.py b/vinca/generate_azure.py index b1a2120..75b68e4 100644 --- a/vinca/generate_azure.py +++ b/vinca/generate_azure.py @@ -413,7 +413,12 @@ def get_full_tree(): config.selected_platform = get_conda_subdir() python_version = temp_vinca_conf.get("python_version", None) - distro = Distro(temp_vinca_conf["ros_distro"], python_version, temp_vinca_conf["_snapshot"], temp_vinca_conf["_additional_packages_snapshot"]) + distro = Distro( + temp_vinca_conf["ros_distro"], + python_version, + temp_vinca_conf["_snapshot"], + temp_vinca_conf["_additional_packages_snapshot"], + ) all_packages = get_selected_packages(distro, temp_vinca_conf) temp_vinca_conf["_selected_pkgs"] = all_packages diff --git a/vinca/generate_gha.py b/vinca/generate_gha.py index 694d8ed..ec9762d 100644 --- a/vinca/generate_gha.py +++ b/vinca/generate_gha.py @@ -327,7 +327,7 @@ def build_osx_pipeline( azure_template=None, script=azure_unix_script, target="osx-64", - pipeline_name="build_osx_64" + pipeline_name="build_osx_64", ): build_unix_pipeline( stages, @@ -337,7 +337,7 @@ def build_osx_pipeline( runs_on=vm_imagename, outfile=outfile, target=target, - pipeline_name=pipeline_name + pipeline_name=pipeline_name, ) @@ -428,7 +428,12 @@ def get_full_tree(): config.selected_platform = get_conda_subdir() python_version = temp_vinca_conf.get("python_version", None) - distro = Distro(temp_vinca_conf["ros_distro"], python_version, temp_vinca_conf["_snapshot"], temp_vinca_conf["_additional_packages_snapshot"]) + distro = Distro( + temp_vinca_conf["ros_distro"], + python_version, + temp_vinca_conf["_snapshot"], + temp_vinca_conf["_additional_packages_snapshot"], + ) all_packages = get_selected_packages(distro, temp_vinca_conf) temp_vinca_conf["_selected_pkgs"] = all_packages @@ -562,7 +567,7 @@ def main(): outfile="osx_arm64.yml", script=azure_unix_script, target=platform, - pipeline_name="build_osx_arm64" + pipeline_name="build_osx_arm64", ) if args.platform == "linux-aarch64": @@ -573,7 +578,7 @@ def main(): runs_on="ubuntu-24.04-arm", outfile="linux_aarch64.yml", target=platform, - pipeline_name="build_linux_aarch64" + pipeline_name="build_linux_aarch64", ) # windows diff --git a/vinca/license_utils.py b/vinca/license_utils.py new file mode 100644 index 0000000..38711bc --- /dev/null +++ b/vinca/license_utils.py @@ -0,0 +1,221 @@ +"""Utilities for converting ROS package licenses to SPDX format.""" + +import re +from typing import List, Optional, Dict +from license_expression import get_spdx_licensing, ExpressionError + +# Lookup table for common non-SPDX license strings to SPDX identifiers +# Note: Keys are lowercase for case-insensitive matching +# Note: Entries like BSD-2 and GPL are omitted as they're already normalized +# by license-expression (BSD-2 -> BSD-2-Clause, GPL -> GPL-1.0-or-later) +LICENSE_LOOKUP: Dict[str, str] = { + # BSD variants + "bsd": "BSD-3-Clause", + "bsd-3": "BSD-3-Clause", + "bsd 3-clause": "BSD-3-Clause", + "3-clause bsd": "BSD-3-Clause", + "bsd 2-clause": "BSD-2-Clause", + "bsd license 2.0": "BSD-2-Clause", + # Apache variants + "apache": "Apache-2.0", + "apache 2": "Apache-2.0", + "apache2": "Apache-2.0", + "apache2.0": "Apache-2.0", + "apache 2.0": "Apache-2.0", + "apache license 2.0": "Apache-2.0", + "apache license, version 2.0": "Apache-2.0", + # GPL variants + "gplv2": "GPL-2.0-only", + "gpl-2": "GPL-2.0-only", + "gpl-2.0": "GPL-2.0-only", + "gnu general public license v2.0": "GPL-2.0-only", + "gplv3": "GPL-3.0-only", + "gpl-3": "GPL-3.0-only", + "gpl-3.0": "GPL-3.0-only", + # AGPL variants + "agplv3": "AGPL-3.0-only", + "agpl-3": "AGPL-3.0-only", + "agpl-3.0": "AGPL-3.0-only", + # LGPL variants (note: defaulting to 2.1-or-later for ROS compatibility) + "lgpl": "LGPL-2.1-or-later", + "lgplv2": "LGPL-2.1-or-later", + "lgplv2.1": "LGPL-2.1-or-later", + "lgpl-2": "LGPL-2.1-or-later", + "lgpl-2.1": "LGPL-2.1-or-later", + "lgplv3": "LGPL-3.0-only", + "lgpl-3": "LGPL-3.0-only", + "lgpl-3.0": "LGPL-3.0-only", + # Mozilla/MPL variants + "mozilla": "MPL-2.0", + "mpl": "MPL-2.0", + "mozilla public license version 1.1": "MPL-1.1", + # Eclipse variants + "eclipse public license 2.0": "EPL-2.0", + "eclipse distribution license 1.0": "EDL-1.0", + # Boost variants + "boost": "BSL-1.0", + "boost software license": "BSL-1.0", + "bsl": "BSL-1.0", + # MIT variants + "mit license": "MIT", + # Zlib variants + "zlib license": "Zlib", + # Creative Commons + "cc by-nc-sa 4.0": "CC-BY-NC-SA-4.0", + "creative commons zero v1.0 universal": "CC0-1.0", + # Public Domain (choosing Unlicense as more appropriate for code) + "public domain": "Unlicense", +} + + +def _process_single_license( + lic_str: str, package_name: Optional[str] = None +) -> Optional[str]: + """Process a single license string and convert to SPDX. + + Args: + lic_str: A single license string + package_name: Optional package name for warning messages + + Returns: + SPDX license identifier or None + """ + # Try to validate and get normalized SPDX expression + validation_result = validate_and_normalize_license(lic_str) + + if validation_result: + # Valid SPDX license, use normalized form + return validation_result + # Check if it's in the lookup table (case-insensitive) + elif lic_str.lower() in LICENSE_LOOKUP: + return LICENSE_LOOKUP[lic_str.lower()] + + # Not found in lookup table either - use LicenseRef- + # Clean up the license string for use in LicenseRef + clean_lic = re.sub(r"[^A-Za-z0-9.\-]", "-", lic_str) + license_ref = f"LicenseRef-{clean_lic}" + pkg_info = f" in package '{package_name}'" if package_name else "" + print( + f"Warning: License '{lic_str}'{pkg_info} is not a " + f"recognized SPDX identifier. Using '{license_ref}' instead." + ) + return license_ref + + +def convert_to_spdx_license( + licenses: List[str], package_name: Optional[str] = None +) -> Optional[str]: + """Convert ROS package licenses to SPDX format. + + Args: + licenses: A list of license strings from catkin_pkg package.licenses + package_name: Optional package name to include in warning messages + + Returns: + A SPDX license expression string, or None if no valid license found. + + Rules: + - If license is "TODO", return None (don't add license) + - If license is valid SPDX, use as-is + - If license is not recognized, prefix with "LicenseRef-" + - Multiple licenses are combined with " OR " + """ + if not licenses: + return None + + spdx_licenses: List[str] = [] + + for lic in licenses: + lic_str = str(lic).strip() + + # Skip TODO and empty licenses + if not lic_str or lic_str.upper() == "TODO": + continue + + # Check lookup table first (before splitting on commas) + # This handles cases like "Apache License, Version 2.0" + if lic_str.lower() in LICENSE_LOOKUP: + spdx_licenses.append(LICENSE_LOOKUP[lic_str.lower()]) + continue + + # Check if license can be normalized by SPDX + validation_result = validate_and_normalize_license(lic_str) + if validation_result: + spdx_licenses.append(validation_result) + continue + + # Check if license contains comma (multiple licenses in one string) + if "," in lic_str: + # Split by comma and process each part + # Filter out empty strings from malformed input + # (trailing/double commas) + parts = [ + part.strip() for part in lic_str.split(",") if part.strip() + ] + sub_licenses = [] + seen_sub = set() + for part in parts: + result = _process_single_license(part, package_name) + if result and result not in seen_sub: + seen_sub.add(result) + sub_licenses.append(result) + if sub_licenses: + # Join with OR for comma-separated licenses (dual-licensed) + spdx_licenses.append(" OR ".join(sub_licenses)) + continue + + # Process single license (fallback for LicenseRef) + result = _process_single_license(lic_str, package_name) + if result: + spdx_licenses.append(result) + + if not spdx_licenses: + return None + + # Deduplicate licenses while preserving order + seen = set() + unique_licenses = [] + for lic in spdx_licenses: + if lic not in seen: + seen.add(lic) + unique_licenses.append(lic) + + # Combine multiple licenses with OR + return " OR ".join(unique_licenses) + + +def validate_and_normalize_license(license_string: str) -> Optional[str]: + """Validate and normalize a license string to SPDX format. + + Args: + license_string: A license identifier string + + Returns: + The normalized SPDX identifier if valid, None otherwise + """ + if not license_string: + return None + + licensing = get_spdx_licensing() + + try: + # Use the validate method to check if the license expression is valid + validation_result = licensing.validate(license_string) + # If there are no validation errors, return the normalized expression + if len(validation_result.errors) == 0: + return validation_result.normalized_expression + return None + except (ExpressionError, Exception): + return None + + +def is_valid_spdx_license(license_string: str) -> bool: + """Check if a license string is a valid SPDX identifier. + + Args: + license_string: A license identifier string + + Returns: + True if the license is a valid SPDX identifier, False otherwise + """ + return validate_and_normalize_license(license_string) is not None diff --git a/vinca/main.py b/vinca/main.py index 375d38f..bdb065b 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -17,7 +17,13 @@ from .v1_selectors import evaluate_selectors from vinca import config -from vinca.utils import get_repodata, get_pkg_build_number, get_pkg_additional_info, is_dummy_metapackage +from vinca.utils import ( + get_repodata, + get_pkg_build_number, + get_pkg_additional_info, + is_dummy_metapackage, +) +from vinca.license_utils import convert_to_spdx_license unsatisfied_deps = set() distro = None @@ -160,7 +166,9 @@ def get_depmods(vinca_conf, pkg_name): def read_vinca_yaml(filepath): yaml = ruamel.yaml.YAML() - vinca_conf = evaluate_selectors(yaml.load(open(filepath, "r")), target_platform=get_conda_subdir()) + vinca_conf = evaluate_selectors( + yaml.load(open(filepath, "r")), target_platform=get_conda_subdir() + ) # normalize paths to absolute paths conda_index = [] @@ -178,9 +186,15 @@ def read_vinca_yaml(filepath): for x in glob.glob(os.path.join(vinca_conf["_patch_dir"], "*.patch")): splitted = os.path.basename(x).split(".") if splitted[0] not in patches: - patches[splitted[0]] = {"any": [], "osx": [], "linux": [], "win": [], "emscripten": []} + patches[splitted[0]] = { + "any": [], + "osx": [], + "linux": [], + "win": [], + "emscripten": [], + } if len(splitted) == 3: - if splitted[1] in ("osx", "linux", "win" , "emscripten"): + if splitted[1] in ("osx", "linux", "win", "emscripten"): patches[splitted[0]][splitted[1]].append(x) continue if splitted[1] == "unix": @@ -201,7 +215,10 @@ def read_vinca_yaml(filepath): vinca_conf["_tests"] = tests if (patch_dir / "dependencies.yaml").exists(): - vinca_conf["depmods"] = evaluate_selectors(yaml.load(open(patch_dir / "dependencies.yaml")), target_platform=get_conda_subdir()) + vinca_conf["depmods"] = evaluate_selectors( + yaml.load(open(patch_dir / "dependencies.yaml")), + target_platform=get_conda_subdir(), + ) if not vinca_conf.get("depmods"): vinca_conf["depmods"] = {} @@ -215,7 +232,10 @@ def read_vinca_yaml(filepath): vinca_conf["trigger_new_versions"] = vinca_conf.get("trigger_new_versions", False) if (Path(filepath).parent / "pkg_additional_info.yaml").exists(): - vinca_conf["_pkg_additional_info"] = evaluate_selectors(yaml.load(open(Path(filepath).parent / "pkg_additional_info.yaml")), target_platform=get_conda_subdir()) + vinca_conf["_pkg_additional_info"] = evaluate_selectors( + yaml.load(open(Path(filepath).parent / "pkg_additional_info.yaml")), + target_platform=get_conda_subdir(), + ) else: vinca_conf["_pkg_additional_info"] = {} @@ -232,7 +252,7 @@ def read_vinca_yaml(filepath): def read_snapshot(vinca_conf): if not "rosdistro_snapshot" in vinca_conf: - return None , None + return None, None yaml = ruamel.yaml.YAML() # load primary snapshot @@ -290,8 +310,8 @@ def generate_output(pkg_shortname, vinca_conf, distro, version, all_pkgs=None): # Use override_version if specified, otherwise use the ROS package version dummy_package_version = gen.get("override_version", version) lower = dummy_package_version - parts = [int(p) for p in lower.split('.')] - seg = len(upper_bound.split('.')) + parts = [int(p) for p in lower.split(".")] + seg = len(upper_bound.split(".")) upper_parts = parts[:seg] upper_parts[-1] += 1 upper_parts += [0] * (len(parts) - seg) @@ -299,7 +319,12 @@ def generate_output(pkg_shortname, vinca_conf, distro, version, all_pkgs=None): constraint = f"{dep_name} >={lower}, <{upper}" output = { "package": {"name": pkg_names[0], "version": dummy_package_version}, - "build": {"number": get_pkg_build_number(vinca_conf.get("build_number", 0), pkg_names[0], vinca_conf), "script": ""}, + "build": { + "number": get_pkg_build_number( + vinca_conf.get("build_number", 0), pkg_names[0], vinca_conf + ), + "script": "", + }, "requirements": {"build": [], "host": [], "run": [constraint]}, } else: @@ -310,7 +335,10 @@ def generate_output(pkg_shortname, vinca_conf, distro, version, all_pkgs=None): "build": [ "${{ compiler('cxx') }}", "${{ compiler('c') }}", - {"if": "target_platform!='emscripten-wasm32'", "then": ["${{ stdlib('c') }}"]}, + { + "if": "target_platform!='emscripten-wasm32'", + "then": ["${{ stdlib('c') }}"], + }, "ninja", "python", "setuptools", @@ -322,7 +350,14 @@ def generate_output(pkg_shortname, vinca_conf, distro, version, all_pkgs=None): {"if": "build_platform != target_platform", "then": ["pkg-config"]}, "cmake", "cython", - {"if": "build_platform != target_platform", "then": ["python", "cross-python_${{ target_platform }}", "numpy"]}, + { + "if": "build_platform != target_platform", + "then": [ + "python", + "cross-python_${{ target_platform }}", + "numpy", + ], + }, ], "host": [ {"if": "build_platform == target_platform", "then": ["pkg-config"]}, @@ -334,14 +369,18 @@ def generate_output(pkg_shortname, vinca_conf, distro, version, all_pkgs=None): }, "build": {"script": ""}, } - + xml = distro.get_release_package_xml(pkg_shortname) # If the snapshot is not aligned with the latest rosdistro (for example if a package is removed, # see https://github.com/RoboStack/ros-jazzy/pull/107#issuecomment-3338962041), get_release_package_xml can return none, - # in that case we can just skip the package, remove if https://github.com/RoboStack/vinca/issues/93 is fixed + # in that case we can just skip the package, remove if https://github.com/RoboStack/vinca/issues/93 is fixed if not xml: - print("Skip " + pkg_shortname + " as it is present in our snapshot, but not in the latest rosdistro cache.") + print( + "Skip " + + pkg_shortname + + " as it is present in our snapshot, but not in the latest rosdistro cache." + ) return None pkg = catkin_pkg.package.parse_package_string(xml) @@ -479,7 +518,9 @@ def sortkey(k): # This should be ok as cmake is only really needed during builds, not when running packages. if "cmake" in output["requirements"]["run"]: output["requirements"]["run"].remove("cmake") - output["requirements"]["run"].append({"if": "target_platform != 'emscripten-wasm32'", "then": ["cmake"]}) + output["requirements"]["run"].append( + {"if": "target_platform != 'emscripten-wasm32'", "then": ["cmake"]} + ) if "cmake" in output["requirements"]["host"]: output["requirements"]["host"].remove("cmake") @@ -488,14 +529,32 @@ def sortkey(k): if f"ros-{config.ros_distro}-mimick-vendor" in output["requirements"]["build"]: output["requirements"]["build"].remove(f"ros-{config.ros_distro}-mimick-vendor") - output["requirements"]["build"].append({"if": "target_platform != 'emscripten-wasm32'", "then": [f"ros-{config.ros_distro}-mimick-vendor"]}) + output["requirements"]["build"].append( + { + "if": "target_platform != 'emscripten-wasm32'", + "then": [f"ros-{config.ros_distro}-mimick-vendor"], + } + ) if f"ros-{config.ros_distro}-mimick-vendor" in output["requirements"]["host"]: output["requirements"]["host"].remove(f"ros-{config.ros_distro}-mimick-vendor") - output["requirements"]["build"].append({"if": "target_platform != 'emscripten-wasm32'", "then": [f"ros-{config.ros_distro}-mimick-vendor"]}) + output["requirements"]["build"].append( + { + "if": "target_platform != 'emscripten-wasm32'", + "then": [f"ros-{config.ros_distro}-mimick-vendor"], + } + ) - if f"ros-{config.ros_distro}-rosidl-default-generators" in output["requirements"]["host"]: - output["requirements"]["build"].append({"if": "target_platform == 'emscripten-wasm32'", "then": [f"ros-{config.ros_distro}-rosidl-default-generators"]}) + if ( + f"ros-{config.ros_distro}-rosidl-default-generators" + in output["requirements"]["host"] + ): + output["requirements"]["build"].append( + { + "if": "target_platform == 'emscripten-wasm32'", + "then": [f"ros-{config.ros_distro}-rosidl-default-generators"], + } + ) output["requirements"]["run"] = sorted(output["requirements"]["run"], key=sortkey) output["requirements"]["host"] = sorted(output["requirements"]["host"], key=sortkey) @@ -569,9 +628,7 @@ def sortkey(k): output["requirements"]["host"] += [ { "if": "linux", - "then": [ - "libgl-devel" - ], + "then": ["libgl-devel"], } ] @@ -585,6 +642,28 @@ def sortkey(k): ] output["requirements"][dep_type] = tmp_nonduplicate + # Add "about" section with license and package metadata + output["about"] = {} + + # Add URLs from package.xml based on their type + for u in pkg.urls: + if u.type == "website": + output["about"]["homepage"] = u.url + elif u.type == "repository": + output["about"]["repository"] = u.url + + # Add license if available (convert to SPDX format) + if pkg.licenses: + spdx_license = convert_to_spdx_license( + [str(lic) for lic in pkg.licenses], package_name=pkg_shortname + ) + if spdx_license: + output["about"]["license"] = spdx_license + + # Add summary/description if available + if pkg.description: + output["about"]["summary"] = pkg.description + return output @@ -622,8 +701,8 @@ def get_pkg(pkg_name): mutex_recipe = generate_mutex_package_recipe(vinca_conf, distro) if mutex_recipe: # Check if mutex package should be skipped - mutex_name = mutex_recipe['package']['name'] - mutex_version = mutex_recipe['package']['version'] + mutex_name = mutex_recipe["package"]["name"] + mutex_version = mutex_recipe["package"]["version"] if should_skip_mutex_package(vinca_conf, mutex_name, mutex_version): print(f"Skipping mutex package {mutex_name} (already built)") @@ -696,8 +775,8 @@ def generate_source(distro, vinca_conf): mutex_recipe = generate_mutex_package_recipe(vinca_conf, distro) if mutex_recipe: # Check if mutex package should be skipped - mutex_name = mutex_recipe['package']['name'] - mutex_version = mutex_recipe['package']['version'] + mutex_name = mutex_recipe["package"]["name"] + mutex_version = mutex_recipe["package"]["version"] if not should_skip_mutex_package(vinca_conf, mutex_name, mutex_version): source[mutex_name] = {} @@ -778,8 +857,13 @@ def get_selected_packages(distro, vinca_conf): if vinca_conf.get("build_all", False): selected_packages = set(distro._distro.release_packages.keys()) # Add packages from rosdistro_additional_recipes.yaml when build_all is True - if "_additional_packages_snapshot" in vinca_conf and vinca_conf["_additional_packages_snapshot"]: - additional_packages = set(vinca_conf["_additional_packages_snapshot"].keys()) + if ( + "_additional_packages_snapshot" in vinca_conf + and vinca_conf["_additional_packages_snapshot"] + ): + additional_packages = set( + vinca_conf["_additional_packages_snapshot"].keys() + ) selected_packages = selected_packages.union(additional_packages) elif vinca_conf["packages_select_by_deps"]: @@ -810,8 +894,16 @@ def get_selected_packages(distro, vinca_conf): # if any ROS2 packages are selected (these are added as dependencies automatically) if not distro.check_ros1() and selected_packages: # Check if we have any ROS packages selected (excluding the workspace/environment packages themselves) - has_ros_packages = any(pkg not in ["ros_workspace", "ros_environment", "ament_cmake_core", "ament_package"] - for pkg in selected_packages) + has_ros_packages = any( + pkg + not in [ + "ros_workspace", + "ros_environment", + "ament_cmake_core", + "ament_package", + ] + for pkg in selected_packages + ) if has_ros_packages: if distro.check_package("ros_workspace"): selected_packages.add("ros_workspace") @@ -845,7 +937,9 @@ def parse_mutex_package_config(vinca_conf): missing_fields = [field for field in required_fields if field not in mutex_pkg] if missing_fields: - raise ValueError(f"mutex_package configuration is missing required fields: {missing_fields}") + raise ValueError( + f"mutex_package configuration is missing required fields: {missing_fields}" + ) # Return validated config with build_number from vinca_conf if not specified config = dict(mutex_pkg) @@ -854,7 +948,9 @@ def parse_mutex_package_config(vinca_conf): return config - raise ValueError(f"mutex_package must be either a string or a dictionary, got {type(mutex_pkg)}") + raise ValueError( + f"mutex_package must be either a string or a dictionary, got {type(mutex_pkg)}" + ) def get_mutex_package_dependency(vinca_conf, distro): @@ -876,12 +972,12 @@ def get_mutex_package_dependency(vinca_conf, distro): # New format: construct the dependency string # Compute the pin from version and upper_bound - version_parts = config['version'].split('.') - upper_bound_parts = config['upper_bound'].split('.') + version_parts = config["version"].split(".") + upper_bound_parts = config["upper_bound"].split(".") # Take as many version parts as specified by upper_bound - pin_parts = version_parts[:len(upper_bound_parts)] - pin = '.'.join(pin_parts) + '.*' + pin_parts = version_parts[: len(upper_bound_parts)] + pin = ".".join(pin_parts) + ".*" return f"{config['name']} {pin} {distro.name}_*" except ValueError as e: @@ -912,25 +1008,24 @@ def generate_mutex_package_recipe(vinca_conf, distro): build_string = f"{distro.name}_{config['build_number']}" recipe = { - "package": { - "name": config["name"], - "version": config["version"] - }, + "package": {"name": config["name"], "version": config["version"]}, "build": { "number": config["build_number"], "string": build_string, - "script": "" + "script": "", }, "requirements": { "run_constraints": config["run_constraints"], "run_exports": { - "weak": [f"${{{{ pin_subpackage('{config['name']}', upper_bound='{config['upper_bound']}') }}}}"] - } + "weak": [ + f"${{{{ pin_subpackage('{config['name']}', upper_bound='{config['upper_bound']}') }}}}" + ] + }, }, "about": { "homepage": f"https://github.com/robostack/ros-{distro.name}", "license": "BSD-3-Clause", - "summary": f"The ROS2 distro mutex. To switch between ROS2 versions, you need to change the mutex.\nE.g. mamba install {config['name']}=*={distro.name} to switch to {distro.name}." + "summary": f"The ROS2 distro mutex. To switch between ROS2 versions, you need to change the mutex.\nE.g. mamba install {config['name']}=*={distro.name} to switch to {distro.name}.", }, } @@ -959,7 +1054,10 @@ def parse_package(pkg, distro, vinca_conf, path): "build": [ "${{ compiler('cxx') }}", "${{ compiler('c') }}", - {"if": "target_platform!='emscripten-wasm32'", "then": ["${{ stdlib('c') }}"]}, + { + "if": "target_platform!='emscripten-wasm32'", + "then": ["${{ stdlib('c') }}"], + }, "ninja", "python", "patch", @@ -1105,7 +1203,9 @@ def main(): if arguments.trigger_new_versions: vinca_conf["trigger_new_versions"] = True else: - vinca_conf["trigger_new_versions"] = vinca_conf.get("trigger_new_versions", False) + vinca_conf["trigger_new_versions"] = vinca_conf.get( + "trigger_new_versions", False + ) if arguments.package: pkg_files = glob.glob(arguments.package) @@ -1114,7 +1214,12 @@ def main(): if "python_version" in vinca_conf: python_version = vinca_conf["python_version"] - distro = Distro(vinca_conf["ros_distro"], python_version, vinca_conf["_snapshot"], vinca_conf["_additional_packages_snapshot"]) + distro = Distro( + vinca_conf["ros_distro"], + python_version, + vinca_conf["_snapshot"], + vinca_conf["_additional_packages_snapshot"], + ) additional_pkgs, parsed_pkgs = [], [] for f in pkg_files: parsed_pkg = catkin_pkg.package.parse_package(f) @@ -1154,10 +1259,15 @@ def main(): ): with open(add_rec) as fi: add_rec_y = yaml.load(fi) - if config.parsed_args.platform == 'emscripten-wasm32': + if config.parsed_args.platform == "emscripten-wasm32": additional_recipe_names.add(add_rec_y["package"]["name"]) else: - if add_rec_y["package"]["name"] not in ["ros-humble-rmw-wasm-cpp", "ros-humble-wasm-cpp", "ros-humble-dynmsg", "ros-humble-test-wasm"]: + if add_rec_y["package"]["name"] not in [ + "ros-humble-rmw-wasm-cpp", + "ros-humble-wasm-cpp", + "ros-humble-dynmsg", + "ros-humble-test-wasm", + ]: additional_recipe_names.add(add_rec_y["package"]["name"]) print("Found additional recipes: ", additional_recipe_names) @@ -1182,7 +1292,9 @@ def main(): for _, pkg in all_pkgs.items(): is_built = False if selected_bn is not None: - pkg_build_number = get_pkg_build_number(selected_bn, pkg["name"], vinca_conf) + pkg_build_number = get_pkg_build_number( + selected_bn, pkg["name"], vinca_conf + ) if pkg["build_number"] == pkg_build_number: is_built = True else: @@ -1203,7 +1315,12 @@ def main(): if "python_version" in vinca_conf: python_version = vinca_conf["python_version"] - distro = Distro(vinca_conf["ros_distro"], python_version, vinca_conf["_snapshot"], vinca_conf["_additional_packages_snapshot"]) + distro = Distro( + vinca_conf["ros_distro"], + python_version, + vinca_conf["_snapshot"], + vinca_conf["_additional_packages_snapshot"], + ) selected_pkgs = get_selected_packages(distro, vinca_conf) diff --git a/vinca/snapshot.py b/vinca/snapshot.py index 93f7900..28793fd 100644 --- a/vinca/snapshot.py +++ b/vinca/snapshot.py @@ -69,7 +69,11 @@ def main(): url, tag, _ = distro.get_released_repo(dep) version = distro.get_version(dep) except AttributeError: - print("\033[93mPackage '{}' has no version set, skipping...\033[0m".format(dep)) + print( + "\033[93mPackage '{}' has no version set, skipping...\033[0m".format( + dep + ) + ) continue output[dep] = {"url": url, "version": version, "tag": tag} @@ -78,5 +82,7 @@ def main(): print("{0:{2}} {1}".format(dep, version, max_len + 2)) with open(args.output, "w") as f: - f.write(f"# Snapshot generated by vinca-snapshot on {utc_time} UTC for distro {args.distro}\n") + f.write( + f"# Snapshot generated by vinca-snapshot on {utc_time} UTC for distro {args.distro}\n" + ) yaml.dump(output, f) diff --git a/vinca/template.py b/vinca/template.py index 00813ac..fa30d47 100644 --- a/vinca/template.py +++ b/vinca/template.py @@ -62,15 +62,20 @@ def write_recipe_package(recipe): with open(recipe_path, "w") as stream: file.dump(recipe, stream) + def copyfile_with_exec_permissions(source_file, destination_file): shutil.copyfile(source_file, destination_file) # It seems that rattler-build requires script to have executable permissions - if os.name == 'posix': + if os.name == "posix": # Retrieve current permissions current_permissions = os.stat(destination_file).st_mode # Set executable permissions for user, group, and others - os.chmod(destination_file, current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + os.chmod( + destination_file, + current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH, + ) + def write_recipe(source, outputs, vinca_conf, single_file=True): # single_file = False @@ -109,7 +114,9 @@ def write_recipe(source, outputs, vinca_conf, single_file=True): meta["package"]["name"] = o["package"]["name"] meta["package"]["version"] = o["package"]["version"] - meta["build"]["number"] = get_pkg_build_number(vinca_conf.get("build_number", 0), o["package"]["name"], vinca_conf) + meta["build"]["number"] = get_pkg_build_number( + vinca_conf.get("build_number", 0), o["package"]["name"], vinca_conf + ) meta["build"]["post_process"] = post_process_items if test := vinca_conf["_tests"].get(o["package"]["name"]): @@ -150,38 +157,62 @@ def write_recipe(source, outputs, vinca_conf, single_file=True): build_scripts = re.findall(r"'(.*?)'", meta["build"]["script"]) for script in build_scripts: - script_filename = script.replace("$RECIPE_DIR", "").replace("%RECIPE_DIR%", "").replace("/", "").replace("\\", "") + script_filename = ( + script.replace("$RECIPE_DIR", "") + .replace("%RECIPE_DIR%", "") + .replace("/", "") + .replace("\\", "") + ) # Generate the build script directly in the recipe directory # Get additional CMake arguments from pkg_additional_info - from vinca.utils import get_pkg_additional_info, ensure_name_is_without_distro_prefix_and_with_underscores + from vinca.utils import ( + get_pkg_additional_info, + ensure_name_is_without_distro_prefix_and_with_underscores, + ) + pkg_name = o["package"]["name"] # Use the proper utility function to normalize the package name - pkg_shortname = ensure_name_is_without_distro_prefix_and_with_underscores(pkg_name, vinca_conf) + pkg_shortname = ( + ensure_name_is_without_distro_prefix_and_with_underscores( + pkg_name, vinca_conf + ) + ) additional_cmake_args = "" additional_folder = "" if pkg_shortname: - pkg_additional_info = get_pkg_additional_info(pkg_shortname, vinca_conf) - additional_cmake_args = pkg_additional_info.get("additional_cmake_args", "") + pkg_additional_info = get_pkg_additional_info( + pkg_shortname, vinca_conf + ) + additional_cmake_args = pkg_additional_info.get( + "additional_cmake_args", "" + ) # Check if this package has folder info from additional_packages_snapshot - if (vinca_conf.get("_additional_packages_snapshot") and - pkg_shortname in vinca_conf["_additional_packages_snapshot"]): - additional_folder = vinca_conf["_additional_packages_snapshot"][pkg_shortname].get("additional_folder", "") - - generate_build_script_for_recipe(script_filename, recipe_dir / script_filename, additional_cmake_args, additional_folder) + if ( + vinca_conf.get("_additional_packages_snapshot") + and pkg_shortname in vinca_conf["_additional_packages_snapshot"] + ): + additional_folder = vinca_conf["_additional_packages_snapshot"][ + pkg_shortname + ].get("additional_folder", "") + + generate_build_script_for_recipe( + script_filename, + recipe_dir / script_filename, + additional_cmake_args, + additional_folder, + ) if "catkin" in o["package"]["name"] or "workspace" in o["package"]["name"]: # Generate activation scripts directly in the recipe directory generate_activation_scripts_for_recipe(recipe_dir) + def generate_template(template_in, template_out, extra_globals=None): import em from vinca.config import skip_testing, ros_distro - g = { - "ros_distro": ros_distro, - "skip_testing": "ON" if skip_testing else "OFF" - } + g = {"ros_distro": ros_distro, "skip_testing": "ON" if skip_testing else "OFF"} # Merge additional global variables if provided if extra_globals: @@ -196,13 +227,19 @@ def generate_template(template_in, template_out, extra_globals=None): # It seems that rattler-build requires script to have executable permissions # See https://github.com/RoboStack/ros-humble/pull/229#issuecomment-2549988298 - if os.name == 'posix': + if os.name == "posix": # Retrieve current permissions current_permissions = os.stat(template_out.name).st_mode # Set executable permissions for user, group, and others - os.chmod(template_out.name, current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + os.chmod( + template_out.name, + current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH, + ) -def generate_build_script_for_recipe(script_name, output_path, additional_cmake_args="", additional_folder=""): + +def generate_build_script_for_recipe( + script_name, output_path, additional_cmake_args="", additional_folder="" +): """Generate a specific build script directly in the recipe directory.""" import pkg_resources @@ -215,11 +252,13 @@ def generate_build_script_for_recipe(script_name, output_path, additional_cmake_ "build_catkin.sh": "templates/build_catkin.sh.in", "bld_catkin.bat": "templates/bld_catkin.bat.in", "bld_colcon_merge.bat": "templates/bld_colcon_merge.bat.in", - "bld_catkin_merge.bat": "templates/bld_catkin_merge.bat.in" + "bld_catkin_merge.bat": "templates/bld_catkin_merge.bat.in", } if script_name in script_templates: - template_in = pkg_resources.resource_filename("vinca", script_templates[script_name]) + template_in = pkg_resources.resource_filename( + "vinca", script_templates[script_name] + ) with open(output_path, "w") as output_file: extra_globals = {} if additional_cmake_args: @@ -233,12 +272,16 @@ def generate_build_script_for_recipe(script_name, output_path, additional_cmake_ generate_template(template_in, output_file, extra_globals) # Set executable permissions on Unix systems - if os.name == 'posix' and script_name.endswith('.sh'): + if os.name == "posix" and script_name.endswith(".sh"): current_permissions = os.stat(output_path).st_mode - os.chmod(output_path, current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + os.chmod( + output_path, + current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH, + ) else: print(f"Warning: Unknown build script template for {script_name}") + def generate_activation_scripts_for_recipe(recipe_dir): """Generate activation scripts directly in the recipe directory.""" import pkg_resources @@ -249,16 +292,21 @@ def generate_activation_scripts_for_recipe(recipe_dir): "activate.ps1": "templates/activate.ps1.in", "deactivate.sh": "templates/deactivate.sh.in", "deactivate.bat": "templates/deactivate.bat.in", - "deactivate.ps1": "templates/deactivate.ps1.in" + "deactivate.ps1": "templates/deactivate.ps1.in", } for script_name, template_path in activation_templates.items(): template_in = pkg_resources.resource_filename("vinca", template_path) output_path = recipe_dir / script_name with open(output_path, "w") as output_file: - generate_template(template_in, output_file) # No extra globals needed for activation scripts + generate_template( + template_in, output_file + ) # No extra globals needed for activation scripts # Set executable permissions on Unix systems for shell scripts - if os.name == 'posix' and script_name.endswith('.sh'): + if os.name == "posix" and script_name.endswith(".sh"): current_permissions = os.stat(output_path).st_mode - os.chmod(output_path, current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + os.chmod( + output_path, + current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH, + ) diff --git a/vinca/test_license_utils.py b/vinca/test_license_utils.py new file mode 100644 index 0000000..5bb6f04 --- /dev/null +++ b/vinca/test_license_utils.py @@ -0,0 +1,270 @@ +from vinca.license_utils import convert_to_spdx_license, is_valid_spdx_license + + +# Tests for is_valid_spdx_license function + + +def test_valid_common_licenses(): + """Test common valid SPDX licenses.""" + assert is_valid_spdx_license("MIT") + assert is_valid_spdx_license("Apache-2.0") + assert is_valid_spdx_license("BSD-3-Clause") + assert is_valid_spdx_license("GPL-3.0-only") + assert is_valid_spdx_license("LGPL-2.1-or-later") + + +def test_invalid_licenses(): + """Test invalid/non-standard license strings.""" + assert not is_valid_spdx_license("Proprietary") + assert not is_valid_spdx_license("Custom License") + assert not is_valid_spdx_license("TODO") + assert not is_valid_spdx_license("Unknown") + + +def test_empty_string(): + """Test empty string returns False.""" + assert not is_valid_spdx_license("") + + +# Tests for convert_to_spdx_license function + + +def test_empty_list(): + """Test empty list returns None.""" + assert convert_to_spdx_license([]) is None + + +def test_single_valid_license(): + """Test single valid SPDX license.""" + assert convert_to_spdx_license(["MIT"]) == "MIT" + assert convert_to_spdx_license(["Apache-2.0"]) == "Apache-2.0" + assert convert_to_spdx_license(["BSD-3-Clause"]) == "BSD-3-Clause" + + +def test_single_invalid_license(): + """Test single invalid license gets LicenseRef- prefix.""" + result = convert_to_spdx_license(["Proprietary"]) + assert result == "LicenseRef-Proprietary" + result = convert_to_spdx_license(["Custom License"]) + assert result == "LicenseRef-Custom-License" + + +def test_todo_license_ignored(): + """Test TODO license is skipped.""" + assert convert_to_spdx_license(["TODO"]) is None + assert convert_to_spdx_license(["todo"]) is None + assert convert_to_spdx_license(["Todo"]) is None + + +def test_multiple_valid_licenses(): + """Test multiple valid licenses are combined with OR.""" + result = convert_to_spdx_license(["MIT", "Apache-2.0"]) + assert result == "MIT OR Apache-2.0" + + result = convert_to_spdx_license(["BSD-3-Clause", "GPL-3.0-only"]) + assert result == "BSD-3-Clause OR GPL-3.0-only" + + +def test_multiple_invalid_licenses(): + """Test multiple invalid licenses get LicenseRef- prefix and OR.""" + result = convert_to_spdx_license(["Custom", "Proprietary"]) + assert result == "LicenseRef-Custom OR LicenseRef-Proprietary" + + +def test_mixed_valid_invalid_licenses(): + """Test mix of valid and invalid licenses.""" + result = convert_to_spdx_license(["MIT", "Custom"]) + assert result == "MIT OR LicenseRef-Custom" + + +def test_todo_with_valid_licenses(): + """Test TODO is filtered out when combined with valid licenses.""" + result = convert_to_spdx_license(["TODO", "MIT"]) + assert result == "MIT" + + result = convert_to_spdx_license(["MIT", "TODO", "Apache-2.0"]) + assert result == "MIT OR Apache-2.0" + + +def test_all_todos(): + """Test list of only TODOs returns None.""" + assert convert_to_spdx_license(["TODO", "todo", "Todo"]) is None + + +def test_license_with_spaces_converted(): + """Test licenses with spaces have spaces replaced with hyphens.""" + result = convert_to_spdx_license(["Creative Commons"]) + assert result == "LicenseRef-Creative-Commons" + + +def test_license_with_slashes_converted(): + """Test licenses with slashes have slashes replaced with hyphens.""" + result = convert_to_spdx_license(["BSD/MIT"]) + assert result == "LicenseRef-BSD-MIT" + + +def test_whitespace_stripped(): + """Test leading/trailing whitespace is stripped.""" + assert convert_to_spdx_license([" MIT "]) == "MIT" + assert convert_to_spdx_license([" Apache-2.0 "]) == "Apache-2.0" + + +def test_license_lookup_bsd(): + """Test BSD license is mapped to BSD-3-Clause.""" + assert convert_to_spdx_license(["BSD"]) == "BSD-3-Clause" + + +def test_license_lookup_apache(): + """Test Apache variants are mapped correctly.""" + assert convert_to_spdx_license(["Apache"]) == "Apache-2.0" + assert convert_to_spdx_license(["Apache 2"]) == "Apache-2.0" + assert convert_to_spdx_license(["Apache 2.0"]) == "Apache-2.0" + + +def test_license_lookup_gpl(): + """Test GPL variants are mapped correctly.""" + # GPL is a valid SPDX identifier and gets normalized + assert convert_to_spdx_license(["GPL"]) == "GPL-1.0-or-later" + # GPLv3 is not valid SPDX, so use lookup table + assert convert_to_spdx_license(["GPLv3"]) == "GPL-3.0-only" + + +def test_license_lookup_with_valid(): + """Test mixed lookup and valid licenses.""" + result = convert_to_spdx_license(["BSD", "MIT"]) + assert result == "BSD-3-Clause OR MIT" + + +def test_bsd2_normalized(): + """Test BSD-2 is automatically normalized (not in lookup).""" + assert convert_to_spdx_license(["BSD-2"]) == "BSD-2-Clause" + + +def test_empty_strings_from_trailing_comma(): + """Test trailing commas don't create empty LicenseRef.""" + assert convert_to_spdx_license(["MIT,"]) == "MIT" + assert convert_to_spdx_license([",MIT"]) == "MIT" + + +def test_empty_strings_from_double_commas(): + """Test double commas are handled correctly.""" + result = convert_to_spdx_license(["MIT,,BSD"]) + assert result == "MIT OR BSD-3-Clause" + + +def test_invalid_characters_in_licenseref(): + """Test invalid characters are replaced in LicenseRef identifiers.""" + # Parentheses should be replaced + result = convert_to_spdx_license(["Custom(v1.0)"]) + assert result == "LicenseRef-Custom-v1.0-" + assert "(" not in result + + # Underscores should be replaced + result = convert_to_spdx_license(["Custom_License"]) + assert result == "LicenseRef-Custom-License" + assert "_" not in result + + # Spaces should be replaced + result = convert_to_spdx_license(["My Custom License"]) + assert result == "LicenseRef-My-Custom-License" + + # Slashes should be replaced + result = convert_to_spdx_license(["Custom/License"]) + assert result == "LicenseRef-Custom-License" + + +def test_duplicate_licenses_deduplicated(): + """Test duplicate licenses are deduplicated.""" + assert convert_to_spdx_license(["MIT", "MIT"]) == "MIT" + result = convert_to_spdx_license(["BSD", "BSD-3-Clause"]) + assert result == "BSD-3-Clause" + + +def test_comma_separated_licenses(): + """Test comma-separated licenses are split with OR.""" + result = convert_to_spdx_license(["BSD, Apache 2.0"]) + assert result == "BSD-3-Clause OR Apache-2.0" + + result = convert_to_spdx_license(["MIT,BSD,Apache 2.0"]) + assert result == "MIT OR BSD-3-Clause OR Apache-2.0" + + +def test_comma_separated_with_duplicates(): + """Test comma-separated licenses with duplicates are deduplicated.""" + result = convert_to_spdx_license(["MIT, MIT"]) + assert result == "MIT" + + +def test_case_insensitive_lookup(): + """Test lookup table works with different cases.""" + # Lowercase + assert convert_to_spdx_license(["bsd"]) == "BSD-3-Clause" + assert convert_to_spdx_license(["apache"]) == "Apache-2.0" + assert convert_to_spdx_license(["mit license"]) == "MIT" + + # Uppercase + assert convert_to_spdx_license(["BSD"]) == "BSD-3-Clause" + assert convert_to_spdx_license(["APACHE"]) == "Apache-2.0" + + # Mixed case + assert convert_to_spdx_license(["BoOsT"]) == "BSL-1.0" + + +def test_new_license_variants(): + """Test newly added common license variants.""" + # GPL variants + assert convert_to_spdx_license(["GPLv2"]) == "GPL-2.0-only" + assert convert_to_spdx_license(["GPL-2"]) == "GPL-2.0-only" + assert convert_to_spdx_license(["GPL-3.0"]) == "GPL-3.0-only" + + # LGPL variants (defaults to 2.1-or-later) + assert convert_to_spdx_license(["LGPL"]) == "LGPL-2.1-or-later" + assert convert_to_spdx_license(["LGPLv2"]) == "LGPL-2.1-or-later" + assert convert_to_spdx_license(["LGPL-2.1"]) == "LGPL-2.1-or-later" + assert convert_to_spdx_license(["LGPLv3"]) == "LGPL-3.0-only" + + # AGPL variants + assert convert_to_spdx_license(["AGPLv3"]) == "AGPL-3.0-only" + assert convert_to_spdx_license(["AGPL-3.0"]) == "AGPL-3.0-only" + + # Boost variants + assert convert_to_spdx_license(["Boost"]) == "BSL-1.0" + assert convert_to_spdx_license(["Boost Software License"]) == "BSL-1.0" + assert convert_to_spdx_license(["BSL"]) == "BSL-1.0" + + # MIT variants + assert convert_to_spdx_license(["MIT License"]) == "MIT" + + # Public Domain + assert convert_to_spdx_license(["Public Domain"]) == "Unlicense" + + +def test_additional_license_variants(): + """Test additional license variants from real ROS packages.""" + # Apache variants + assert convert_to_spdx_license(["Apache2"]) == "Apache-2.0" + assert convert_to_spdx_license(["Apache2.0"]) == "Apache-2.0" + + # LGPL variants + assert convert_to_spdx_license(["LGPLv2.1"]) == "LGPL-2.1-or-later" + + # Eclipse variants + assert convert_to_spdx_license(["Eclipse Public License 2.0"]) == "EPL-2.0" + assert convert_to_spdx_license(["Eclipse Distribution License 1.0"]) == "EDL-1.0" + + # Zlib + assert convert_to_spdx_license(["zlib License"]) == "Zlib" + + # BSD variants + assert convert_to_spdx_license(["3-Clause BSD"]) == "BSD-3-Clause" + assert convert_to_spdx_license(["BSD License 2.0"]) == "BSD-2-Clause" + + # Creative Commons + assert ( + convert_to_spdx_license(["Creative Commons Zero v1.0 Universal"]) == "CC0-1.0" + ) + + # GNU GPL + assert ( + convert_to_spdx_license(["GNU General Public License v2.0"]) == "GPL-2.0-only" + ) diff --git a/vinca/test_v1_selectors.py b/vinca/test_v1_selectors.py index 5faa3d7..9b5c37b 100644 --- a/vinca/test_v1_selectors.py +++ b/vinca/test_v1_selectors.py @@ -5,6 +5,7 @@ yaml = ruamel.yaml.YAML() + # -----------------------------------------------------------------------------# # Fixtures # # -----------------------------------------------------------------------------# @@ -43,7 +44,9 @@ def test_then_else_branch(): k: 2 """ data = yaml.load(src) - assert v1_selectors.evaluate_selectors(data, target_platform="linux-64") == [{"k": 1}] + assert v1_selectors.evaluate_selectors(data, target_platform="linux-64") == [ + {"k": 1} + ] assert v1_selectors.evaluate_selectors(data, target_platform="win-64") == [{"k": 2}] @@ -61,4 +64,3 @@ def test_spliced_list(): assert out["host"] == ["linux-tool-1", "linux-tool-2", "always-tool"] out2 = v1_selectors.evaluate_selectors(data, target_platform="win-64") assert out2["host"] == ["always-tool"] - diff --git a/vinca/utils.py b/vinca/utils.py index e52c628..e5e2d11 100644 --- a/vinca/utils.py +++ b/vinca/utils.py @@ -66,6 +66,7 @@ def get_repodata(url_or_path, platform=None): fcache.write(content.decode("utf-8")) return json.loads(content) + def ensure_name_is_without_distro_prefix_and_with_underscores(name, vinca_conf): """ Ensure that the name is without distro prefix and with underscores @@ -73,24 +74,31 @@ def ensure_name_is_without_distro_prefix_and_with_underscores(name, vinca_conf): """ newname = name.replace("-", "_") distro_prefix = "ros_" + vinca_conf.get("ros_distro") + "_" - if (newname.startswith(distro_prefix) ): + if newname.startswith(distro_prefix): newname = newname.replace(distro_prefix, "") return newname + def get_pkg_additional_info(pkg_name, vinca_conf): - normalized_name = ensure_name_is_without_distro_prefix_and_with_underscores(pkg_name, vinca_conf) + normalized_name = ensure_name_is_without_distro_prefix_and_with_underscores( + pkg_name, vinca_conf + ) pkg_additional_info_all = vinca_conf["_pkg_additional_info"] if pkg_additional_info_all is None: pkg_additional_info = {} else: - pkg_additional_info = vinca_conf.get("_pkg_additional_info", {}).get(normalized_name, {}) + pkg_additional_info = vinca_conf.get("_pkg_additional_info", {}).get( + normalized_name, {} + ) return pkg_additional_info + def get_pkg_build_number(default_build_number, pkg_name, vinca_conf): pkg_additional_info = get_pkg_additional_info(pkg_name, vinca_conf) return pkg_additional_info.get("build_number", default_build_number) + # Return true if the package is actually provided in conda-forge, and so we generate # only a recipe with a run dependency on the conda forge package def is_dummy_metapackage(pkg_name, vinca_conf): diff --git a/vinca/v1_selectors.py b/vinca/v1_selectors.py index fb1b520..95539fe 100644 --- a/vinca/v1_selectors.py +++ b/vinca/v1_selectors.py @@ -10,6 +10,7 @@ >>> data = yaml.load(open("recipe.yaml")) >>> clean = evaluate_selectors(data, target_platform="linux-64") """ + from __future__ import annotations import copy @@ -85,6 +86,7 @@ def _platform_flags(platform: str | None) -> dict[str, bool | str]: # -----------------------------------------------------------------------------# _ENV = jinja2.Environment() + def _eval_condition(expr: str, ctx: Mapping[str, Any]) -> bool: """Safe-ish evaluation of the selector expression using jinja2.""" try: @@ -109,9 +111,9 @@ def _process_node(node: Any, ctx: Mapping[str, Any]) -> Any | None: if isinstance(node, Mapping) and "if" in node and "then" in node: cond = str(node["if"]) branch = "then" if _eval_condition(cond, ctx) else "else" - if branch in node: # recurse into chosen branch + if branch in node: # recurse into chosen branch return _process_node(node[branch], ctx) - return None # nothing chosen – drop + return None # nothing chosen – drop # Recursion – dict --------------------------------------------------------- if isinstance(node, MutableMapping):