mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[bazel][robotpy] Add mirror for robotpy's wpiuil and wpinet libraries (#8062)
Project import generated by Copybara. GitOrigin-RevId: 92ea93d1b47a82667044bd0af05f7fdb34d2c2c2
This commit is contained in:
1
.bazelrc
1
.bazelrc
@@ -35,6 +35,7 @@ build:build_java --test_tag_filters=allwpilib-build-java --build_tag_filters=all
|
||||
build:build_cpp --test_tag_filters=+allwpilib-build-cpp --build_tag_filters=+allwpilib-build-cpp
|
||||
build:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
|
||||
test:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
|
||||
common:skip_robotpy --test_tag_filters=-robotpy --build_tag_filters=-robotpy
|
||||
|
||||
# Build Buddy Cache Setup
|
||||
build:build_buddy --bes_results_url=https://app.buildbuddy.io/invocation/
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -28,3 +28,5 @@
|
||||
|
||||
# Generated files
|
||||
*/src/generated/** linguist-generated
|
||||
*/robotpy_native_build_info.bzl linguist-generated
|
||||
*/robotpy_pybind_build_info.bzl linguist-generated
|
||||
|
||||
@@ -23,6 +23,7 @@ compile_pip_requirements(
|
||||
extra_args = ["--allow-unsafe"],
|
||||
requirements_in = "requirements.txt",
|
||||
requirements_txt = "requirements_lock.txt",
|
||||
requirements_windows = "//:requirements_windows_lock.txt",
|
||||
# compile_pip_requirements does not respect target_compatible_with for some of the targets it generates under the hood
|
||||
tags = ["no-systemcore"],
|
||||
)
|
||||
|
||||
13
README-RobotPy.md
Normal file
13
README-RobotPy.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# robotpy in allwpilb
|
||||
allwpilib hosts a mirror of RobotPy that can be built with bazel on Linux. The intent of the mirror is to have breaking changes identified early and fixed by the PR creator so that when wpilib releases are made there is much less work required to release a RobotPy version that wraps it. It is not a goal for allwpilib to replace the RobotPy repo; it will still be considered the "source of truth" for python builds and will be responsible for building against all of the applicable architectures and multiple versions of python.
|
||||
|
||||
## Build Process
|
||||
The upstream RobotPy repository uses toml configuration files and semiwrap to produce Meson build scripts. The allwpilib fork uses these toml configuration files to auto generate bazel build scripts. In general, each project (wpiutil, wpimath, etc) defines two pybind extensions; one that simply wraps the native library, and another that adds extension(s) that and contains all of the python files for the library. Both of these subprojects have auto-generated build files; a `robotpy_native_build_info.bzl` for the lidar wraper and `robotpy_pybind_build_info.bzl` which defines the extensions and python library.
|
||||
|
||||
## Disabling robotpy builds
|
||||
Building the robotpy software on top of the standard C++/Java software can result in more than doubling the amount of time it takes to compile. To skip building the robotpy tooling you can add `--config=skip_robotpy` to the command line or to your `user.bazelrc`
|
||||
|
||||
# Syncing with robotpy
|
||||
NOTE: This process is currently unlanded while robotpy gets the 2027 branch stable
|
||||
|
||||
[Copybara](https://github.com/google/copybara) is used to maintin synchronization between the upstream robotpy repositories and the allwpilib mirror. Github actions can be manually run which will create pull requests that will update all of the robotpy files between the two repositories. The ideal process is that the allwpilib mirror is always building in CI, and once a release is created the RobotPy team can run the `wpilib -> robotpy` copybara task, make any fine tuned adjustements and create their release. In the event that additional changes are made on the robotpy side, they can run the `robotpy -> wpilib` task to push the updates back to the mirror. However the goal of the mirroring the software here is to be able to more rapidly test changes and will hopefully overwhelmingly eliminate the need for syncs this direction.
|
||||
26
WORKSPACE
26
WORKSPACE
@@ -79,6 +79,27 @@ http_archive(
|
||||
url = "https://github.com/wpilibsuite/rules_bzlmodrio_toolchains/releases/download/2025-1.bcr6/rules_bzlmodrio_toolchains-2025-1.bcr6.tar.gz",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "pybind11_bazel",
|
||||
integrity = "sha256-iwRj1wuX2pDS6t6DqiCfhIXisv4y+7CvxSJtZoSAzGw=",
|
||||
strip_prefix = "pybind11_bazel-2b6082a4d9d163a52299718113fa41e4b7978db5",
|
||||
urls = ["https://github.com/pybind/pybind11_bazel/archive/2b6082a4d9d163a52299718113fa41e4b7978db5.tar.gz"],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "pybind11",
|
||||
build_file = "@pybind11_bazel//:pybind11-BUILD.bazel",
|
||||
strip_prefix = "pybind11-dfe7e65b4527eeb11036402aac3a394130960bb2",
|
||||
urls = ["https://github.com/pybind/pybind11/archive/dfe7e65b4527eeb11036402aac3a394130960bb2.zip"],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "rules_python_pytest",
|
||||
sha256 = "e2556404ef56ea3ec938597616afc51d78e1832cfe511b196e9f2b8fd7f8f149",
|
||||
strip_prefix = "rules_python_pytest-1.1.1",
|
||||
url = "https://github.com/caseyduquettesc/rules_python_pytest/releases/download/v1.1.1/rules_python_pytest-v1.1.1.tar.gz",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "bazel_skylib",
|
||||
sha256 = "51b5105a760b353773f904d2bbc5e664d0987fbaf22265164de65d43e910d8ac",
|
||||
@@ -138,6 +159,7 @@ pip_parse(
|
||||
name = "allwpilib_pip_deps",
|
||||
python_interpreter_target = "@python_3_10_host//:python",
|
||||
requirements_lock = "//:requirements_lock.txt",
|
||||
requirements_windows = "//:requirements_windows_lock.txt",
|
||||
)
|
||||
|
||||
load("@allwpilib_pip_deps//:requirements.bzl", "install_deps")
|
||||
@@ -371,6 +393,10 @@ load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
|
||||
|
||||
rules_pkg_dependencies()
|
||||
|
||||
load("@rules_python_pytest//python_pytest:repositories.bzl", "rules_python_pytest_dependencies")
|
||||
|
||||
rules_python_pytest_dependencies()
|
||||
|
||||
# Capture the repository environmental variables which specify the filter list for what architectures to build in CI.
|
||||
load("//shared/bazel/rules:publishing_rule.bzl", "publishing_repo")
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
jinja2==3.0.0a1
|
||||
jinja2==3.1.6
|
||||
protobuf==5.28.3
|
||||
grpcio-tools==1.68.0
|
||||
semiwrap==0.1.8
|
||||
pytest
|
||||
numpy
|
||||
opencv-python~=4.6
|
||||
|
||||
@@ -4,62 +4,70 @@
|
||||
#
|
||||
# bazel run //:requirements.update
|
||||
#
|
||||
grpcio==1.68.0 \
|
||||
--hash=sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354 \
|
||||
--hash=sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21 \
|
||||
--hash=sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116 \
|
||||
--hash=sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a \
|
||||
--hash=sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829 \
|
||||
--hash=sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1 \
|
||||
--hash=sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363 \
|
||||
--hash=sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a \
|
||||
--hash=sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9 \
|
||||
--hash=sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b \
|
||||
--hash=sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03 \
|
||||
--hash=sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415 \
|
||||
--hash=sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7 \
|
||||
--hash=sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121 \
|
||||
--hash=sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f \
|
||||
--hash=sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd \
|
||||
--hash=sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d \
|
||||
--hash=sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4 \
|
||||
--hash=sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10 \
|
||||
--hash=sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5 \
|
||||
--hash=sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332 \
|
||||
--hash=sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544 \
|
||||
--hash=sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75 \
|
||||
--hash=sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665 \
|
||||
--hash=sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110 \
|
||||
--hash=sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd \
|
||||
--hash=sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a \
|
||||
--hash=sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618 \
|
||||
--hash=sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d \
|
||||
--hash=sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30 \
|
||||
--hash=sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1 \
|
||||
--hash=sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1 \
|
||||
--hash=sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d \
|
||||
--hash=sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796 \
|
||||
--hash=sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3 \
|
||||
--hash=sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b \
|
||||
--hash=sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659 \
|
||||
--hash=sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a \
|
||||
--hash=sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05 \
|
||||
--hash=sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a \
|
||||
--hash=sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c \
|
||||
--hash=sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161 \
|
||||
--hash=sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb \
|
||||
--hash=sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78 \
|
||||
--hash=sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27 \
|
||||
--hash=sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe \
|
||||
--hash=sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b \
|
||||
--hash=sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc \
|
||||
--hash=sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155 \
|
||||
--hash=sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490 \
|
||||
--hash=sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d \
|
||||
--hash=sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2 \
|
||||
--hash=sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3 \
|
||||
--hash=sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e \
|
||||
--hash=sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3
|
||||
cxxheaderparser[pcpp]==1.5.0 \
|
||||
--hash=sha256:0b9600f817d7378794a0f5df10972fd85f73ba9d3ea0090a5b6b5c12be3b1f01 \
|
||||
--hash=sha256:2a93fc81c62d2e4de3e92a697336557debe13db44bfef0f2d4fa81501cd1f36f
|
||||
# via semiwrap
|
||||
dictdiffer==0.9.0 \
|
||||
--hash=sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578 \
|
||||
--hash=sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595
|
||||
# via semiwrap
|
||||
exceptiongroup==1.3.0 \
|
||||
--hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \
|
||||
--hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88
|
||||
# via pytest
|
||||
grpcio==1.73.1 \
|
||||
--hash=sha256:052e28fe9c41357da42250a91926a3e2f74c046575c070b69659467ca5aa976b \
|
||||
--hash=sha256:07f08705a5505c9b5b0cbcbabafb96462b5a15b7236bbf6bbcc6b0b91e1cbd7e \
|
||||
--hash=sha256:0a9f3ea8dce9eae9d7cb36827200133a72b37a63896e0e61a9d5ec7d61a59ab1 \
|
||||
--hash=sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854 \
|
||||
--hash=sha256:105492124828911f85127e4825d1c1234b032cb9d238567876b5515d01151379 \
|
||||
--hash=sha256:10af9f2ab98a39f5b6c1896c6fc2036744b5b41d12739d48bed4c3e15b6cf900 \
|
||||
--hash=sha256:1c0bf15f629b1497436596b1cbddddfa3234273490229ca29561209778ebe182 \
|
||||
--hash=sha256:1c502c2e950fc7e8bf05c047e8a14522ef7babac59abbfde6dbf46b7a0d9c71e \
|
||||
--hash=sha256:24e06a5319e33041e322d32c62b1e728f18ab8c9dbc91729a3d9f9e3ed336642 \
|
||||
--hash=sha256:277b426a0ed341e8447fbf6c1d6b68c952adddf585ea4685aa563de0f03df887 \
|
||||
--hash=sha256:2d70f4ddd0a823436c2624640570ed6097e40935c9194482475fe8e3d9754d55 \
|
||||
--hash=sha256:303c8135d8ab176f8038c14cc10d698ae1db9c480f2b2823f7a987aa2a4c5646 \
|
||||
--hash=sha256:3841a8a5a66830261ab6a3c2a3dc539ed84e4ab019165f77b3eeb9f0ba621f26 \
|
||||
--hash=sha256:42f0660bce31b745eb9d23f094a332d31f210dcadd0fc8e5be7e4c62a87ce86b \
|
||||
--hash=sha256:45cf17dcce5ebdb7b4fe9e86cb338fa99d7d1bb71defc78228e1ddf8d0de8cbb \
|
||||
--hash=sha256:4a68f8c9966b94dff693670a5cf2b54888a48a5011c5d9ce2295a1a1465ee84f \
|
||||
--hash=sha256:5b9b1805a7d61c9e90541cbe8dfe0a593dfc8c5c3a43fe623701b6a01b01d710 \
|
||||
--hash=sha256:610e19b04f452ba6f402ac9aa94eb3d21fbc94553368008af634812c4a85a99e \
|
||||
--hash=sha256:628c30f8e77e0258ab788750ec92059fc3d6628590fb4b7cea8c102503623ed7 \
|
||||
--hash=sha256:65b0458a10b100d815a8426b1442bd17001fdb77ea13665b2f7dc9e8587fdc6b \
|
||||
--hash=sha256:67a0468256c9db6d5ecb1fde4bf409d016f42cef649323f0a08a72f352d1358b \
|
||||
--hash=sha256:686231cdd03a8a8055f798b2b54b19428cdf18fa1549bee92249b43607c42668 \
|
||||
--hash=sha256:68b84d65bbdebd5926eb5c53b0b9ec3b3f83408a30e4c20c373c5337b4219ec5 \
|
||||
--hash=sha256:6957025a4608bb0a5ff42abd75bfbb2ed99eda29d5992ef31d691ab54b753643 \
|
||||
--hash=sha256:6a2b372e65fad38842050943f42ce8fee00c6f2e8ea4f7754ba7478d26a356ee \
|
||||
--hash=sha256:6a6037891cd2b1dd1406b388660522e1565ed340b1fea2955b0234bdd941a862 \
|
||||
--hash=sha256:6abfc0f9153dc4924536f40336f88bd4fe7bd7494f028675e2e04291b8c2c62a \
|
||||
--hash=sha256:75fc8e543962ece2f7ecd32ada2d44c0c8570ae73ec92869f9af8b944863116d \
|
||||
--hash=sha256:7fce2cd1c0c1116cf3850564ebfc3264fba75d3c74a7414373f1238ea365ef87 \
|
||||
--hash=sha256:83a6c2cce218e28f5040429835fa34a29319071079e3169f9543c3fbeff166d2 \
|
||||
--hash=sha256:89018866a096e2ce21e05eabed1567479713ebe57b1db7cbb0f1e3b896793ba4 \
|
||||
--hash=sha256:8f5a6df3fba31a3485096ac85b2e34b9666ffb0590df0cd044f58694e6a1f6b5 \
|
||||
--hash=sha256:921b25618b084e75d424a9f8e6403bfeb7abef074bb6c3174701e0f2542debcf \
|
||||
--hash=sha256:96c112333309493c10e118d92f04594f9055774757f5d101b39f8150f8c25582 \
|
||||
--hash=sha256:ad1d958c31cc91ab050bd8a91355480b8e0683e21176522bacea225ce51163f2 \
|
||||
--hash=sha256:ad5c958cc3d98bb9d71714dc69f1c13aaf2f4b53e29d4cc3f1501ef2e4d129b2 \
|
||||
--hash=sha256:b310824ab5092cf74750ebd8a8a8981c1810cb2b363210e70d06ef37ad80d4f9 \
|
||||
--hash=sha256:b3215f69a0670a8cfa2ab53236d9e8026bfb7ead5d4baabe7d7dc11d30fda967 \
|
||||
--hash=sha256:b4adc97d2d7f5c660a5498bda978ebb866066ad10097265a5da0511323ae9f50 \
|
||||
--hash=sha256:ba2cea9f7ae4bc21f42015f0ec98f69ae4179848ad744b210e7685112fa507a1 \
|
||||
--hash=sha256:bc5eccfd9577a5dc7d5612b2ba90cca4ad14c6d949216c68585fdec9848befb1 \
|
||||
--hash=sha256:c45a28a0cfb6ddcc7dc50a29de44ecac53d115c3388b2782404218db51cb2df3 \
|
||||
--hash=sha256:c54796ca22b8349cc594d18b01099e39f2b7ffb586ad83217655781a350ce4da \
|
||||
--hash=sha256:cce7265b9617168c2d08ae570fcc2af4eaf72e84f8c710ca657cc546115263af \
|
||||
--hash=sha256:d60588ab6ba0ac753761ee0e5b30a29398306401bfbceffe7d68ebb21193f9d4 \
|
||||
--hash=sha256:d74c3f4f37b79e746271aa6cdb3a1d7e4432aea38735542b23adcabaaee0c097 \
|
||||
--hash=sha256:dc7d7fd520614fce2e6455ba89791458020a39716951c7c07694f9dbae28e9c0 \
|
||||
--hash=sha256:de18769aea47f18e782bf6819a37c1c528914bfd5683b8782b9da356506190c8 \
|
||||
--hash=sha256:ed451a0e39c8e51eb1612b78686839efd1a920666d1666c1adfdb4fd51680c0f \
|
||||
--hash=sha256:f43ffb3bd415c57224c7427bfb9e6c46a0b6e998754bfa0d00f408e1873dcbb5 \
|
||||
--hash=sha256:f48e862aed925ae987eb7084409a80985de75243389dc9d9c271dd711e589918
|
||||
# via grpcio-tools
|
||||
grpcio-tools==1.68.0 \
|
||||
--hash=sha256:01ace351a51d7ee120963a4612b1f00e964462ec548db20d17f8902e238592c8 \
|
||||
@@ -118,9 +126,13 @@ grpcio-tools==1.68.0 \
|
||||
--hash=sha256:f65942fab440e99113ce14436deace7554d5aa554ea18358e3a5f3fc47efe322 \
|
||||
--hash=sha256:f95103e3e4e7fee7c6123bc9e4e925e07ad24d8d09d7c1c916fb6c8d1cb9e726
|
||||
# via -r requirements.txt
|
||||
jinja2==3.0.0a1 \
|
||||
--hash=sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484 \
|
||||
--hash=sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668
|
||||
iniconfig==2.1.0 \
|
||||
--hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \
|
||||
--hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760
|
||||
# via pytest
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
# via -r requirements.txt
|
||||
markupsafe==3.0.2 \
|
||||
--hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \
|
||||
@@ -185,6 +197,105 @@ markupsafe==3.0.2 \
|
||||
--hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \
|
||||
--hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50
|
||||
# via jinja2
|
||||
numpy==2.2.6 \
|
||||
--hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
|
||||
--hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
|
||||
--hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
|
||||
--hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
|
||||
--hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
|
||||
--hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
|
||||
--hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
|
||||
--hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
|
||||
--hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
|
||||
--hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
|
||||
--hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
|
||||
--hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
|
||||
--hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
|
||||
--hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
|
||||
--hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
|
||||
--hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
|
||||
--hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
|
||||
--hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
|
||||
--hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
|
||||
--hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
|
||||
--hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
|
||||
--hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
|
||||
--hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
|
||||
--hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
|
||||
--hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
|
||||
--hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
|
||||
--hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
|
||||
--hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
|
||||
--hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
|
||||
--hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
|
||||
--hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
|
||||
--hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
|
||||
--hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
|
||||
--hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
|
||||
--hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
|
||||
--hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
|
||||
--hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
|
||||
--hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
|
||||
--hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
|
||||
--hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
|
||||
--hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
|
||||
--hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
|
||||
--hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
|
||||
--hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
|
||||
--hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
|
||||
--hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
|
||||
--hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
|
||||
--hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
|
||||
--hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
|
||||
--hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
|
||||
--hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
|
||||
--hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
|
||||
--hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
|
||||
--hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
|
||||
--hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# opencv-python
|
||||
opencv-python==4.11.0.86 \
|
||||
--hash=sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4 \
|
||||
--hash=sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec \
|
||||
--hash=sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202 \
|
||||
--hash=sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a \
|
||||
--hash=sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d \
|
||||
--hash=sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b \
|
||||
--hash=sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66
|
||||
# via -r requirements.txt
|
||||
packaging==25.0 \
|
||||
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
|
||||
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
|
||||
# via
|
||||
# pytest
|
||||
# semiwrap
|
||||
pcpp==1.30 \
|
||||
--hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \
|
||||
--hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1
|
||||
# via cxxheaderparser
|
||||
pkgconf==2.4.3.post1 \
|
||||
--hash=sha256:101bed059939c26b04dfba1226a9c0ebf1f08b9bee98354797c2d887a08a2d7a \
|
||||
--hash=sha256:1f334bd2eaf2cb07feb09be439b62ecca1ac2a0aaa447587d5a31029bb43bf69 \
|
||||
--hash=sha256:36b7be7658296663d67151d2dbb5895721e6a66d5bcc903d7caae1ac6316456d \
|
||||
--hash=sha256:3b5a1905dd2f08396f1e5a8bab6d0c35e9cb7f3087f1a27f089dcc09ae126f09 \
|
||||
--hash=sha256:4346e011187ceff0856e1c472a759790b225856da68c60b806e051c84c6ac9ed \
|
||||
--hash=sha256:442b3aa06ddeb20e5cefc8cbc5811a02db128295a215f497d817cc0f0d358f71 \
|
||||
--hash=sha256:4e8fe5abadf9c64d4cae927445da5172310cdec300a9c3e49716a95e61848a5f \
|
||||
--hash=sha256:564a84be78f62605f39a8f45d5449a3549647e6488b8133b8a05281d4cba8aed \
|
||||
--hash=sha256:6f77ac67af2fac4947ab436e0b6f80db73cca22c87ad3abc6948e096a68370d1 \
|
||||
--hash=sha256:86857d46fef3c6ee1011a11fe20717803e9c40e004a1347a0876b6e39485288d \
|
||||
--hash=sha256:8b49ac5d034be5f5e22ec0dd8d6e40f0ae69974299bf84368f4dcffa1ffa5633 \
|
||||
--hash=sha256:a95610a629818290305860f666bab82b53039746a44e36de35ecf55275345e66 \
|
||||
--hash=sha256:b52a01db329f8541f9f9e7c69c48b62dbe326658fc67b66ebdfb4aeccc7ccc60 \
|
||||
--hash=sha256:defe70c329df7d7992b64a105e78d97f154727b595271fd97a70f3ce33b05478 \
|
||||
--hash=sha256:ec31ce85eab01f7a41d2c86a43827556fe8238f7c5b51ccca42bfd01762d84ca
|
||||
# via semiwrap
|
||||
pluggy==1.6.0 \
|
||||
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
|
||||
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
|
||||
# via pytest
|
||||
protobuf==5.28.3 \
|
||||
--hash=sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24 \
|
||||
--hash=sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535 \
|
||||
@@ -200,9 +311,190 @@ protobuf==5.28.3 \
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# grpcio-tools
|
||||
pybind11-stubgen==2.5.4 \
|
||||
--hash=sha256:8625f25da48cf96eea24ba7cae673b5f49b45847b6ef01eead60c4eb762fe5c5 \
|
||||
--hash=sha256:b6bd44a6d4ba55cef80bd8af92f1f8195b1c6bb0f7bd2f6d785c9530ce6bcae9
|
||||
# via semiwrap
|
||||
pygments==2.19.2 \
|
||||
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
|
||||
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
|
||||
# via pytest
|
||||
pytest==8.4.1 \
|
||||
--hash=sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7 \
|
||||
--hash=sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c
|
||||
# via -r requirements.txt
|
||||
pyyaml==6.0.2 \
|
||||
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
|
||||
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
|
||||
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
|
||||
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
|
||||
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
|
||||
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
|
||||
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
|
||||
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
|
||||
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
|
||||
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
|
||||
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
|
||||
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
|
||||
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
|
||||
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
|
||||
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
|
||||
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
|
||||
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
|
||||
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
|
||||
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
|
||||
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
|
||||
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
|
||||
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
|
||||
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
|
||||
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
|
||||
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
|
||||
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
|
||||
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
|
||||
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
|
||||
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
|
||||
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
|
||||
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
|
||||
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
|
||||
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
|
||||
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
|
||||
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
|
||||
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
|
||||
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
|
||||
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
|
||||
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
|
||||
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
|
||||
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
|
||||
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
|
||||
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
|
||||
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
|
||||
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
|
||||
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
|
||||
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
|
||||
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
|
||||
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
|
||||
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
|
||||
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
||||
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||
# via semiwrap
|
||||
ruamel-yaml==0.18.14 \
|
||||
--hash=sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2 \
|
||||
--hash=sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7
|
||||
# via semiwrap
|
||||
ruamel-yaml-clib==0.2.12 \
|
||||
--hash=sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b \
|
||||
--hash=sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4 \
|
||||
--hash=sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef \
|
||||
--hash=sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5 \
|
||||
--hash=sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3 \
|
||||
--hash=sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632 \
|
||||
--hash=sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6 \
|
||||
--hash=sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7 \
|
||||
--hash=sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680 \
|
||||
--hash=sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf \
|
||||
--hash=sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da \
|
||||
--hash=sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6 \
|
||||
--hash=sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a \
|
||||
--hash=sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01 \
|
||||
--hash=sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519 \
|
||||
--hash=sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6 \
|
||||
--hash=sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f \
|
||||
--hash=sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd \
|
||||
--hash=sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2 \
|
||||
--hash=sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52 \
|
||||
--hash=sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd \
|
||||
--hash=sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d \
|
||||
--hash=sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c \
|
||||
--hash=sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6 \
|
||||
--hash=sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb \
|
||||
--hash=sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a \
|
||||
--hash=sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969 \
|
||||
--hash=sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28 \
|
||||
--hash=sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d \
|
||||
--hash=sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e \
|
||||
--hash=sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45 \
|
||||
--hash=sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4 \
|
||||
--hash=sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12 \
|
||||
--hash=sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31 \
|
||||
--hash=sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642 \
|
||||
--hash=sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e \
|
||||
--hash=sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285 \
|
||||
--hash=sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed \
|
||||
--hash=sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1 \
|
||||
--hash=sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7 \
|
||||
--hash=sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3 \
|
||||
--hash=sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475 \
|
||||
--hash=sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5 \
|
||||
--hash=sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76 \
|
||||
--hash=sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987 \
|
||||
--hash=sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df
|
||||
# via ruamel-yaml
|
||||
semiwrap==0.1.8 \
|
||||
--hash=sha256:af5fe5aa3fb9c39b9924ab2f763f41a7a8128ffbaf743a3cb0c3bef6a30c8233 \
|
||||
--hash=sha256:e176f9f4cca2409a104fab7d14956e1e371ee36264c8478b78a2d142e104537d
|
||||
# via -r requirements.txt
|
||||
sphinxify==0.12 \
|
||||
--hash=sha256:3ec299e78babac7d3457f47bf263411b48e10b9c8add18d7159fa0327cc4a061 \
|
||||
--hash=sha256:ec97af947884bacd8e18f14ff2b6030b6da829a6a5bf7a32421b633b10c6f7e8
|
||||
# via semiwrap
|
||||
tomli==2.2.1 \
|
||||
--hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \
|
||||
--hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \
|
||||
--hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \
|
||||
--hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \
|
||||
--hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \
|
||||
--hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \
|
||||
--hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \
|
||||
--hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \
|
||||
--hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \
|
||||
--hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \
|
||||
--hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \
|
||||
--hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \
|
||||
--hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \
|
||||
--hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \
|
||||
--hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \
|
||||
--hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \
|
||||
--hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \
|
||||
--hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \
|
||||
--hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \
|
||||
--hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \
|
||||
--hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \
|
||||
--hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \
|
||||
--hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \
|
||||
--hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \
|
||||
--hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \
|
||||
--hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \
|
||||
--hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \
|
||||
--hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \
|
||||
--hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \
|
||||
--hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \
|
||||
--hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \
|
||||
--hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7
|
||||
# via
|
||||
# pytest
|
||||
# semiwrap
|
||||
tomli-w==1.2.0 \
|
||||
--hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \
|
||||
--hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021
|
||||
# via semiwrap
|
||||
toposort==1.10 \
|
||||
--hash=sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd \
|
||||
--hash=sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87
|
||||
# via semiwrap
|
||||
typing-extensions==4.13.2 \
|
||||
--hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \
|
||||
--hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef
|
||||
# via
|
||||
# exceptiongroup
|
||||
# semiwrap
|
||||
validobj==1.3 \
|
||||
--hash=sha256:0ddb2e73693763e2014620327486f9e458fcf1d016ce286a146111dc8493e298 \
|
||||
--hash=sha256:b5a6f79f76064dc1a4e3b2239bf40ea1c4f4ce8d742c9a78784174f784c9cb38
|
||||
# via semiwrap
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==75.6.0 \
|
||||
--hash=sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6 \
|
||||
--hash=sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d
|
||||
setuptools==80.9.0 \
|
||||
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
|
||||
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
|
||||
# via grpcio-tools
|
||||
|
||||
506
requirements_windows_lock.txt
Normal file
506
requirements_windows_lock.txt
Normal file
@@ -0,0 +1,506 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# bazel run //:requirements.update
|
||||
#
|
||||
colorama==0.4.6 \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via
|
||||
# pkgconf
|
||||
# pytest
|
||||
cxxheaderparser[pcpp]==1.5.0 \
|
||||
--hash=sha256:0b9600f817d7378794a0f5df10972fd85f73ba9d3ea0090a5b6b5c12be3b1f01 \
|
||||
--hash=sha256:2a93fc81c62d2e4de3e92a697336557debe13db44bfef0f2d4fa81501cd1f36f
|
||||
# via semiwrap
|
||||
dictdiffer==0.9.0 \
|
||||
--hash=sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578 \
|
||||
--hash=sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595
|
||||
# via semiwrap
|
||||
exceptiongroup==1.3.0 \
|
||||
--hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \
|
||||
--hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88
|
||||
# via pytest
|
||||
grpcio==1.73.1 \
|
||||
--hash=sha256:052e28fe9c41357da42250a91926a3e2f74c046575c070b69659467ca5aa976b \
|
||||
--hash=sha256:07f08705a5505c9b5b0cbcbabafb96462b5a15b7236bbf6bbcc6b0b91e1cbd7e \
|
||||
--hash=sha256:0a9f3ea8dce9eae9d7cb36827200133a72b37a63896e0e61a9d5ec7d61a59ab1 \
|
||||
--hash=sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854 \
|
||||
--hash=sha256:105492124828911f85127e4825d1c1234b032cb9d238567876b5515d01151379 \
|
||||
--hash=sha256:10af9f2ab98a39f5b6c1896c6fc2036744b5b41d12739d48bed4c3e15b6cf900 \
|
||||
--hash=sha256:1c0bf15f629b1497436596b1cbddddfa3234273490229ca29561209778ebe182 \
|
||||
--hash=sha256:1c502c2e950fc7e8bf05c047e8a14522ef7babac59abbfde6dbf46b7a0d9c71e \
|
||||
--hash=sha256:24e06a5319e33041e322d32c62b1e728f18ab8c9dbc91729a3d9f9e3ed336642 \
|
||||
--hash=sha256:277b426a0ed341e8447fbf6c1d6b68c952adddf585ea4685aa563de0f03df887 \
|
||||
--hash=sha256:2d70f4ddd0a823436c2624640570ed6097e40935c9194482475fe8e3d9754d55 \
|
||||
--hash=sha256:303c8135d8ab176f8038c14cc10d698ae1db9c480f2b2823f7a987aa2a4c5646 \
|
||||
--hash=sha256:3841a8a5a66830261ab6a3c2a3dc539ed84e4ab019165f77b3eeb9f0ba621f26 \
|
||||
--hash=sha256:42f0660bce31b745eb9d23f094a332d31f210dcadd0fc8e5be7e4c62a87ce86b \
|
||||
--hash=sha256:45cf17dcce5ebdb7b4fe9e86cb338fa99d7d1bb71defc78228e1ddf8d0de8cbb \
|
||||
--hash=sha256:4a68f8c9966b94dff693670a5cf2b54888a48a5011c5d9ce2295a1a1465ee84f \
|
||||
--hash=sha256:5b9b1805a7d61c9e90541cbe8dfe0a593dfc8c5c3a43fe623701b6a01b01d710 \
|
||||
--hash=sha256:610e19b04f452ba6f402ac9aa94eb3d21fbc94553368008af634812c4a85a99e \
|
||||
--hash=sha256:628c30f8e77e0258ab788750ec92059fc3d6628590fb4b7cea8c102503623ed7 \
|
||||
--hash=sha256:65b0458a10b100d815a8426b1442bd17001fdb77ea13665b2f7dc9e8587fdc6b \
|
||||
--hash=sha256:67a0468256c9db6d5ecb1fde4bf409d016f42cef649323f0a08a72f352d1358b \
|
||||
--hash=sha256:686231cdd03a8a8055f798b2b54b19428cdf18fa1549bee92249b43607c42668 \
|
||||
--hash=sha256:68b84d65bbdebd5926eb5c53b0b9ec3b3f83408a30e4c20c373c5337b4219ec5 \
|
||||
--hash=sha256:6957025a4608bb0a5ff42abd75bfbb2ed99eda29d5992ef31d691ab54b753643 \
|
||||
--hash=sha256:6a2b372e65fad38842050943f42ce8fee00c6f2e8ea4f7754ba7478d26a356ee \
|
||||
--hash=sha256:6a6037891cd2b1dd1406b388660522e1565ed340b1fea2955b0234bdd941a862 \
|
||||
--hash=sha256:6abfc0f9153dc4924536f40336f88bd4fe7bd7494f028675e2e04291b8c2c62a \
|
||||
--hash=sha256:75fc8e543962ece2f7ecd32ada2d44c0c8570ae73ec92869f9af8b944863116d \
|
||||
--hash=sha256:7fce2cd1c0c1116cf3850564ebfc3264fba75d3c74a7414373f1238ea365ef87 \
|
||||
--hash=sha256:83a6c2cce218e28f5040429835fa34a29319071079e3169f9543c3fbeff166d2 \
|
||||
--hash=sha256:89018866a096e2ce21e05eabed1567479713ebe57b1db7cbb0f1e3b896793ba4 \
|
||||
--hash=sha256:8f5a6df3fba31a3485096ac85b2e34b9666ffb0590df0cd044f58694e6a1f6b5 \
|
||||
--hash=sha256:921b25618b084e75d424a9f8e6403bfeb7abef074bb6c3174701e0f2542debcf \
|
||||
--hash=sha256:96c112333309493c10e118d92f04594f9055774757f5d101b39f8150f8c25582 \
|
||||
--hash=sha256:ad1d958c31cc91ab050bd8a91355480b8e0683e21176522bacea225ce51163f2 \
|
||||
--hash=sha256:ad5c958cc3d98bb9d71714dc69f1c13aaf2f4b53e29d4cc3f1501ef2e4d129b2 \
|
||||
--hash=sha256:b310824ab5092cf74750ebd8a8a8981c1810cb2b363210e70d06ef37ad80d4f9 \
|
||||
--hash=sha256:b3215f69a0670a8cfa2ab53236d9e8026bfb7ead5d4baabe7d7dc11d30fda967 \
|
||||
--hash=sha256:b4adc97d2d7f5c660a5498bda978ebb866066ad10097265a5da0511323ae9f50 \
|
||||
--hash=sha256:ba2cea9f7ae4bc21f42015f0ec98f69ae4179848ad744b210e7685112fa507a1 \
|
||||
--hash=sha256:bc5eccfd9577a5dc7d5612b2ba90cca4ad14c6d949216c68585fdec9848befb1 \
|
||||
--hash=sha256:c45a28a0cfb6ddcc7dc50a29de44ecac53d115c3388b2782404218db51cb2df3 \
|
||||
--hash=sha256:c54796ca22b8349cc594d18b01099e39f2b7ffb586ad83217655781a350ce4da \
|
||||
--hash=sha256:cce7265b9617168c2d08ae570fcc2af4eaf72e84f8c710ca657cc546115263af \
|
||||
--hash=sha256:d60588ab6ba0ac753761ee0e5b30a29398306401bfbceffe7d68ebb21193f9d4 \
|
||||
--hash=sha256:d74c3f4f37b79e746271aa6cdb3a1d7e4432aea38735542b23adcabaaee0c097 \
|
||||
--hash=sha256:dc7d7fd520614fce2e6455ba89791458020a39716951c7c07694f9dbae28e9c0 \
|
||||
--hash=sha256:de18769aea47f18e782bf6819a37c1c528914bfd5683b8782b9da356506190c8 \
|
||||
--hash=sha256:ed451a0e39c8e51eb1612b78686839efd1a920666d1666c1adfdb4fd51680c0f \
|
||||
--hash=sha256:f43ffb3bd415c57224c7427bfb9e6c46a0b6e998754bfa0d00f408e1873dcbb5 \
|
||||
--hash=sha256:f48e862aed925ae987eb7084409a80985de75243389dc9d9c271dd711e589918
|
||||
# via grpcio-tools
|
||||
grpcio-tools==1.68.0 \
|
||||
--hash=sha256:01ace351a51d7ee120963a4612b1f00e964462ec548db20d17f8902e238592c8 \
|
||||
--hash=sha256:061345c0079b9471f32230186ab01acb908ea0e577bc1699a8cf47acef8be4af \
|
||||
--hash=sha256:0f77957e3a0916a0dd18d57ce6b49d95fc9a5cfed92310f226339c0fda5394f6 \
|
||||
--hash=sha256:10d03e3ad4af6284fd27cb14f5a3d52045913c1253e3e24a384ed91bc8adbfcd \
|
||||
--hash=sha256:1117a81592542f0c36575082daa6413c57ca39188b18a4c50ec7332616f4b97e \
|
||||
--hash=sha256:1769d7f529de1cc102f7fb900611e3c0b69bdb244fca1075b24d6e5b49024586 \
|
||||
--hash=sha256:17d0c9004ea82b4213955a585401e80c30d4b37a1d4ace32ccdea8db4d3b7d43 \
|
||||
--hash=sha256:196cd8a3a5963a4c9e424314df9eb573b305e6f958fe6508d26580ce01e7aa56 \
|
||||
--hash=sha256:19bafb80948eda979b1b3a63c1567162d06249f43068a0e46a028a448e6f72d4 \
|
||||
--hash=sha256:261d98fd635595de42aadee848f9af46da6654d63791c888891e94f66c5d0682 \
|
||||
--hash=sha256:26335eea976dfc1ff5d90b19c309a9425bd53868112a0507ad20f297f2c21d3e \
|
||||
--hash=sha256:28ebdbad2ef16699d07400b65260240851049a75502eff69a59b127d3ab960f1 \
|
||||
--hash=sha256:2919faae04fe47bad57fc9b578aeaab527da260e851f321a253b6b11862254a8 \
|
||||
--hash=sha256:2ec3a2e0afa4866ccc5ba33c071aebaa619245dfdd840cbb74f2b0591868d085 \
|
||||
--hash=sha256:3aa40958355920ae2846c6fb5cadac4f2c8e33234a2982fef8101da0990e3968 \
|
||||
--hash=sha256:453ee3193d59c974c678d91f08786f43c25ef753651b0825dc3d008c31baf68d \
|
||||
--hash=sha256:46b537480b8fd2195d988120a28467601a2a3de2e504043b89fb90318e1eb754 \
|
||||
--hash=sha256:4fe611d89a1836df8936f066d39c7eb03d4241806449ec45d4b8e1c843ae8011 \
|
||||
--hash=sha256:511224a99726eb84db9ddb84dc8a75377c3eae797d835f99e80128ec618376d5 \
|
||||
--hash=sha256:51e5a090849b30c99a2396d42140b8a3e558eff6cdfa12603f9582e2cd07724e \
|
||||
--hash=sha256:533ce6791a5ba21e35d74c6c25caf4776f5692785a170c01ea1153783ad5af31 \
|
||||
--hash=sha256:56842a0ce74b4b92eb62cd5ee00181b2d3acc58ba0c4fd20d15a5db51f891ba6 \
|
||||
--hash=sha256:57e29e78c33fb1b1d557fbe7650d722d1f2b0a9f53ea73beb8ea47e627b6000b \
|
||||
--hash=sha256:59a885091bf29700ba0e14a954d156a18714caaa2006a7f328b18e1ac4b1e721 \
|
||||
--hash=sha256:5afd2f3f7257b52228a7808a2b4a765893d4d802d7a2377d9284853e67d045c6 \
|
||||
--hash=sha256:5d3150d784d8050b10dcf5eb06e04fb90747a1547fed3a062a608d940fe57066 \
|
||||
--hash=sha256:66b70b37184d40806844f51c2757c6b852511d4ea46a3bf2c7e931a47b455bc6 \
|
||||
--hash=sha256:6950725bf7a496f81d3ec3324334ffc9dbec743b510dd0e897f51f8627eeb6ac \
|
||||
--hash=sha256:6dd69c9f3ff85eee8d1f71adf7023c638ca8d465633244ac1b7f19bc3668612d \
|
||||
--hash=sha256:700f171cd3293ee8d50cd43171562ff07b14fa8e49ee471cd91c6924c7da8644 \
|
||||
--hash=sha256:737804ec2225dd4cc27e633b4ca0e963b0795161bf678285fab6586e917fd867 \
|
||||
--hash=sha256:766c2cd2e365e0fc0e559af56f2c2d144d95fd7cb8668a34d533e66d6435eb34 \
|
||||
--hash=sha256:795f2cd76f68a12b0b5541b98187ba367dd69b49d359cf98b781ead742961370 \
|
||||
--hash=sha256:7dc5195dc02057668cc22da1ff1aea1811f6fa0deb801b3194dec1fe0bab1cf0 \
|
||||
--hash=sha256:80b733014eb40d920d836d782e5cdea0dcc90d251a2ffb35ab378ef4f8a42c14 \
|
||||
--hash=sha256:849b12bec2320e49e988df104c92217d533e01febac172a4495caab36d9f0edc \
|
||||
--hash=sha256:88640d95ee41921ac7352fa5fadca52a06d7e21fbe53e6a706a9a494f756be7d \
|
||||
--hash=sha256:8fefc6d000e169a97336feded23ce614df3fb9926fc48c7a9ff8ea459d93b5b0 \
|
||||
--hash=sha256:92a09afe64fe26696595de2036e10967876d26b12c894cc9160f00152cacebe7 \
|
||||
--hash=sha256:9509a5c3ed3d54fa7ac20748d501cb86668f764605a0a68f275339ee0f1dc1a6 \
|
||||
--hash=sha256:ab93fab49fa1e699e577ff5fbb99aba660164d710d4c33cfe0aa9d06f585539f \
|
||||
--hash=sha256:b094b22919b786ad73c20372ef5e546330e7cd2c6dc12293b7ed586975f35d38 \
|
||||
--hash=sha256:b47ae076ffb29a68e517bc03552bef0d9c973f8e18adadff180b123e973a26ea \
|
||||
--hash=sha256:b4ca81770cd729a9ea536d871aacedbde2b732bb9bb83c9d993d63f58502153d \
|
||||
--hash=sha256:c10f3faa0cc4d89eb546f53b623837af23e86dc495d3b89510bcc0e0a6c0b8b2 \
|
||||
--hash=sha256:c77ecc5164bb413a613bdac9091dcc29d26834a2ac42fcd1afdfcda9e3003e68 \
|
||||
--hash=sha256:cad40c3164ee9cef62524dea509449ea581b17ea493178beef051bf79b5103ca \
|
||||
--hash=sha256:d0470ffc6a93c86cdda48edd428d22e2fef17d854788d60d0d5f291038873157 \
|
||||
--hash=sha256:d3e678162e1d7a8720dc05fdd537fc8df082a50831791f7bb1c6f90095f8368b \
|
||||
--hash=sha256:dd9a654af8536b3de8525bff72a245fef62d572eabf96ac946fe850e707cb27d \
|
||||
--hash=sha256:e31be6dc61496a59c1079b0a669f93dfcc2cdc4b1dbdc4374247cd09cee1329b \
|
||||
--hash=sha256:e903d07bc65232aa9e7704c829aec263e1e139442608e473d7912417a9908e29 \
|
||||
--hash=sha256:ee86157ef899f58ba2fe1055cce0d33bd703e99aa6d5a0895581ac3969f06bfa \
|
||||
--hash=sha256:f65942fab440e99113ce14436deace7554d5aa554ea18358e3a5f3fc47efe322 \
|
||||
--hash=sha256:f95103e3e4e7fee7c6123bc9e4e925e07ad24d8d09d7c1c916fb6c8d1cb9e726
|
||||
# via -r requirements.txt
|
||||
iniconfig==2.1.0 \
|
||||
--hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \
|
||||
--hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760
|
||||
# via pytest
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
# via -r requirements.txt
|
||||
markupsafe==3.0.2 \
|
||||
--hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \
|
||||
--hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \
|
||||
--hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \
|
||||
--hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \
|
||||
--hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \
|
||||
--hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \
|
||||
--hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \
|
||||
--hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \
|
||||
--hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \
|
||||
--hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \
|
||||
--hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \
|
||||
--hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \
|
||||
--hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \
|
||||
--hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \
|
||||
--hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \
|
||||
--hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \
|
||||
--hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \
|
||||
--hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \
|
||||
--hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \
|
||||
--hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \
|
||||
--hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \
|
||||
--hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \
|
||||
--hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \
|
||||
--hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \
|
||||
--hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \
|
||||
--hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \
|
||||
--hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \
|
||||
--hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \
|
||||
--hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \
|
||||
--hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \
|
||||
--hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \
|
||||
--hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \
|
||||
--hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \
|
||||
--hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \
|
||||
--hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \
|
||||
--hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \
|
||||
--hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \
|
||||
--hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \
|
||||
--hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \
|
||||
--hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \
|
||||
--hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \
|
||||
--hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \
|
||||
--hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \
|
||||
--hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \
|
||||
--hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \
|
||||
--hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \
|
||||
--hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \
|
||||
--hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \
|
||||
--hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \
|
||||
--hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \
|
||||
--hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \
|
||||
--hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \
|
||||
--hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \
|
||||
--hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \
|
||||
--hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \
|
||||
--hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \
|
||||
--hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \
|
||||
--hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \
|
||||
--hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \
|
||||
--hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \
|
||||
--hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50
|
||||
# via jinja2
|
||||
numpy==2.2.6 \
|
||||
--hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
|
||||
--hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
|
||||
--hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
|
||||
--hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
|
||||
--hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
|
||||
--hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
|
||||
--hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
|
||||
--hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
|
||||
--hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
|
||||
--hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
|
||||
--hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
|
||||
--hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
|
||||
--hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
|
||||
--hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
|
||||
--hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
|
||||
--hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
|
||||
--hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
|
||||
--hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
|
||||
--hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
|
||||
--hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
|
||||
--hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
|
||||
--hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
|
||||
--hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
|
||||
--hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
|
||||
--hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
|
||||
--hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
|
||||
--hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
|
||||
--hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
|
||||
--hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
|
||||
--hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
|
||||
--hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
|
||||
--hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
|
||||
--hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
|
||||
--hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
|
||||
--hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
|
||||
--hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
|
||||
--hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
|
||||
--hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
|
||||
--hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
|
||||
--hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
|
||||
--hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
|
||||
--hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
|
||||
--hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
|
||||
--hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
|
||||
--hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
|
||||
--hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
|
||||
--hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
|
||||
--hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
|
||||
--hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
|
||||
--hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
|
||||
--hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
|
||||
--hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
|
||||
--hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
|
||||
--hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
|
||||
--hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# opencv-python
|
||||
opencv-python==4.11.0.86 \
|
||||
--hash=sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4 \
|
||||
--hash=sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec \
|
||||
--hash=sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202 \
|
||||
--hash=sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a \
|
||||
--hash=sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d \
|
||||
--hash=sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b \
|
||||
--hash=sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66
|
||||
# via -r requirements.txt
|
||||
packaging==25.0 \
|
||||
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
|
||||
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
|
||||
# via
|
||||
# pytest
|
||||
# semiwrap
|
||||
pcpp==1.30 \
|
||||
--hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \
|
||||
--hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1
|
||||
# via cxxheaderparser
|
||||
pkgconf==2.4.3.post1 \
|
||||
--hash=sha256:101bed059939c26b04dfba1226a9c0ebf1f08b9bee98354797c2d887a08a2d7a \
|
||||
--hash=sha256:1f334bd2eaf2cb07feb09be439b62ecca1ac2a0aaa447587d5a31029bb43bf69 \
|
||||
--hash=sha256:36b7be7658296663d67151d2dbb5895721e6a66d5bcc903d7caae1ac6316456d \
|
||||
--hash=sha256:3b5a1905dd2f08396f1e5a8bab6d0c35e9cb7f3087f1a27f089dcc09ae126f09 \
|
||||
--hash=sha256:4346e011187ceff0856e1c472a759790b225856da68c60b806e051c84c6ac9ed \
|
||||
--hash=sha256:442b3aa06ddeb20e5cefc8cbc5811a02db128295a215f497d817cc0f0d358f71 \
|
||||
--hash=sha256:4e8fe5abadf9c64d4cae927445da5172310cdec300a9c3e49716a95e61848a5f \
|
||||
--hash=sha256:564a84be78f62605f39a8f45d5449a3549647e6488b8133b8a05281d4cba8aed \
|
||||
--hash=sha256:6f77ac67af2fac4947ab436e0b6f80db73cca22c87ad3abc6948e096a68370d1 \
|
||||
--hash=sha256:86857d46fef3c6ee1011a11fe20717803e9c40e004a1347a0876b6e39485288d \
|
||||
--hash=sha256:8b49ac5d034be5f5e22ec0dd8d6e40f0ae69974299bf84368f4dcffa1ffa5633 \
|
||||
--hash=sha256:a95610a629818290305860f666bab82b53039746a44e36de35ecf55275345e66 \
|
||||
--hash=sha256:b52a01db329f8541f9f9e7c69c48b62dbe326658fc67b66ebdfb4aeccc7ccc60 \
|
||||
--hash=sha256:defe70c329df7d7992b64a105e78d97f154727b595271fd97a70f3ce33b05478 \
|
||||
--hash=sha256:ec31ce85eab01f7a41d2c86a43827556fe8238f7c5b51ccca42bfd01762d84ca
|
||||
# via semiwrap
|
||||
pluggy==1.6.0 \
|
||||
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
|
||||
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
|
||||
# via pytest
|
||||
protobuf==5.28.3 \
|
||||
--hash=sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24 \
|
||||
--hash=sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535 \
|
||||
--hash=sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b \
|
||||
--hash=sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548 \
|
||||
--hash=sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584 \
|
||||
--hash=sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b \
|
||||
--hash=sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36 \
|
||||
--hash=sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135 \
|
||||
--hash=sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868 \
|
||||
--hash=sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687 \
|
||||
--hash=sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# grpcio-tools
|
||||
pybind11-stubgen==2.5.4 \
|
||||
--hash=sha256:8625f25da48cf96eea24ba7cae673b5f49b45847b6ef01eead60c4eb762fe5c5 \
|
||||
--hash=sha256:b6bd44a6d4ba55cef80bd8af92f1f8195b1c6bb0f7bd2f6d785c9530ce6bcae9
|
||||
# via semiwrap
|
||||
pygments==2.19.2 \
|
||||
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
|
||||
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
|
||||
# via pytest
|
||||
pytest==8.4.1 \
|
||||
--hash=sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7 \
|
||||
--hash=sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c
|
||||
# via -r requirements.txt
|
||||
pyyaml==6.0.2 \
|
||||
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
|
||||
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
|
||||
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
|
||||
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
|
||||
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
|
||||
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
|
||||
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
|
||||
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
|
||||
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
|
||||
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
|
||||
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
|
||||
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
|
||||
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
|
||||
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
|
||||
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
|
||||
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
|
||||
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
|
||||
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
|
||||
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
|
||||
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
|
||||
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
|
||||
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
|
||||
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
|
||||
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
|
||||
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
|
||||
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
|
||||
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
|
||||
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
|
||||
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
|
||||
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
|
||||
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
|
||||
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
|
||||
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
|
||||
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
|
||||
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
|
||||
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
|
||||
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
|
||||
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
|
||||
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
|
||||
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
|
||||
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
|
||||
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
|
||||
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
|
||||
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
|
||||
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
|
||||
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
|
||||
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
|
||||
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
|
||||
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
|
||||
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
|
||||
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
||||
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||
# via semiwrap
|
||||
ruamel-yaml==0.18.14 \
|
||||
--hash=sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2 \
|
||||
--hash=sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7
|
||||
# via semiwrap
|
||||
ruamel-yaml-clib==0.2.12 \
|
||||
--hash=sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b \
|
||||
--hash=sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4 \
|
||||
--hash=sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef \
|
||||
--hash=sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5 \
|
||||
--hash=sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3 \
|
||||
--hash=sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632 \
|
||||
--hash=sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6 \
|
||||
--hash=sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7 \
|
||||
--hash=sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680 \
|
||||
--hash=sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf \
|
||||
--hash=sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da \
|
||||
--hash=sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6 \
|
||||
--hash=sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a \
|
||||
--hash=sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01 \
|
||||
--hash=sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519 \
|
||||
--hash=sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6 \
|
||||
--hash=sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f \
|
||||
--hash=sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd \
|
||||
--hash=sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2 \
|
||||
--hash=sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52 \
|
||||
--hash=sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd \
|
||||
--hash=sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d \
|
||||
--hash=sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c \
|
||||
--hash=sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6 \
|
||||
--hash=sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb \
|
||||
--hash=sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a \
|
||||
--hash=sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969 \
|
||||
--hash=sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28 \
|
||||
--hash=sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d \
|
||||
--hash=sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e \
|
||||
--hash=sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45 \
|
||||
--hash=sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4 \
|
||||
--hash=sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12 \
|
||||
--hash=sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31 \
|
||||
--hash=sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642 \
|
||||
--hash=sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e \
|
||||
--hash=sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285 \
|
||||
--hash=sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed \
|
||||
--hash=sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1 \
|
||||
--hash=sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7 \
|
||||
--hash=sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3 \
|
||||
--hash=sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475 \
|
||||
--hash=sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5 \
|
||||
--hash=sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76 \
|
||||
--hash=sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987 \
|
||||
--hash=sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df
|
||||
# via ruamel-yaml
|
||||
semiwrap==0.1.8 \
|
||||
--hash=sha256:af5fe5aa3fb9c39b9924ab2f763f41a7a8128ffbaf743a3cb0c3bef6a30c8233 \
|
||||
--hash=sha256:e176f9f4cca2409a104fab7d14956e1e371ee36264c8478b78a2d142e104537d
|
||||
# via -r requirements.txt
|
||||
sphinxify==0.12 \
|
||||
--hash=sha256:3ec299e78babac7d3457f47bf263411b48e10b9c8add18d7159fa0327cc4a061 \
|
||||
--hash=sha256:ec97af947884bacd8e18f14ff2b6030b6da829a6a5bf7a32421b633b10c6f7e8
|
||||
# via semiwrap
|
||||
tomli==2.2.1 \
|
||||
--hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \
|
||||
--hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \
|
||||
--hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \
|
||||
--hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \
|
||||
--hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \
|
||||
--hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \
|
||||
--hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \
|
||||
--hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \
|
||||
--hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \
|
||||
--hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \
|
||||
--hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \
|
||||
--hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \
|
||||
--hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \
|
||||
--hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \
|
||||
--hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \
|
||||
--hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \
|
||||
--hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \
|
||||
--hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \
|
||||
--hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \
|
||||
--hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \
|
||||
--hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \
|
||||
--hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \
|
||||
--hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \
|
||||
--hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \
|
||||
--hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \
|
||||
--hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \
|
||||
--hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \
|
||||
--hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \
|
||||
--hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \
|
||||
--hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \
|
||||
--hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \
|
||||
--hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7
|
||||
# via
|
||||
# pytest
|
||||
# semiwrap
|
||||
tomli-w==1.2.0 \
|
||||
--hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \
|
||||
--hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021
|
||||
# via semiwrap
|
||||
toposort==1.10 \
|
||||
--hash=sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd \
|
||||
--hash=sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87
|
||||
# via semiwrap
|
||||
typing-extensions==4.13.2 \
|
||||
--hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \
|
||||
--hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef
|
||||
# via
|
||||
# exceptiongroup
|
||||
# semiwrap
|
||||
validobj==1.3 \
|
||||
--hash=sha256:0ddb2e73693763e2014620327486f9e458fcf1d016ce286a146111dc8493e298 \
|
||||
--hash=sha256:b5a6f79f76064dc1a4e3b2239bf40ea1c4f4ce8d742c9a78784174f784c9cb38
|
||||
# via semiwrap
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==80.9.0 \
|
||||
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
|
||||
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
|
||||
# via grpcio-tools
|
||||
@@ -29,3 +29,7 @@ build:linux --host_cxxopt=-Wno-missing-requires
|
||||
build:linux --host_cxxopt=-Wno-implicit-fallthrough
|
||||
|
||||
build:linux --host_per_file_copt=external/zlib/.*\.c@-Wno-deprecated-non-prototype
|
||||
|
||||
# Set soname. Needed for robotpy
|
||||
build:linux --features=set_soname
|
||||
build:linux --host_features=set_soname
|
||||
|
||||
85
shared/bazel/rules/robotpy/BUILD.bazel
Normal file
85
shared/bazel/rules/robotpy/BUILD.bazel
Normal file
@@ -0,0 +1,85 @@
|
||||
load("@allwpilib_pip_deps//:requirements.bzl", "requirement", "whl_requirement")
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
|
||||
load("@rules_python//python:pip.bzl", "whl_filegroup")
|
||||
|
||||
exports_files(["wrapper.py"])
|
||||
|
||||
py_library(
|
||||
name = "hack_pkgcfgs",
|
||||
srcs = ["hack_pkgcfgs.py"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = "generation_utils",
|
||||
srcs = ["generation_utils.py"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
requirement("semiwrap"),
|
||||
requirement("jinja2"),
|
||||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "generate_native_build_file",
|
||||
srcs = ["generate_native_build_file.py"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":generation_utils",
|
||||
":hack_pkgcfgs",
|
||||
requirement("semiwrap"),
|
||||
requirement("jinja2"),
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "jinja_templates",
|
||||
srcs = glob(["*.jinja2"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "generate_pybind_build_file",
|
||||
srcs = ["generate_pybind_build_file.py"],
|
||||
data = [
|
||||
":jinja_templates",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":generation_utils",
|
||||
":hack_pkgcfgs",
|
||||
requirement("semiwrap"),
|
||||
requirement("jinja2"),
|
||||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "wrapper",
|
||||
srcs = ["wrapper.py"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//shared/bazel/rules/robotpy:hack_pkgcfgs",
|
||||
requirement("semiwrap"),
|
||||
],
|
||||
)
|
||||
|
||||
whl_filegroup(
|
||||
name = "semiwrap_header_files",
|
||||
pattern = "semiwrap/include",
|
||||
whl = whl_requirement("semiwrap"),
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "semiwrap_headers",
|
||||
hdrs = [":semiwrap_header_files"],
|
||||
includes = ["semiwrap_header_files/semiwrap/include"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
whl_filegroup(
|
||||
name = "semiwrap_casters_files",
|
||||
pattern = "semiwrap/semiwrap.pybind11.json",
|
||||
visibility = ["//visibility:public"],
|
||||
whl = whl_requirement("semiwrap"),
|
||||
)
|
||||
94
shared/bazel/rules/robotpy/build_info_gen.bzl
Normal file
94
shared/bazel/rules/robotpy/build_info_gen.bzl
Normal file
@@ -0,0 +1,94 @@
|
||||
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
|
||||
load("//shared/bazel/rules/robotpy:compatibility_select.bzl", "robotpy_compatibility_select")
|
||||
|
||||
def generate_robotpy_native_wrapper_build_info(name, pyproject_toml, third_party_dirs = []):
|
||||
"""
|
||||
This function will generate the bazel file necessary to declare a library that wraps a standard allwpilib library.
|
||||
|
||||
Params:
|
||||
pyproject_toml - Path to the native library wrappers definition file
|
||||
third_party_dirs - Any directories under src/main/native/thirdparty that should be used by semiwrap
|
||||
"""
|
||||
cmd = "$(location //shared/bazel/rules/robotpy:generate_native_build_file) --output_file=$(OUTS)"
|
||||
cmd += " --project_cfg=$(location " + pyproject_toml + ")"
|
||||
if third_party_dirs:
|
||||
cmd += " --third_party_dirs "
|
||||
for d in third_party_dirs:
|
||||
cmd += " " + d
|
||||
native.genrule(
|
||||
name = "{}.gen_build_info".format(name),
|
||||
tools = ["//shared/bazel/rules/robotpy:generate_native_build_file"],
|
||||
srcs = [pyproject_toml],
|
||||
outs = ["{}-generated_build_info.bzl".format(name)],
|
||||
cmd = cmd,
|
||||
tags = ["robotpy"],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
)
|
||||
|
||||
write_source_files(
|
||||
name = "{}.generate_build_info".format(name),
|
||||
files = {
|
||||
"robotpy_native_build_info.bzl": "{}-generated_build_info.bzl".format(name),
|
||||
},
|
||||
visibility = ["//visibility:public"],
|
||||
suggested_update_target = "//:write_robotpy_generated_native_files",
|
||||
tags = ["robotpy"],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
)
|
||||
|
||||
def generate_robotpy_pybind_build_info(
|
||||
name,
|
||||
package_root_file,
|
||||
yaml_files = [],
|
||||
pkgcfgs = [],
|
||||
additional_srcs = [],
|
||||
generated_file_name = "robotpy_pybind_build_info.bzl",
|
||||
pyproject_toml = "src/main/python/pyproject.toml",
|
||||
stripped_include_prefix = None,
|
||||
yml_prefix = None):
|
||||
"""
|
||||
This function will generate the bazel file necessary to build a pybind library with all of its extensions.
|
||||
|
||||
Params:
|
||||
package_root_file - An __init__.py file used to key the semiwrap wrappers on the project root.
|
||||
yaml_files - All of the yaml files used by semi wrap to run library wrapping
|
||||
pkgcfgs - Local files used to trick semiwrap into thinking a library is installed
|
||||
additional_srcs - Any additional sources needed by the semiwrap process
|
||||
generated_file_name - Indicates the path of the auto-generated file
|
||||
pyproject_toml - Location of the pyproject.toml file that defines this project
|
||||
yml_prefix - Optional. Used in the event that the yml files are in a non-standard location
|
||||
"""
|
||||
|
||||
cmd = "$(location //shared/bazel/rules/robotpy:generate_pybind_build_file) --project_file=$(location " + pyproject_toml + ") --output_file=$(OUTS)"
|
||||
|
||||
cmd += " --package_root_file=" + package_root_file
|
||||
if stripped_include_prefix:
|
||||
cmd += " --stripped_include_prefix=" + stripped_include_prefix
|
||||
if yml_prefix:
|
||||
cmd += " --yml_prefix=" + yml_prefix
|
||||
|
||||
if pkgcfgs:
|
||||
cmd += " --pkgcfgs "
|
||||
for x in pkgcfgs:
|
||||
cmd += " $(locations " + x + ")"
|
||||
|
||||
native.genrule(
|
||||
name = "{}.gen_build_info".format(name),
|
||||
tools = ["//shared/bazel/rules/robotpy:generate_pybind_build_file"],
|
||||
srcs = [pyproject_toml, package_root_file] + yaml_files + pkgcfgs + additional_srcs + ["//shared/bazel/rules/robotpy:jinja_templates"],
|
||||
outs = ["{}-generated_build_info.bzl".format(name)],
|
||||
cmd = cmd,
|
||||
tags = ["robotpy"],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
)
|
||||
|
||||
write_source_files(
|
||||
name = "{}.generate_build_info".format(name),
|
||||
files = {
|
||||
generated_file_name: "{}-generated_build_info.bzl".format(name),
|
||||
},
|
||||
suggested_update_target = "//:write_robotpy_generated_pybind_files",
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
)
|
||||
6
shared/bazel/rules/robotpy/compatibility_select.bzl
Normal file
6
shared/bazel/rules/robotpy/compatibility_select.bzl
Normal file
@@ -0,0 +1,6 @@
|
||||
def robotpy_compatibility_select():
|
||||
return select({
|
||||
"@bazel_tools//src/conditions:windows": ["@platforms//:incompatible"],
|
||||
"@rules_bzlmodrio_toolchains//constraints/is_systemcore:systemcore": ["@platforms//:incompatible"],
|
||||
"//conditions:default": [],
|
||||
})
|
||||
110
shared/bazel/rules/robotpy/generate_native_build_file.py
Normal file
110
shared/bazel/rules/robotpy/generate_native_build_file.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import argparse
|
||||
import json
|
||||
|
||||
import tomli
|
||||
from jinja2 import BaseLoader, Environment
|
||||
|
||||
from shared.bazel.rules.robotpy.generation_utils import (
|
||||
fixup_python_dep_name,
|
||||
fixup_root_package_name,
|
||||
fixup_shared_lib_name,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--project_cfg")
|
||||
parser.add_argument("--output_file")
|
||||
parser.add_argument("--third_party_dirs", nargs="+")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.project_cfg, "rb") as fp:
|
||||
raw_config = tomli.load(fp)
|
||||
|
||||
def double_quotes(data):
|
||||
if data:
|
||||
return json.dumps(data)
|
||||
return None
|
||||
|
||||
def get_pc_dep(library):
|
||||
base_project = library.replace("robotpy-native-", "")
|
||||
wpilib_project = fixup_root_package_name(base_project)
|
||||
return f"//{wpilib_project}:native/{base_project}/{library}.pc"
|
||||
|
||||
def get_python_dep(library):
|
||||
base_project = library.replace("robotpy-native-", "")
|
||||
wpilib_project = fixup_root_package_name(base_project)
|
||||
return f"//{fixup_root_package_name(wpilib_project)}:{fixup_python_dep_name(library)}"
|
||||
|
||||
env = Environment(loader=BaseLoader)
|
||||
env.filters["double_quotes"] = double_quotes
|
||||
env.filters["get_pc_dep"] = get_pc_dep
|
||||
env.filters["get_python_dep"] = get_python_dep
|
||||
template = env.from_string(BUILD_FILE_TEMPLATE)
|
||||
|
||||
nativelib_config = raw_config["tool"]["hatch"]["build"]["hooks"]["nativelib"]
|
||||
project_name = nativelib_config["pcfile"][0]["name"]
|
||||
root_package = fixup_root_package_name(project_name)
|
||||
shared_library_name = fixup_shared_lib_name(project_name)
|
||||
with open(args.output_file, "w") as f:
|
||||
f.write(
|
||||
template.render(
|
||||
raw_project_config=raw_config["project"],
|
||||
nativelib_config=nativelib_config,
|
||||
root_package=root_package,
|
||||
shared_library_name=shared_library_name,
|
||||
third_party_dirs=args.third_party_dirs or [],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
BUILD_FILE_TEMPLATE = """# THIS FILE IS AUTO GENERATED
|
||||
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "native_wrappery_library")
|
||||
|
||||
def define_native_wrapper(name, pyproject_toml = None):
|
||||
copy_to_directory(
|
||||
name = "{}.copy_headers".format(name),
|
||||
srcs = native.glob(["src/main/native/include/**"]) + native.glob(["src/generated/main/native/include/**"], allow_empty = True){% if third_party_dirs %} + native.glob([
|
||||
{%- for dir in third_party_dirs %}
|
||||
"src/main/native/thirdparty/{{dir}}/include/**",
|
||||
{%- endfor %}
|
||||
]){%- endif %},
|
||||
out = "native/{{nativelib_config.pcfile[0].name}}/include",
|
||||
root_paths = ["src/main/native/include/"],
|
||||
replace_prefixes = {
|
||||
"{{root_package}}/src/generated/main/native/include": "",
|
||||
"{{root_package}}/src/main/native/include": "",
|
||||
{%- for dir in third_party_dirs %}
|
||||
"{{root_package}}/src/main/native/thirdparty/{{dir}}/include": "",
|
||||
{%- endfor %}
|
||||
},
|
||||
verbose = False,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
native_wrappery_library(
|
||||
name = name,
|
||||
pyproject_toml = pyproject_toml or "src/main/python/native-pyproject.toml",
|
||||
libinit_file = "native/{{nativelib_config.pcfile[0].name}}/_init_{{raw_project_config.name.replace("-", "_")}}.py",
|
||||
pc_file = "native/{{nativelib_config.pcfile[0].name}}/{{raw_project_config.name}}.pc",
|
||||
pc_deps = [
|
||||
{%- for dep in nativelib_config.pcfile[0].requires | sort %}
|
||||
"{{dep | get_pc_dep}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
deps = [
|
||||
{%- for dep in nativelib_config.pcfile[0].requires | sort %}
|
||||
"{{dep | get_python_dep}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
headers = "{}.copy_headers".format(name),
|
||||
native_shared_library = "shared/{{shared_library_name}}",
|
||||
install_path = "native/{{nativelib_config.pcfile[0].name}}/",
|
||||
)
|
||||
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
428
shared/bazel/rules/robotpy/generate_pybind_build_file.py
Normal file
428
shared/bazel/rules/robotpy/generate_pybind_build_file.py
Normal file
@@ -0,0 +1,428 @@
|
||||
import argparse
|
||||
import collections
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
from typing import Dict, List, Union
|
||||
|
||||
import jinja2
|
||||
import tomli
|
||||
from jinja2 import BaseLoader, Environment
|
||||
from semiwrap.makeplan import (
|
||||
BuildTarget,
|
||||
BuildTargetOutput,
|
||||
CppMacroValue,
|
||||
Entrypoint,
|
||||
ExtensionModule,
|
||||
LocalDependency,
|
||||
makeplan,
|
||||
)
|
||||
from semiwrap.pkgconf_cache import PkgconfCache
|
||||
from semiwrap.pyproject import PyProject
|
||||
|
||||
from shared.bazel.rules.robotpy.generation_utils import (
|
||||
fixup_native_lib_name,
|
||||
fixup_python_dep_name,
|
||||
fixup_root_package_name,
|
||||
fixup_shared_lib_name,
|
||||
)
|
||||
from shared.bazel.rules.robotpy.hack_pkgcfgs import hack_pkgconfig
|
||||
|
||||
|
||||
class HeaderToDatConfig:
|
||||
def __init__(self, header_to_dat_args: BuildTarget):
|
||||
includes = []
|
||||
defines = []
|
||||
|
||||
idx = 0
|
||||
while True:
|
||||
if header_to_dat_args.args[idx] == "-I":
|
||||
includes.append(header_to_dat_args.args[idx + 1])
|
||||
elif header_to_dat_args.args[idx] == "-D":
|
||||
defines.append(header_to_dat_args.args[idx + 1])
|
||||
else:
|
||||
break
|
||||
idx += 2
|
||||
if header_to_dat_args.args[idx] == "--cpp":
|
||||
idx += 2
|
||||
|
||||
args = header_to_dat_args.args[idx:]
|
||||
self.class_name = args[0]
|
||||
self.yml_file = args[1].path
|
||||
self.defines = defines
|
||||
|
||||
include_root = str(args[3])
|
||||
if "native" in include_root:
|
||||
|
||||
root_dir = pathlib.Path(
|
||||
include_root[: include_root.find("__main__/") + len("__main__/")]
|
||||
)
|
||||
base_include_root = pathlib.Path(*args[3].relative_to(root_dir).parts[3:])
|
||||
base_include_file = args[2].relative_to(include_root)
|
||||
base_library = re.search("native/(.*?)/", include_root).groups(1)[0]
|
||||
|
||||
self.include_file = f"$(execpath :{fixup_native_lib_name('robotpy-native-' + base_library)}.copy_headers)/{base_include_file}"
|
||||
self.include_root = f"$(execpath :{fixup_native_lib_name('robotpy-native-' + base_library)}.copy_headers)"
|
||||
else:
|
||||
root_dir = pathlib.Path(
|
||||
include_root[: include_root.find("__main__/") + len("__main__/")]
|
||||
)
|
||||
if root_dir.is_absolute():
|
||||
self.include_file = args[2].relative_to(root_dir)
|
||||
self.include_root = args[3].relative_to(root_dir)
|
||||
else:
|
||||
self.include_file = args[2]
|
||||
self.include_root = args[3]
|
||||
# type casters = 4
|
||||
# dat file = 5
|
||||
# d file = 6
|
||||
# compiler info = 7
|
||||
|
||||
self.templates = []
|
||||
self.trampolines = []
|
||||
|
||||
args = args[8:]
|
||||
assert 0 == len(args)
|
||||
|
||||
|
||||
class ResolveCastersConfig:
|
||||
def __init__(self, item: BuildTarget):
|
||||
self.pkl_file = item.args[0].name
|
||||
self.dep_file = item.args[1].name
|
||||
# semiwrap casters = 2
|
||||
self.caster_files = []
|
||||
caster_deps = set()
|
||||
|
||||
for dep_path in item.args[3:]:
|
||||
if isinstance(dep_path, BuildTargetOutput):
|
||||
output_file = dep_path.target.args[2]
|
||||
caster_deps.add(
|
||||
f":src/main/python/{dep_path.target.install_path}/{output_file.name}"
|
||||
)
|
||||
else:
|
||||
relevant_parts = dep_path.parts[3:]
|
||||
caster_deps.add(
|
||||
f"//{relevant_parts[0]}:" + "/".join(relevant_parts[1:])
|
||||
)
|
||||
|
||||
self.caster_deps = sorted(caster_deps)
|
||||
|
||||
|
||||
class GenLibInitPyConfig:
|
||||
def __init__(self, item: BuildTarget):
|
||||
self.output_file = item.args[0].name
|
||||
self.modules = item.args[1:]
|
||||
self.install_path = item.install_path
|
||||
|
||||
|
||||
class GenPkgConfConfig:
|
||||
def __init__(self, item: BuildTarget):
|
||||
self.module_pkg_name = item.args[0]
|
||||
self.pkg_name = item.args[1]
|
||||
self.project_file = item.args[2].path
|
||||
self.output_file = item.args[3].name
|
||||
# --libinit-py = 4
|
||||
self.libinit_py = item.args[5]
|
||||
|
||||
assert 0 == len(item.args[6:])
|
||||
|
||||
self.install_path = item.install_path
|
||||
|
||||
|
||||
class GenModInitHpp:
|
||||
def __init__(self, item: BuildTarget):
|
||||
self.lib_name = item.args[0]
|
||||
self.output_file = item.args[1].name
|
||||
idx = 2
|
||||
while idx < len(item.args):
|
||||
if item.args[idx].command != "header2dat":
|
||||
break
|
||||
idx += 1
|
||||
|
||||
assert 0 == len(item.args[idx:])
|
||||
|
||||
|
||||
class PublishCastersConfig:
|
||||
def __init__(self, projectcfg, item: BuildTarget):
|
||||
self.project_file = item.args[0].path
|
||||
self.casters_name = item.args[1]
|
||||
self.json_output = item.args[2].name
|
||||
self.pc_output = item.args[3].name
|
||||
assert 0 == len(item.args[4:])
|
||||
|
||||
self.install_path = item.install_path
|
||||
|
||||
self.include_paths = []
|
||||
caster_cfg = projectcfg.export_type_casters[self.casters_name]
|
||||
|
||||
for inc_dir in caster_cfg.includedir:
|
||||
self.include_paths.append(f"src/main/python/{inc_dir}")
|
||||
|
||||
|
||||
class BazelExtensionModule:
|
||||
def __init__(
|
||||
self,
|
||||
extension_module: ExtensionModule,
|
||||
additional_extension_targets: Dict[str, BuildTarget],
|
||||
):
|
||||
self.name = extension_module.name
|
||||
self.package_name = extension_module.package_name
|
||||
self.install_path = extension_module.install_path
|
||||
|
||||
self.generation_data = self._extract_header_generation(extension_module.sources)
|
||||
self.resolve_casters = ResolveCastersConfig(
|
||||
additional_extension_targets["resolve-casters"]
|
||||
)
|
||||
self.gen_libinit = GenLibInitPyConfig(
|
||||
additional_extension_targets["gen-libinit-py"]
|
||||
)
|
||||
self.gen_pkgconf = GenPkgConfConfig(additional_extension_targets["gen-pkgconf"])
|
||||
self.gen_modinit = GenModInitHpp(
|
||||
additional_extension_targets["gen-modinit-hpp"]
|
||||
)
|
||||
|
||||
self.pkgcache = PkgconfCache()
|
||||
|
||||
all_dependencies = set()
|
||||
|
||||
for d in extension_module.depends:
|
||||
if isinstance(d, LocalDependency):
|
||||
all_dependencies.add(d.name)
|
||||
self._collect_local_dependency_names(d, all_dependencies)
|
||||
|
||||
native_wrapper_dependencies = set()
|
||||
local_extension_dependencies = set()
|
||||
dynamic_dependencies = set()
|
||||
for dep_name in all_dependencies:
|
||||
if "native" in dep_name:
|
||||
|
||||
transative_deps = set()
|
||||
self._get_transative_native_dependencies(dep_name, transative_deps)
|
||||
for d in transative_deps:
|
||||
base_library = fixup_root_package_name(
|
||||
d.replace("robotpy-native-", "")
|
||||
)
|
||||
native_wrapper_dependencies.add(
|
||||
f"//{base_library}:{fixup_native_lib_name(d)}.copy_headers"
|
||||
)
|
||||
elif "-casters" in dep_name:
|
||||
base_library = dep_name.split("-")[0]
|
||||
local_extension_dependencies.add(f"//{base_library}:{dep_name}")
|
||||
else:
|
||||
base_library = fixup_root_package_name(dep_name.split("_")[0])
|
||||
local_extension_dependencies.add(
|
||||
f"//{base_library}:{fixup_shared_lib_name(base_library)}"
|
||||
)
|
||||
dynamic_dependencies.add(
|
||||
f"//{base_library}:shared/{fixup_shared_lib_name(base_library)}"
|
||||
)
|
||||
if dep_name != self.name:
|
||||
local_extension_dependencies.add(
|
||||
f"//{base_library}:{dep_name}_pybind_library"
|
||||
)
|
||||
|
||||
self.native_wrapper_dependencies = sorted(native_wrapper_dependencies)
|
||||
self.local_extension_dependencies = sorted(local_extension_dependencies)
|
||||
self.dynamic_dependencies = sorted(dynamic_dependencies)
|
||||
|
||||
def get_defines(self):
|
||||
defines = set()
|
||||
for h2d_def in self.generation_data.values():
|
||||
defines.update(h2d_def.defines)
|
||||
return sorted(defines)
|
||||
|
||||
def _get_transative_native_dependencies(self, dep_name, transative_deps):
|
||||
entry = self.pkgcache.get(dep_name)
|
||||
transative_deps.add(dep_name)
|
||||
for req in entry.requires:
|
||||
if req not in transative_deps:
|
||||
transative_deps.add(req)
|
||||
self._get_transative_native_dependencies(req, transative_deps)
|
||||
|
||||
def _collect_local_dependency_names(self, dep, all_dependencies):
|
||||
for child_dep in dep.depends:
|
||||
if isinstance(child_dep, str):
|
||||
if child_dep != "semiwrap":
|
||||
all_dependencies.add(child_dep)
|
||||
elif isinstance(child_dep, LocalDependency):
|
||||
all_dependencies.add(child_dep.name)
|
||||
self._collect_local_dependency_names(child_dep, all_dependencies)
|
||||
else:
|
||||
raise
|
||||
|
||||
def _extract_header_generation(self, sources) -> Dict[str, HeaderToDatConfig]:
|
||||
generation_data: Dict[str, HeaderToDatConfig] = {}
|
||||
|
||||
def get_h2d_config(target_info: BuildTarget) -> HeaderToDatConfig:
|
||||
config = HeaderToDatConfig(target_info)
|
||||
if config.class_name not in generation_data:
|
||||
generation_data[config.class_name] = config
|
||||
return generation_data[config.class_name]
|
||||
|
||||
for source in sources:
|
||||
if source.command == "dat2cpp":
|
||||
h2d_config = get_h2d_config(source.args[0])
|
||||
elif source.command == "dat2trampoline":
|
||||
h2d_config = get_h2d_config(source.args[0])
|
||||
name, out_file = source.args[1:]
|
||||
h2d_config.trampolines.append((name, out_file.name))
|
||||
elif source.command == "dat2tmplcpp":
|
||||
h2d_config = get_h2d_config(source.args[0])
|
||||
name, out_file = source.args[1:]
|
||||
h2d_config.templates.append((out_file.name[:-4], name))
|
||||
elif source.command == "dat2tmplhpp":
|
||||
# Handled by dat2tmplcpp
|
||||
continue
|
||||
elif source.command == "gen-modinit-hpp":
|
||||
# Handled elsewhere
|
||||
continue
|
||||
else:
|
||||
raise Exception("Unknown command", source.command)
|
||||
|
||||
return generation_data
|
||||
|
||||
|
||||
def generate_pybind_build_file(
|
||||
pkgcfgs: List[pathlib.Path],
|
||||
project_file: pathlib.Path,
|
||||
package_root_file: str,
|
||||
stripped_include_prefix: str,
|
||||
yml_prefix: Union[str, None],
|
||||
output_file: pathlib.Path,
|
||||
):
|
||||
project_dir = project_file.parent
|
||||
plan = makeplan(project_dir)
|
||||
|
||||
hack_pkgconfig(pkgcfgs)
|
||||
|
||||
extension_modules = []
|
||||
entry_points = collections.defaultdict(list)
|
||||
|
||||
pyproject = PyProject(project_file)
|
||||
projectcfg = pyproject.project
|
||||
|
||||
# Cache built up for an extension module. Gets reset when an ExtensionModule is encountered
|
||||
additional_extension_targets: Dict[str, BuildTarget] = {}
|
||||
publish_casters_targets = []
|
||||
|
||||
for item in plan:
|
||||
if isinstance(item, ExtensionModule):
|
||||
extension_modules.append(
|
||||
BazelExtensionModule(item, additional_extension_targets)
|
||||
)
|
||||
additional_extension_targets = {}
|
||||
elif isinstance(item, BuildTarget):
|
||||
if item.command in [
|
||||
"resolve-casters",
|
||||
"gen-libinit-py",
|
||||
"gen-pkgconf",
|
||||
"gen-modinit-hpp",
|
||||
]:
|
||||
if item.command in additional_extension_targets:
|
||||
raise Exception(f"Repeated target {item.command}")
|
||||
additional_extension_targets[item.command] = item
|
||||
elif item.command in [
|
||||
"header2dat",
|
||||
"dat2cpp",
|
||||
"dat2tmplcpp",
|
||||
"dat2tmplhpp",
|
||||
"dat2trampoline",
|
||||
"make-pyi",
|
||||
]:
|
||||
pass
|
||||
elif item.command == "publish-casters":
|
||||
publish_casters_targets.append(PublishCastersConfig(projectcfg, item))
|
||||
else:
|
||||
raise Exception(f"Unhandled build target {item.command}")
|
||||
elif isinstance(item, Entrypoint):
|
||||
entry_points[item.group].append(f"{item.name} = {item.package}")
|
||||
elif isinstance(item, LocalDependency):
|
||||
pass
|
||||
elif isinstance(item, CppMacroValue):
|
||||
pass
|
||||
else:
|
||||
raise Exception(f"Unknown item {type(item)}")
|
||||
|
||||
with open(project_file, "rb") as fp:
|
||||
raw_config = tomli.load(fp)
|
||||
|
||||
try:
|
||||
top_level_name = raw_config["tool"]["hatch"]["build"]["targets"]["wheel"][
|
||||
"packages"
|
||||
]
|
||||
except KeyError:
|
||||
top_level_name = [raw_config["project"]["name"]]
|
||||
assert len(top_level_name) == 1
|
||||
top_level_name = top_level_name[0]
|
||||
|
||||
template_file = "shared/bazel/rules/robotpy/pybind_build_file_template.jinja2"
|
||||
with open(template_file, "r") as f:
|
||||
template_contents = f.read()
|
||||
|
||||
def jsonify(item):
|
||||
if isinstance(item, jinja2.runtime.Undefined):
|
||||
return "None"
|
||||
return json.dumps(item)
|
||||
|
||||
def target_from_python_dep(python_dep):
|
||||
if "native" in python_dep:
|
||||
base_library = python_dep.replace("robotpy-native-", "")
|
||||
return f"//{fixup_root_package_name(base_library)}:{fixup_python_dep_name(python_dep)}"
|
||||
else:
|
||||
base_library = python_dep.replace("robotpy-", "")
|
||||
return f"//{fixup_root_package_name(base_library)}:{fixup_python_dep_name(python_dep)}"
|
||||
|
||||
python_deps = []
|
||||
if "dependencies" in raw_config["project"]:
|
||||
for d in raw_config["project"]["dependencies"]:
|
||||
if "robotpy-cli" in d:
|
||||
continue
|
||||
pd = target_from_python_dep(d.split("==")[0])
|
||||
python_deps.append(pd)
|
||||
|
||||
env = Environment(loader=BaseLoader)
|
||||
env.filters["jsonify"] = jsonify
|
||||
template = env.from_string(template_contents)
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
f.write(
|
||||
template.render(
|
||||
extension_modules=extension_modules,
|
||||
top_level_name=top_level_name,
|
||||
publish_casters_targets=publish_casters_targets,
|
||||
python_deps=sorted(python_deps),
|
||||
stripped_include_prefix=stripped_include_prefix,
|
||||
yml_prefix=yml_prefix,
|
||||
package_root_file=package_root_file,
|
||||
raw_project_config=raw_config["project"],
|
||||
entry_points=entry_points,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--project_file", type=pathlib.Path, required=True)
|
||||
parser.add_argument("--output_file", type=pathlib.Path, required=True)
|
||||
parser.add_argument(
|
||||
"--stripped_include_prefix", type=str, default="src/main/python"
|
||||
)
|
||||
parser.add_argument("--yml_prefix", type=str)
|
||||
parser.add_argument("--package_root_file", type=str)
|
||||
parser.add_argument("--pkgcfgs", type=pathlib.Path, nargs="+")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
generate_pybind_build_file(
|
||||
args.pkgcfgs,
|
||||
args.project_file,
|
||||
args.package_root_file,
|
||||
args.stripped_include_prefix,
|
||||
args.yml_prefix,
|
||||
args.output_file,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
42
shared/bazel/rules/robotpy/generation_utils.py
Normal file
42
shared/bazel/rules/robotpy/generation_utils.py
Normal file
@@ -0,0 +1,42 @@
|
||||
def fixup_root_package_name(name):
|
||||
if name == "wpihal":
|
||||
return "hal"
|
||||
if name == "wpilib":
|
||||
return "wpilibc"
|
||||
if name == "wpilog":
|
||||
return "datalog"
|
||||
if name == "xrp":
|
||||
return "xrpVendordep"
|
||||
if name == "romi":
|
||||
return "romiVendordep"
|
||||
if name == "pyntcore":
|
||||
return "ntcore"
|
||||
return name
|
||||
|
||||
|
||||
def fixup_native_lib_name(name):
|
||||
return name
|
||||
|
||||
|
||||
def fixup_shared_lib_name(name):
|
||||
if name == "wpihal":
|
||||
return "wpiHal"
|
||||
if name == "hal":
|
||||
return "wpiHal"
|
||||
if name == "wpilib":
|
||||
return "wpilibc"
|
||||
if name == "xrp":
|
||||
return "xrpVendordep"
|
||||
if name == "romi":
|
||||
return "romiVendordep"
|
||||
return name
|
||||
|
||||
|
||||
def fixup_python_dep_name(name):
|
||||
if name == "robotpy-datalog":
|
||||
return "robotpy-wpilog"
|
||||
if name == "robotpy-ntcore":
|
||||
return "pyntcore"
|
||||
if name == "wpilib":
|
||||
return "robotpy-wpilib"
|
||||
return name
|
||||
19
shared/bazel/rules/robotpy/hack_pkgcfgs.py
Normal file
19
shared/bazel/rules/robotpy/hack_pkgcfgs.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
import pathlib
|
||||
from typing import List
|
||||
|
||||
|
||||
def hack_pkgconfig(pkgcfgs: List[pathlib.Path]):
|
||||
"""
|
||||
This will place the given files in the PKG_CONFIG_PATH in such a way that will trick
|
||||
semiwrap into thinking the libraries have been installed
|
||||
"""
|
||||
|
||||
pkg_config_paths = os.environ.get("PKG_CONFIG_PATH", "").split(os.pathsep)
|
||||
|
||||
if pkgcfgs:
|
||||
for pc in pkgcfgs:
|
||||
# pkg_config_paths.append(str(pc.parent.absolute()))
|
||||
pkg_config_paths.append(str(pc.parent))
|
||||
|
||||
os.environ["PKG_CONFIG_PATH"] = os.pathsep.join(pkg_config_paths)
|
||||
12
shared/bazel/rules/robotpy/hatchlib_native_port/BUILD.bazel
Normal file
12
shared/bazel/rules/robotpy/hatchlib_native_port/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
||||
load("@allwpilib_pip_deps//:requirements.bzl", "requirement")
|
||||
load("@rules_python//python:defs.bzl", "py_binary")
|
||||
|
||||
py_binary(
|
||||
name = "generate_native_lib_files",
|
||||
srcs = glob(["*.py"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//shared/bazel/rules/robotpy:hack_pkgcfgs",
|
||||
requirement("semiwrap"),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
This is a port of [hatch_nativelib](https://github.com/robotpy/hatch-nativelib/tree/main/src/hatch_nativelib). That tool is not librar-icized and required a fork.
|
||||
106
shared/bazel/rules/robotpy/hatchlib_native_port/config.py
Normal file
106
shared/bazel/rules/robotpy/hatchlib_native_port/config.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import dataclasses
|
||||
import pathlib
|
||||
import typing as T
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PcFileConfig:
|
||||
"""
|
||||
Contents of [[tool.hatch.build.hooks.nativelib.pcfile]] items
|
||||
"""
|
||||
|
||||
pcfile: str
|
||||
"""
|
||||
File to write pkgconf file to (relative to pyproject.toml)
|
||||
"""
|
||||
|
||||
description: T.Optional[str] = None
|
||||
"""Description of this package. If not specified, uses the first line of the package description."""
|
||||
|
||||
name: T.Optional[str] = None
|
||||
"""Name of this package. If not specified, is basename of pcfile without extension"""
|
||||
|
||||
version: T.Optional[str] = None
|
||||
"""If not specified, set to package version"""
|
||||
|
||||
includedir: T.Optional[str] = None
|
||||
"""Where include files can be found (relative to pyproject.toml)"""
|
||||
|
||||
libdir: T.Optional[str] = None
|
||||
"""Where the library is located. If not specified, it is next to pcfile"""
|
||||
|
||||
shared_libraries: T.Optional[T.List[str]] = None
|
||||
"""Name of shared libraries located in libdir (without extension)"""
|
||||
|
||||
libs_private: T.Optional[str] = None
|
||||
"""The link flags for private libraries not exposed to applications"""
|
||||
|
||||
requires: T.Optional[T.List[str]] = None
|
||||
"""
|
||||
Names of other packages this package requires. They must be installed
|
||||
at build time.
|
||||
"""
|
||||
|
||||
requires_private: T.Optional[T.List[str]] = None
|
||||
"""
|
||||
Names of private packages this package requires. They must be installed
|
||||
at build time.
|
||||
"""
|
||||
|
||||
extra_cflags: T.Optional[str] = None
|
||||
"""A list of extra compiler flags to be added to Cflags after header search path"""
|
||||
|
||||
extra_link_flags: T.Optional[str] = None
|
||||
"""A list of extra link flags to be added to Libs"""
|
||||
|
||||
variables: T.Optional[T.Dict[str, str]] = None
|
||||
"""
|
||||
Custom variables to add to the generated file. Prefix, libdir, includedir must not be specified."""
|
||||
|
||||
init_module: str = "auto"
|
||||
"""
|
||||
If specified, the name of the python module that will be written next to
|
||||
the .pc file which will load the shared_libraries
|
||||
"""
|
||||
|
||||
enable_if: T.Optional[str] = None
|
||||
"""
|
||||
This is a PEP 508 environment marker specification.
|
||||
|
||||
This pcfile will only be generated if the environment marker matches the current
|
||||
build environment
|
||||
"""
|
||||
|
||||
def get_name(self) -> str:
|
||||
if self.name:
|
||||
return self.name
|
||||
return self.get_pc_path().name[:-3]
|
||||
|
||||
def get_out_path(self) -> pathlib.Path:
|
||||
return self.get_pc_path().parent
|
||||
|
||||
def get_pc_path(self) -> pathlib.Path:
|
||||
pc_path = pathlib.PurePosixPath(self.pcfile)
|
||||
if pc_path.is_absolute():
|
||||
raise ValueError(f"pcfile must not be absolute (is {pc_path})")
|
||||
if not pc_path.name.endswith(".pc"):
|
||||
raise ValueError(f"pcfile must end with .pc (is {pc_path})")
|
||||
return pathlib.Path(pc_path)
|
||||
|
||||
def get_init_module(self) -> str:
|
||||
if self.init_module == "auto":
|
||||
name = self.get_pc_path().name[:-3]
|
||||
name = name.replace("-", "_").replace(".", "_")
|
||||
module = f"_init_{name}"
|
||||
else:
|
||||
module = self.init_module
|
||||
|
||||
if not module.isidentifier():
|
||||
raise ValueError(
|
||||
f"init_module must be a valid python identifier (got {module})"
|
||||
)
|
||||
return module
|
||||
|
||||
def get_init_module_path(self) -> pathlib.Path:
|
||||
module = self.get_init_module()
|
||||
return self.get_out_path() / f"{module}.py"
|
||||
@@ -0,0 +1,318 @@
|
||||
import functools
|
||||
import pathlib
|
||||
import platform
|
||||
import sys
|
||||
import typing as T
|
||||
|
||||
import pkgconf
|
||||
import tomli
|
||||
from packaging.markers import Marker
|
||||
|
||||
from shared.bazel.rules.robotpy.hack_pkgcfgs import hack_pkgconfig
|
||||
from shared.bazel.rules.robotpy.hatchlib_native_port.config import PcFileConfig
|
||||
from shared.bazel.rules.robotpy.hatchlib_native_port.validate import parse_input
|
||||
|
||||
# Port of https://github.com/robotpy/hatch-nativelib/blob/main/src/hatch_nativelib/plugin.py
|
||||
|
||||
INITPY_VARNAME = "pkgconf_pypi_initpy"
|
||||
|
||||
platform_sys = platform.system()
|
||||
is_windows = platform_sys == "Windows"
|
||||
is_macos = platform_sys == "Darwin"
|
||||
|
||||
|
||||
class NativelibHook:
|
||||
def __init__(self, output_pcfile, output_libinit, config, metadata):
|
||||
self.output_pcfile = output_pcfile
|
||||
self.output_libinit = output_libinit
|
||||
|
||||
self.config = config
|
||||
self.root_pth = output_pcfile.parent.parent.parent
|
||||
self.metadata = metadata
|
||||
|
||||
def initialize(self):
|
||||
for pcfg in self._pcfiles:
|
||||
self._generate_pcfile(pcfg, {})
|
||||
|
||||
def _get_pkg_from_path(self, path: pathlib.Path) -> str:
|
||||
rel = path.relative_to(self.root_pth)
|
||||
return str(rel).replace("/", ".").replace("\\", ".")
|
||||
|
||||
def _generate_pcfile(
|
||||
self, pcfg: PcFileConfig, build_data: T.Dict[str, T.Any]
|
||||
) -> pathlib.Path:
|
||||
|
||||
pcfile_rel = pcfg.get_pc_path()
|
||||
pcfile = self.output_pcfile
|
||||
prefix_rel = pcfile_rel.parent
|
||||
prefix_path = pcfile.parent
|
||||
|
||||
prefix = "${pcfiledir}"
|
||||
|
||||
# variables first
|
||||
variables = {}
|
||||
variables["prefix"] = prefix
|
||||
|
||||
if pcfg.includedir:
|
||||
increl = pathlib.PurePosixPath(pcfg.includedir).relative_to(
|
||||
prefix_rel.as_posix()
|
||||
)
|
||||
variables["includedir"] = f"${{prefix}}/{increl}"
|
||||
|
||||
if pcfg.shared_libraries:
|
||||
if pcfg.libdir:
|
||||
librel = pathlib.PurePosixPath(pcfg.libdir).relative_to(
|
||||
prefix_rel.as_posix()
|
||||
)
|
||||
variables["libdir"] = f"${{prefix}}/{librel}"
|
||||
else:
|
||||
variables["libdir"] = "${prefix}"
|
||||
|
||||
if pcfg.variables:
|
||||
for n in ("prefix", "includedir", "libdir", INITPY_VARNAME):
|
||||
if n in pcfg.variables:
|
||||
raise ValueError(f"variables may not contain {n}")
|
||||
|
||||
variables.update(variables)
|
||||
|
||||
# If there are libraries, generate _init_NAME.py for each
|
||||
if pcfg.shared_libraries:
|
||||
package = self._get_pkg_from_path(prefix_path)
|
||||
variables[INITPY_VARNAME] = f"{package}.{pcfg.get_init_module()}"
|
||||
self._generate_init_py(pcfg, prefix_path, build_data)
|
||||
|
||||
# .. not documented but it works?
|
||||
# eps = self.metadata.core.entry_points.setdefault("pkg_config", {})
|
||||
# eps[pcfg.get_name()] = package
|
||||
|
||||
contents = [f"{k}={v}" for k, v in variables.items()]
|
||||
contents.append("")
|
||||
|
||||
description = pcfg.description
|
||||
if description is None:
|
||||
description = self.metadata["description"]
|
||||
|
||||
if not description:
|
||||
raise ValueError(
|
||||
f"tool.hatch.build.hooks.nativelib.pcfile: description not provided for {pcfg.get_name()}"
|
||||
)
|
||||
|
||||
contents += [
|
||||
f"Name: {pcfg.get_name()}",
|
||||
f"Description: {description}",
|
||||
]
|
||||
|
||||
version = pcfg.version or self.metadata["version"]
|
||||
if version:
|
||||
contents.append(f"Version: {version}")
|
||||
|
||||
libs = []
|
||||
if pcfg.shared_libraries:
|
||||
libs.append("-L${libdir}")
|
||||
libs.extend(f"-l{lib}" for lib in pcfg.shared_libraries)
|
||||
|
||||
cflags = []
|
||||
if pcfg.includedir:
|
||||
cflags.append("-I${includedir}")
|
||||
|
||||
if pcfg.extra_cflags:
|
||||
cflags.append(pcfg.extra_cflags)
|
||||
|
||||
if pcfg.requires:
|
||||
contents.append(f"Requires: {' '.join(pcfg.requires)}")
|
||||
|
||||
if pcfg.requires_private:
|
||||
contents.append(f"Requires.private: {' '.join(pcfg.requires_private)}")
|
||||
|
||||
if libs:
|
||||
contents.append(f"Libs: {' '.join(libs)}")
|
||||
|
||||
if pcfg.libs_private:
|
||||
contents.append(f"Libs.private: {pcfg.libs_private}")
|
||||
|
||||
if cflags:
|
||||
contents.append(f"Cflags: {' '.join(cflags)}")
|
||||
|
||||
content = ("\n".join(contents)) + "\n"
|
||||
pcfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(pcfile, "w") as fp:
|
||||
fp.write(content)
|
||||
|
||||
return pcfile
|
||||
|
||||
def _generate_init_py(
|
||||
self,
|
||||
pcfg: PcFileConfig,
|
||||
prefix_path: pathlib.Path,
|
||||
build_data: T.Dict[str, T.Any],
|
||||
):
|
||||
libinit_py_rel = pcfg.get_init_module_path()
|
||||
self.root_pth / libinit_py_rel
|
||||
|
||||
libdir = prefix_path
|
||||
if pcfg.libdir:
|
||||
libdir = self.root_pth / pathlib.PurePosixPath(pcfg.libdir)
|
||||
libdir = pathlib.Path(str(libdir).replace("src/", "").replace("src\\", ""))
|
||||
|
||||
lib_paths = []
|
||||
assert pcfg.shared_libraries is not None
|
||||
for lib in pcfg.shared_libraries:
|
||||
lib_path = libdir / self._make_shared_lib_fname(lib)
|
||||
lib_paths.append(lib_path)
|
||||
|
||||
if pcfg.requires:
|
||||
requires = pcfg.requires
|
||||
else:
|
||||
requires = []
|
||||
|
||||
_write_libinit_py(self.output_libinit, lib_paths, requires)
|
||||
|
||||
def _make_shared_lib_fname(self, lib: str):
|
||||
if is_windows:
|
||||
return f"{lib}.dll"
|
||||
elif is_macos:
|
||||
return f"lib{lib}.dylib"
|
||||
else:
|
||||
return f"lib{lib}.so"
|
||||
|
||||
@functools.cached_property
|
||||
def _pcfiles(self) -> T.List[PcFileConfig]:
|
||||
pcfiles = []
|
||||
for i, raw_pc in enumerate(self.config.get("pcfile", [])):
|
||||
pcfile = parse_input(
|
||||
raw_pc,
|
||||
PcFileConfig,
|
||||
"pyproject.toml",
|
||||
f"tool.hatch.build.hooks.nativelib.pcfile[{i}]",
|
||||
)
|
||||
|
||||
if pcfile.enable_if and not Marker(pcfile.enable_if).evaluate():
|
||||
print(
|
||||
f"{pcfile.pcfile} skipped because enable_if did not match current environment"
|
||||
)
|
||||
continue
|
||||
|
||||
pcfiles.append(pcfile)
|
||||
|
||||
return pcfiles
|
||||
|
||||
|
||||
# TODO: this belongs in a separate script/api that can be used from multiple tools
|
||||
def _write_libinit_py(
|
||||
init_py: pathlib.Path,
|
||||
libs: T.List[pathlib.Path],
|
||||
requires: T.List[str],
|
||||
):
|
||||
"""
|
||||
:param init_py: the _init module for the library(ies) that is written out
|
||||
:param libs: for each library that is being initialized, this is the
|
||||
path to that library
|
||||
|
||||
:param requires: other pkgconf packages that these libraries depend on.
|
||||
Their init_py will be looked up and imported first.
|
||||
"""
|
||||
|
||||
contents = [
|
||||
"# This file is automatically generated, DO NOT EDIT",
|
||||
"# fmt: off",
|
||||
"",
|
||||
]
|
||||
|
||||
for req in requires:
|
||||
r = pkgconf.run_pkgconf(
|
||||
req, f"--variable={INITPY_VARNAME}", capture_output=True
|
||||
)
|
||||
# TODO: should this be a fatal error
|
||||
if r.returncode == 0:
|
||||
module = r.stdout.decode("utf-8").strip() # type: ignore[arg-type, union-attr]
|
||||
contents.append(f"import {module}")
|
||||
else:
|
||||
raise Exception("Could not find ", req)
|
||||
|
||||
if contents[-1] != "":
|
||||
contents.append("")
|
||||
|
||||
if libs:
|
||||
contents += [
|
||||
"def __load_library():",
|
||||
" from os.path import abspath, join, dirname, exists",
|
||||
]
|
||||
|
||||
if is_macos:
|
||||
contents += [" from ctypes import CDLL, RTLD_GLOBAL"]
|
||||
else:
|
||||
contents += [" from ctypes import cdll", ""]
|
||||
|
||||
if len(libs) > 1:
|
||||
contents.append(" libs = []")
|
||||
|
||||
contents.append(" root = abspath(dirname(__file__))")
|
||||
|
||||
for lib in libs:
|
||||
rel = lib.relative_to(init_py.parent)
|
||||
components = ", ".join(map(repr, rel.parts))
|
||||
|
||||
contents += [
|
||||
"",
|
||||
f" lib_path = join(root, {components})",
|
||||
"",
|
||||
" try:",
|
||||
]
|
||||
|
||||
if is_macos:
|
||||
load = "CDLL(lib_path, mode=RTLD_GLOBAL)"
|
||||
else:
|
||||
load = "cdll.LoadLibrary(lib_path)"
|
||||
|
||||
if len(libs) > 1:
|
||||
contents.append(f" libs.append({load})")
|
||||
else:
|
||||
contents.append(f" return {load}")
|
||||
|
||||
contents += [
|
||||
" except FileNotFoundError:",
|
||||
f" if not exists(lib_path):",
|
||||
f' raise FileNotFoundError("{lib.name} was not found on your system. Is this package correctly installed?")',
|
||||
]
|
||||
|
||||
if is_windows:
|
||||
contents.append(
|
||||
f' raise Exception("{lib.name} could not be loaded. Do you have Visual Studio C++ Redistributible installed?")'
|
||||
)
|
||||
else:
|
||||
contents.append(
|
||||
f' raise FileNotFoundError("{lib.name} could not be loaded. There is a missing dependency.")'
|
||||
)
|
||||
|
||||
if len(libs) > 1:
|
||||
contents += [" return libs"]
|
||||
|
||||
contents += ["", "__lib = __load_library()", ""]
|
||||
|
||||
content = ("\n".join(contents)) + "\n"
|
||||
|
||||
init_py.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(init_py, "w") as fp:
|
||||
fp.write(content)
|
||||
|
||||
|
||||
def main():
|
||||
pyproject_toml = sys.argv[1]
|
||||
libinit_file = pathlib.Path(sys.argv[2])
|
||||
pc_file = pathlib.Path(sys.argv[3])
|
||||
pkgcfgs = [pathlib.Path(x) for x in sys.argv[4:]]
|
||||
|
||||
hack_pkgconfig(pkgcfgs)
|
||||
|
||||
with open(pyproject_toml, "rb") as fp:
|
||||
raw_config = tomli.load(fp)
|
||||
|
||||
nativelib_cfg = raw_config["tool"]["hatch"]["build"]["hooks"]["nativelib"]
|
||||
metadata = raw_config["project"]
|
||||
|
||||
generator = NativelibHook(pc_file, libinit_file, nativelib_cfg, metadata)
|
||||
generator.initialize()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
43
shared/bazel/rules/robotpy/hatchlib_native_port/validate.py
Normal file
43
shared/bazel/rules/robotpy/hatchlib_native_port/validate.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import typing
|
||||
|
||||
import validobj.validation
|
||||
from validobj import errors
|
||||
|
||||
T = typing.TypeVar("T")
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _convert_validation_error(
|
||||
fname, ve: errors.ValidationError, prefix: str
|
||||
) -> ValidationError:
|
||||
locs = []
|
||||
msgs = []
|
||||
|
||||
e: typing.Optional[BaseException] = ve
|
||||
while e is not None:
|
||||
|
||||
if isinstance(e, errors.WrongFieldError):
|
||||
locs.append(f".{e.wrong_field}")
|
||||
elif isinstance(e, errors.WrongListItemError):
|
||||
locs.append(f"[{e.wrong_index}]")
|
||||
else:
|
||||
msgs.append(str(e))
|
||||
|
||||
e = e.__cause__
|
||||
|
||||
loc = "".join(locs)
|
||||
if loc.startswith("."):
|
||||
loc = loc[1:]
|
||||
msg = "\n ".join(msgs)
|
||||
vmsg = f"{fname}: {prefix}{loc}:\n {msg}"
|
||||
return ValidationError(vmsg)
|
||||
|
||||
|
||||
def parse_input(value: typing.Any, spec: typing.Type[T], fname, prefix: str) -> T:
|
||||
try:
|
||||
return validobj.validation.parse_input(value, spec)
|
||||
except errors.ValidationError as ve:
|
||||
raise _convert_validation_error(fname, ve, prefix) from None
|
||||
236
shared/bazel/rules/robotpy/pybind_build_file_template.jinja2
Normal file
236
shared/bazel/rules/robotpy/pybind_build_file_template.jinja2
Normal file
@@ -0,0 +1,236 @@
|
||||
# THIS FILE IS AUTO GENERATED
|
||||
{% if publish_casters_targets %}
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
{%- endif %}
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "create_pybind_library", "robotpy_library")
|
||||
load("//shared/bazel/rules/robotpy:semiwrap_helpers.bzl", "gen_libinit", "gen_modinit_hpp", "gen_pkgconf", {% if publish_casters_targets %}"publish_casters", {% endif %}"resolve_casters", "run_header_gen")
|
||||
load("//shared/bazel/rules/robotpy:semiwrap_tool_helpers.bzl", "scan_headers", "update_yaml_files")
|
||||
{% for extension_module in extension_modules%}
|
||||
def {{extension_module.name}}_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includes = [], extra_pyi_deps = []):
|
||||
{{extension_module.name|upper}}_HEADER_GEN = [
|
||||
{%- for header_cfg in extension_module.generation_data.values() %}
|
||||
struct(
|
||||
class_name = "{{header_cfg.class_name}}",
|
||||
yml_file = "{{header_cfg.yml_file}}",
|
||||
header_root = "{{header_cfg.include_root}}",
|
||||
header_file = "{{header_cfg.include_file}}",
|
||||
{%- if header_cfg.templates|length > 0 %}
|
||||
tmpl_class_names = [
|
||||
{%- for tmpl in header_cfg.templates %}
|
||||
("{{ tmpl[0] }}", "{{ tmpl[1] }}"),
|
||||
{%- endfor %}
|
||||
],
|
||||
{%- else %}
|
||||
tmpl_class_names = [],
|
||||
{%- endif %}
|
||||
{%- if header_cfg.trampolines|length > 0 %}
|
||||
trampolines = [
|
||||
{%- for trampoline in header_cfg.trampolines %}
|
||||
("{{ trampoline[0] }}", "{{ trampoline[1] }}"),
|
||||
{%- endfor %}
|
||||
],
|
||||
{%- else %}
|
||||
trampolines = [],
|
||||
{%- endif %}
|
||||
),
|
||||
{%- endfor %}
|
||||
]
|
||||
|
||||
resolve_casters(
|
||||
name = "{{extension_module.name}}.resolve_casters",
|
||||
caster_deps = {{ extension_module.resolve_casters.caster_deps | jsonify }},
|
||||
casters_pkl_file = "{{ extension_module.resolve_casters.pkl_file }}",
|
||||
dep_file = "{{ extension_module.resolve_casters.dep_file }}",
|
||||
)
|
||||
|
||||
gen_libinit(
|
||||
name = "{{extension_module.name}}.gen_lib_init",
|
||||
output_file = "{{stripped_include_prefix}}/{{extension_module.gen_libinit.install_path}}/{{extension_module.gen_libinit.output_file}}",
|
||||
modules = {{extension_module.gen_libinit.modules | jsonify}},
|
||||
)
|
||||
|
||||
gen_pkgconf(
|
||||
name = "{{extension_module.name}}.gen_pkgconf",
|
||||
libinit_py = "{{ extension_module.gen_pkgconf.libinit_py }}",
|
||||
module_pkg_name = "{{ extension_module.gen_pkgconf.module_pkg_name }}",
|
||||
output_file = "{{ extension_module.gen_pkgconf.output_file }}",
|
||||
pkg_name = "{{ extension_module.gen_pkgconf.pkg_name }}",
|
||||
install_path = "{{stripped_include_prefix}}/{{ extension_module.gen_pkgconf.install_path }}",
|
||||
project_file = "{{ stripped_include_prefix }}/{{ extension_module.gen_pkgconf.project_file }}",
|
||||
package_root = "{{package_root_file}}",
|
||||
)
|
||||
|
||||
gen_modinit_hpp(
|
||||
name = "{{extension_module.name}}.gen_modinit_hpp",
|
||||
input_dats = [x.class_name for x in {{extension_module.name|upper}}_HEADER_GEN],
|
||||
libname = "{{ extension_module.gen_modinit.lib_name }}",
|
||||
output_file = "{{ extension_module.gen_modinit.output_file }}",
|
||||
)
|
||||
|
||||
run_header_gen(
|
||||
name = "{{extension_module.name}}",
|
||||
casters_pickle = "{{ extension_module.resolve_casters.pkl_file }}",
|
||||
header_gen_config = {{extension_module.name|upper}}_HEADER_GEN,
|
||||
trampoline_subpath = "{{stripped_include_prefix}}/{{ extension_module.install_path }}",
|
||||
deps = header_to_dat_deps,
|
||||
local_native_libraries = [
|
||||
{%- for header_path in extension_module.native_wrapper_dependencies|sort %}
|
||||
"{{header_path}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
{%- if extension_module.get_defines() %}
|
||||
generation_defines = [{%-for da in extension_module.get_defines() %}"{{da.replace("=", " ")}}"{% endfor %}],
|
||||
{%- endif %}
|
||||
{%- if yml_prefix %}
|
||||
yml_prefix = "{{yml_prefix}}",
|
||||
{%- endif %}
|
||||
)
|
||||
|
||||
create_pybind_library(
|
||||
name = "{{extension_module.name}}",
|
||||
install_path = "{{stripped_include_prefix}}/{{extension_module.install_path}}/",
|
||||
extension_name = "{{ extension_module.gen_modinit.lib_name }}",
|
||||
generated_srcs = [":{{extension_module.name}}.generated_srcs"],
|
||||
semiwrap_header = [":{{extension_module.name}}.gen_modinit_hpp"],
|
||||
deps = [
|
||||
":{{extension_module.name}}.tmpl_hdrs",
|
||||
":{{extension_module.name}}.trampoline_hdrs",
|
||||
{%- for dep in extension_module.local_extension_dependencies %}
|
||||
"{{dep}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
dynamic_deps = [
|
||||
{%- for dep in extension_module.dynamic_dependencies %}
|
||||
"{{dep}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
extra_hdrs = extra_hdrs,
|
||||
extra_srcs = srcs,
|
||||
includes = includes,
|
||||
{%- if extension_module.get_defines() %}
|
||||
local_defines = [{%-for da in extension_module.get_defines() %}"{{da.replace(' ', '=')}}"{% endfor %}],
|
||||
{%- endif %}
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = "{{extension_module.name}}.generated_files",
|
||||
srcs = [
|
||||
"{{extension_module.name}}.gen_modinit_hpp.gen",
|
||||
"{{extension_module.name}}.header_gen_files",
|
||||
"{{extension_module.name}}.gen_pkgconf",
|
||||
"{{extension_module.name}}.gen_lib_init",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
{% endfor %}
|
||||
{%- for caster_info in publish_casters_targets %}
|
||||
def publish_library_casters():
|
||||
publish_casters(
|
||||
name = "publish_casters",
|
||||
caster_name = "{{caster_info.casters_name}}",
|
||||
output_json = "{{stripped_include_prefix}}/{{caster_info.install_path}}/{{caster_info.json_output}}",
|
||||
output_pc = "{{stripped_include_prefix}}/{{caster_info.install_path}}/{{caster_info.pc_output}}",
|
||||
project_config = "{{ stripped_include_prefix }}/{{caster_info.project_file}}",
|
||||
package_root = "{{package_root_file}}",
|
||||
typecasters_srcs = native.glob([{% for inc in caster_info.include_paths %}"{{ inc }}/**"{% if not loop.last%}, {% endif %}{% endfor %}]),
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "{{caster_info.casters_name}}",
|
||||
hdrs = native.glob([{% for inc in caster_info.include_paths %}"{{ inc }}/*.h"{% if not loop.last%}, {% endif %}{% endfor %}]),
|
||||
includes = [{% for inc in caster_info.include_paths %}"{{ inc }}"{% if not loop.last%}, {% endif %}{% endfor %}],
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
{% endfor %}
|
||||
def define_pybind_library(name, pkgcfgs = []):
|
||||
# Helper used to generate all files with one target.
|
||||
native.filegroup(
|
||||
name = "{}.generated_files".format(name),
|
||||
srcs = [
|
||||
{%- for em in extension_modules %}
|
||||
"{{em.name}}.generated_files",
|
||||
{%- endfor %}
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Files that will be included in the wheel as data deps
|
||||
native.filegroup(
|
||||
name = "{}.generated_pkgcfg_files".format(name),
|
||||
srcs = [
|
||||
{%- for em in extension_modules %}
|
||||
"{{stripped_include_prefix}}/{{em.gen_pkgconf.install_path}}/{{ em.gen_pkgconf.output_file }}",
|
||||
{%- endfor %}
|
||||
{%- for caster_info in publish_casters_targets %}
|
||||
"{{stripped_include_prefix}}/{{caster_info.install_path}}/{{caster_info.pc_output}}",
|
||||
"{{stripped_include_prefix}}/{{caster_info.install_path}}/{{caster_info.json_output}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Contains all of the non-python files that need to be included in the wheel
|
||||
native.filegroup(
|
||||
name = "{}.extra_files".format(name),
|
||||
srcs = native.glob(["{{stripped_include_prefix}}/{{top_level_name}}/**"], exclude = ["{{stripped_include_prefix}}/{{top_level_name}}/**/*.py"], allow_empty = True),
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
robotpy_library(
|
||||
name = name,
|
||||
srcs = native.glob(["{{stripped_include_prefix}}/{{top_level_name}}/**/*.py"]) + [
|
||||
{%- for em in extension_modules %}
|
||||
"{{stripped_include_prefix}}/{{ em.gen_pkgconf.libinit_py.replace(".", "/") }}.py",
|
||||
{%- endfor %}
|
||||
],
|
||||
data = [
|
||||
"{}.generated_pkgcfg_files".format(name),
|
||||
"{}.extra_files".format(name),
|
||||
{%- for em in extension_modules %}
|
||||
":{{stripped_include_prefix}}/{{em.install_path}}/{{em.gen_modinit.lib_name}}",
|
||||
{%- endfor %}
|
||||
{%- for em in extension_modules %}
|
||||
":{{em.name}}.trampoline_hdr_files",
|
||||
{%- endfor %}
|
||||
],
|
||||
imports = ["{{stripped_include_prefix}}"],
|
||||
deps = [
|
||||
{%- for d in python_deps %}
|
||||
"{{d}}",
|
||||
{%- endfor %}
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
update_yaml_files(
|
||||
name = "{}-update-yaml".format(name),
|
||||
yaml_output_directory = "{{ stripped_include_prefix }}/semiwrap",
|
||||
extra_hdrs = native.glob(["{{stripped_include_prefix}}/**/*.h"], allow_empty = True) + [
|
||||
{%- if python_deps %}
|
||||
{% for d in python_deps %}
|
||||
{%- if "native" in d %}"{{d}}.copy_headers",{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
],
|
||||
package_root_file = "{{package_root_file}}",
|
||||
pkgcfgs = pkgcfgs,
|
||||
pyproject_toml = "{{ stripped_include_prefix }}/pyproject.toml",
|
||||
yaml_files = native.glob(["{{stripped_include_prefix}}/semiwrap/**"]),
|
||||
)
|
||||
|
||||
scan_headers(
|
||||
name = "{}-scan-headers".format(name),
|
||||
extra_hdrs = native.glob(["{{stripped_include_prefix}}/**/*.h"], allow_empty = True) + [
|
||||
{%- if python_deps %}
|
||||
{% for d in python_deps %}
|
||||
{%- if "native" in d %}"{{d}}.copy_headers",{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
],
|
||||
package_root_file = "{{package_root_file}}",
|
||||
pkgcfgs = pkgcfgs,
|
||||
pyproject_toml = "{{ stripped_include_prefix }}/pyproject.toml",
|
||||
)
|
||||
197
shared/bazel/rules/robotpy/pybind_rules.bzl
Normal file
197
shared/bazel/rules/robotpy/pybind_rules.bzl
Normal file
@@ -0,0 +1,197 @@
|
||||
load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
|
||||
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library")
|
||||
load("@rules_python//python:defs.bzl", "py_library")
|
||||
load("//shared/bazel/rules/robotpy:compatibility_select.bzl", "robotpy_compatibility_select")
|
||||
|
||||
def create_pybind_library(
|
||||
name,
|
||||
extension_name,
|
||||
install_path,
|
||||
generated_srcs = [],
|
||||
extra_hdrs = [],
|
||||
extra_srcs = [],
|
||||
deps = [],
|
||||
dynamic_deps = [],
|
||||
semiwrap_header = [],
|
||||
includes = [],
|
||||
local_defines = []):
|
||||
"""
|
||||
Function to create a pybind C++ extension library that has been defined by a semiwrap config
|
||||
|
||||
Outputs:
|
||||
<name>_pybind_library - A pybind_library that functions like a header-only cc_library. It will include all
|
||||
of the extra_hdrs, resolve the include paths, and add a dependency on the semiwrap headrs
|
||||
<install_path + extension_name> - A pybind_extension that wraps the pybind_library and compiles all the source files.
|
||||
|
||||
Params:
|
||||
extension_name - The name of the pybind extension. Should be sourced from pyproject
|
||||
install_path - The subpath where the library will be installed. Should be source from pyproject
|
||||
generated_srcs - List of auto-generated sources to be compiled into the extension.
|
||||
extra_hdrs - Any non-autogenerated headers
|
||||
extra_srcs - Any non-autogenerated sources
|
||||
deps - cc_library deps used to create the pybind_library
|
||||
dynamic_deps - cc_shared_library deps used to filter objects from the pybind_extension
|
||||
semiwrap_header - Auto-generated file used to initialize the extension
|
||||
includes - see cc_library#includes. Used during the creating of the pybind_library
|
||||
local_defines - see cc_library#local_defines. Used during the compilation of the extension
|
||||
"""
|
||||
pybind_library(
|
||||
name = "{}_pybind_library".format(name),
|
||||
hdrs = extra_hdrs,
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
deps = deps + [
|
||||
"//shared/bazel/rules/robotpy:semiwrap_headers",
|
||||
],
|
||||
includes = includes,
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
extension_name = extension_name or "_{}".format(name)
|
||||
pybind_extension(
|
||||
name = install_path + extension_name,
|
||||
srcs = extra_srcs + generated_srcs,
|
||||
deps = [":{}_pybind_library".format(name)] + semiwrap_header,
|
||||
dynamic_deps = dynamic_deps,
|
||||
copts = select({
|
||||
"@bazel_tools//src/conditions:darwin": [
|
||||
"-Wno-deprecated-declarations",
|
||||
"-Wno-overloaded-virtual",
|
||||
"-Wno-pessimizing-move",
|
||||
"-Wno-unused-value",
|
||||
],
|
||||
"@bazel_tools//src/conditions:linux_x86_64": [
|
||||
"-Wno-attributes",
|
||||
"-Wno-unused-value",
|
||||
"-Wno-deprecated",
|
||||
"-Wno-deprecated-declarations",
|
||||
"-Wno-unused-parameter",
|
||||
"-Wno-redundant-move",
|
||||
"-Wno-unused-but-set-variable",
|
||||
"-Wno-unused-variable",
|
||||
"-Wno-pessimizing-move",
|
||||
"-Wno-overloaded-virtual",
|
||||
],
|
||||
"@bazel_tools//src/conditions:windows": [
|
||||
],
|
||||
}),
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
local_defines = local_defines,
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def robotpy_library(
|
||||
name,
|
||||
data = [],
|
||||
**kwargs):
|
||||
"""
|
||||
Defines a python library that is wrapping a series of pybind extensions.
|
||||
|
||||
Outputs:
|
||||
<name> - The python library
|
||||
"""
|
||||
py_library(
|
||||
name = name,
|
||||
data = data,
|
||||
tags = ["robotpy"],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def copy_native_file(name, library, base_path):
|
||||
"""
|
||||
Copies a compiled shared library into a naming format that can be used by robotpy rules. The libraries are named
|
||||
differently on OSX / Linux / Windows and this creates a handy alias to for easier use downstream
|
||||
"""
|
||||
copy_file(
|
||||
name = name + ".win_copy_lib",
|
||||
src = library,
|
||||
out = "{}lib/{}.dll".format(base_path, name),
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
copy_file(
|
||||
name = name + ".osx_copy_lib",
|
||||
src = library,
|
||||
out = "{}lib/lib{}.dylib".format(base_path, name),
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
copy_file(
|
||||
name = name + ".linux_copy_lib",
|
||||
src = library,
|
||||
out = "{}lib/lib{}.so".format(base_path, name),
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
native.alias(
|
||||
name = "{}.copy_lib".format(name),
|
||||
actual = select({
|
||||
"@bazel_tools//src/conditions:darwin": name + ".osx_copy_lib",
|
||||
"@bazel_tools//src/conditions:windows": name + ".win_copy_lib",
|
||||
"//conditions:default": name + ".linux_copy_lib",
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def _folder_prefix(name):
|
||||
if "/" in name:
|
||||
last_slash = name.rfind("/")
|
||||
return (name[0:last_slash], name[last_slash + 1:])
|
||||
else:
|
||||
return ("", name)
|
||||
|
||||
def native_wrappery_library(
|
||||
name,
|
||||
pyproject_toml,
|
||||
libinit_file,
|
||||
pc_file,
|
||||
pc_deps,
|
||||
native_shared_library,
|
||||
install_path,
|
||||
headers,
|
||||
deps = []):
|
||||
"""
|
||||
This function provides a sugar wrapper for defining a python library that wraps an allwpilib native library
|
||||
"""
|
||||
cmd = "$(locations //shared/bazel/rules/robotpy/hatchlib_native_port:generate_native_lib_files) "
|
||||
cmd += " $(location " + pyproject_toml + ")"
|
||||
cmd += " $(OUTS) "
|
||||
for pc_dep in pc_deps:
|
||||
cmd += " $(location " + pc_dep + ")"
|
||||
|
||||
native.genrule(
|
||||
name = name + ".gen",
|
||||
srcs = [pyproject_toml],
|
||||
outs = [libinit_file, pc_file],
|
||||
cmd = cmd,
|
||||
tools = ["//shared/bazel/rules/robotpy/hatchlib_native_port:generate_native_lib_files"] + pc_deps,
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
prefix, libname = _folder_prefix(native_shared_library)
|
||||
|
||||
copy_native_file(
|
||||
name = libname,
|
||||
library = native_shared_library,
|
||||
base_path = install_path,
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = name + ".pc_wrapper",
|
||||
srcs = [pc_file],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = name,
|
||||
srcs = [libinit_file],
|
||||
data = [pc_file, ":{}.copy_lib".format(libname), headers],
|
||||
deps = deps,
|
||||
imports = ["."],
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
17
shared/bazel/rules/robotpy/pytest_util.bzl
Normal file
17
shared/bazel/rules/robotpy/pytest_util.bzl
Normal file
@@ -0,0 +1,17 @@
|
||||
load("@rules_python_pytest//python_pytest:defs.bzl", "py_pytest_test")
|
||||
load("//shared/bazel/rules/robotpy:compatibility_select.bzl", "robotpy_compatibility_select")
|
||||
|
||||
def robotpy_py_test(name, srcs, **kwargs):
|
||||
py_pytest_test(
|
||||
name = name,
|
||||
size = "small",
|
||||
srcs = srcs,
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = [
|
||||
"no-asan",
|
||||
"no-tsan",
|
||||
"robotpy",
|
||||
],
|
||||
legacy_create_init = 0,
|
||||
**kwargs
|
||||
)
|
||||
357
shared/bazel/rules/robotpy/semiwrap_helpers.bzl
Normal file
357
shared/bazel/rules/robotpy/semiwrap_helpers.bzl
Normal file
@@ -0,0 +1,357 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
load("//shared/bazel/rules/robotpy:compatibility_select.bzl", "robotpy_compatibility_select")
|
||||
|
||||
RESOLVE_CASTERS_DIR = "generated/resolve_casters/"
|
||||
HEADER_DAT_DIR = "generated/header_to_dat/"
|
||||
DAT_TO_CC_DIR = "generated/dat_to_cc/"
|
||||
DAT_TO_TMPL_CC_DIR = "generated/dat_to_tmpl_cc/"
|
||||
DAT_TO_TMPL_HDR_DIR = "generated/dat_to_tmpl_hdr/"
|
||||
GEN_MODINIT_HDR_DIR = "generated/gen_modinit_hdr/"
|
||||
|
||||
def _location_helper(filename):
|
||||
return " $(locations " + filename + ")"
|
||||
|
||||
def _wrapper():
|
||||
return "$(locations //shared/bazel/rules/robotpy:wrapper) "
|
||||
|
||||
def _wrapper_dep():
|
||||
return ["//shared/bazel/rules/robotpy:wrapper"]
|
||||
|
||||
def _semiwrap_caster():
|
||||
return "//shared/bazel/rules/robotpy:semiwrap_casters_files"
|
||||
|
||||
def publish_casters(
|
||||
name,
|
||||
project_config,
|
||||
caster_name,
|
||||
output_json,
|
||||
output_pc,
|
||||
typecasters_srcs,
|
||||
package_root):
|
||||
"""
|
||||
Sugar wrapper for the semiwrap.cmd.publish_casters tool
|
||||
"""
|
||||
cmd = _wrapper() + " semiwrap.cmd.publish_casters"
|
||||
cmd += " $(SRCS) " + caster_name + " $(OUTS)"
|
||||
|
||||
native.genrule(
|
||||
name = name,
|
||||
srcs = [project_config],
|
||||
outs = [output_json, output_pc],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + typecasters_srcs + [package_root],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def resolve_casters(
|
||||
name,
|
||||
casters_pkl_file,
|
||||
dep_file,
|
||||
caster_files = [],
|
||||
caster_deps = []):
|
||||
"""
|
||||
Sugar wrapper for the semiwrap.cmd.resolve_casters tool
|
||||
"""
|
||||
cmd = _wrapper() + " semiwrap.cmd.resolve_casters "
|
||||
cmd += " $(OUTS)"
|
||||
|
||||
cmd += _location_helper(_semiwrap_caster()) + "/semiwrap/semiwrap.pybind11.json"
|
||||
|
||||
resolved_caster_files = []
|
||||
|
||||
deps = []
|
||||
for dep in caster_deps:
|
||||
deps.append(dep)
|
||||
cmd += _location_helper(dep)
|
||||
|
||||
for cfd in caster_files:
|
||||
if cfd.startswith(":"):
|
||||
resolved_caster_files.append(cfd)
|
||||
cmd += _location_helper(cfd)
|
||||
else:
|
||||
cmd += " " + cfd
|
||||
|
||||
native.genrule(
|
||||
name = name,
|
||||
srcs = resolved_caster_files + deps,
|
||||
outs = [RESOLVE_CASTERS_DIR + casters_pkl_file, RESOLVE_CASTERS_DIR + dep_file],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [_semiwrap_caster()],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def gen_libinit(
|
||||
name,
|
||||
output_file,
|
||||
modules):
|
||||
"""
|
||||
Sugar wrapper for the semiwrap.cmd.gen_libinit tool
|
||||
"""
|
||||
cmd = _wrapper() + " semiwrap.cmd.gen_libinit "
|
||||
cmd += " $(OUTS) "
|
||||
cmd += " ".join(modules)
|
||||
|
||||
native.genrule(
|
||||
name = name,
|
||||
outs = [output_file],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep(),
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def gen_pkgconf(
|
||||
name,
|
||||
project_file,
|
||||
module_pkg_name,
|
||||
pkg_name,
|
||||
output_file,
|
||||
libinit_py,
|
||||
install_path,
|
||||
package_root):
|
||||
"""
|
||||
Sugar wrapper for the semiwrap.cmd.gen_pkgconf tool
|
||||
"""
|
||||
cmd = _wrapper() + " semiwrap.cmd.gen_pkgconf "
|
||||
cmd += " " + module_pkg_name + " " + pkg_name
|
||||
cmd += _location_helper(project_file)
|
||||
cmd += " $(OUTS)"
|
||||
if libinit_py:
|
||||
cmd += " --libinit-py " + libinit_py
|
||||
|
||||
OUT_FILE = install_path + "/" + output_file
|
||||
native.genrule(
|
||||
name = name,
|
||||
srcs = [package_root],
|
||||
outs = [OUT_FILE],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [project_file],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def header_to_dat(
|
||||
name,
|
||||
casters_pickle,
|
||||
include_root,
|
||||
class_name,
|
||||
yml_file,
|
||||
header_location,
|
||||
generation_includes = [],
|
||||
header_to_dat_deps = [],
|
||||
extra_defines = [],
|
||||
deps = []):
|
||||
"""
|
||||
Sugar wrapper for the semiwrap.cmd.header2dat tool
|
||||
"""
|
||||
cmd = _wrapper() + " semiwrap.cmd.header2dat "
|
||||
cmd += "--cpp 202002L " # TODO(pj) - This is the option when I ran on linux. Does its value really matter?
|
||||
cmd += class_name
|
||||
cmd += _location_helper(yml_file)
|
||||
|
||||
cmd += " -I " + include_root
|
||||
|
||||
for inc in generation_includes:
|
||||
cmd += " -I " + inc
|
||||
for d in extra_defines:
|
||||
cmd += " -D '" + d + "'"
|
||||
cmd += " " + header_location
|
||||
|
||||
cmd += " " + include_root
|
||||
cmd += _location_helper(RESOLVE_CASTERS_DIR + casters_pickle)
|
||||
cmd += " $(OUTS)"
|
||||
cmd += " bogus c++20 ccache c++ -- -std=c++20" # TODO(pj) Does it matter what these values are?
|
||||
|
||||
native.genrule(
|
||||
name = name + "." + class_name,
|
||||
srcs = [RESOLVE_CASTERS_DIR + casters_pickle] + deps + header_to_dat_deps,
|
||||
outs = [HEADER_DAT_DIR + class_name + ".dat", HEADER_DAT_DIR + class_name + ".d"],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [yml_file],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def dat_to_cc(
|
||||
name,
|
||||
class_name):
|
||||
dat_file = HEADER_DAT_DIR + class_name + ".dat"
|
||||
cmd = _wrapper() + " semiwrap.cmd.dat2cpp "
|
||||
cmd += _location_helper(dat_file)
|
||||
cmd += " $(OUTS)"
|
||||
native.genrule(
|
||||
name = name + "." + class_name,
|
||||
outs = [DAT_TO_CC_DIR + class_name + ".cpp"],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [dat_file],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def dat_to_tmpl_cpp(name, base_class_name, specialization, tmp_class_name):
|
||||
cmd = _wrapper() + " semiwrap.cmd.dat2tmplcpp "
|
||||
cmd += _location_helper(HEADER_DAT_DIR + base_class_name + ".dat")
|
||||
cmd += " " + specialization
|
||||
cmd += " $(OUTS)"
|
||||
native.genrule(
|
||||
name = name + "." + tmp_class_name,
|
||||
outs = [DAT_TO_TMPL_CC_DIR + tmp_class_name + ".cpp"],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [HEADER_DAT_DIR + base_class_name + ".dat"],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def dat_to_tmpl_hpp(name, class_name):
|
||||
dat_file = HEADER_DAT_DIR + class_name + ".dat"
|
||||
|
||||
cmd = _wrapper() + " semiwrap.cmd.dat2tmplhpp "
|
||||
cmd += _location_helper(dat_file)
|
||||
cmd += " $(OUTS)"
|
||||
native.genrule(
|
||||
name = name + "." + class_name,
|
||||
outs = [DAT_TO_TMPL_HDR_DIR + class_name + "_tmpl.hpp"],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [dat_file],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def dat_to_trampoline(name, dat_file, class_name, output_file):
|
||||
cmd = _wrapper() + " semiwrap.cmd.dat2trampoline "
|
||||
|
||||
cmd += _location_helper(HEADER_DAT_DIR + dat_file)
|
||||
cmd += " " + class_name
|
||||
cmd += " $(OUTS)"
|
||||
|
||||
native.genrule(
|
||||
name = name + "." + output_file,
|
||||
outs = [output_file],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + [HEADER_DAT_DIR + dat_file],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def gen_modinit_hpp(
|
||||
name,
|
||||
libname,
|
||||
input_dats,
|
||||
output_file):
|
||||
input_dats = [HEADER_DAT_DIR + x + ".dat" for x in input_dats]
|
||||
|
||||
cmd = _wrapper() + " semiwrap.cmd.gen_modinit_hpp "
|
||||
cmd += " " + libname
|
||||
cmd += " $(OUTS)"
|
||||
for input_dat in input_dats:
|
||||
cmd += _location_helper(input_dat)
|
||||
|
||||
native.genrule(
|
||||
name = name + ".gen",
|
||||
outs = [GEN_MODINIT_HDR_DIR + output_file],
|
||||
cmd = cmd,
|
||||
tools = _wrapper_dep() + input_dats,
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
cc_library(
|
||||
name = name,
|
||||
hdrs = [GEN_MODINIT_HDR_DIR + output_file],
|
||||
strip_include_prefix = GEN_MODINIT_HDR_DIR,
|
||||
tags = ["robotpy"],
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
)
|
||||
|
||||
def run_header_gen(name, casters_pickle, trampoline_subpath, header_gen_config, deps = [], generation_defines = [], local_native_libraries = [], header_to_dat_deps = [], yml_prefix = "src/main/python/"):
|
||||
generation_includes = []
|
||||
header_to_dat_deps = list(header_to_dat_deps)
|
||||
|
||||
for dep in local_native_libraries:
|
||||
header_to_dat_deps.append(dep)
|
||||
generation_includes.append("$(execpath " + dep + ")")
|
||||
|
||||
for header_gen in header_gen_config:
|
||||
header_to_dat(
|
||||
name = name + ".header_to_dat",
|
||||
casters_pickle = casters_pickle,
|
||||
include_root = header_gen.header_root,
|
||||
class_name = header_gen.class_name,
|
||||
yml_file = yml_prefix + header_gen.yml_file,
|
||||
header_location = header_gen.header_file,
|
||||
deps = deps,
|
||||
generation_includes = generation_includes,
|
||||
extra_defines = generation_defines,
|
||||
header_to_dat_deps = header_to_dat_deps,
|
||||
)
|
||||
|
||||
generated_cc_files = []
|
||||
for header_gen in header_gen_config:
|
||||
dat_to_cc(
|
||||
name = name + ".dat_to_cc",
|
||||
class_name = header_gen.class_name,
|
||||
)
|
||||
generated_cc_files.append(DAT_TO_CC_DIR + header_gen.class_name + ".cpp")
|
||||
|
||||
tmpl_hdrs = []
|
||||
for header_gen in header_gen_config:
|
||||
if header_gen.tmpl_class_names:
|
||||
dat_to_tmpl_hpp(
|
||||
name = name + ".dat_to_tmpl_hpp",
|
||||
class_name = header_gen.class_name,
|
||||
)
|
||||
tmpl_hdrs.append(DAT_TO_TMPL_HDR_DIR + header_gen.class_name + "_tmpl.hpp")
|
||||
|
||||
for tmpl_class_name, specialization in header_gen.tmpl_class_names:
|
||||
dat_to_tmpl_cpp(
|
||||
name = name + ".dat_to_tmpl_cpp",
|
||||
base_class_name = header_gen.class_name,
|
||||
specialization = specialization,
|
||||
tmp_class_name = tmpl_class_name,
|
||||
)
|
||||
generated_cc_files.append(DAT_TO_TMPL_CC_DIR + tmpl_class_name + ".cpp")
|
||||
|
||||
trampoline_hdrs = []
|
||||
for header_gen in header_gen_config:
|
||||
for trampoline_symbol, trampoline_header in header_gen.trampolines:
|
||||
output_path = trampoline_subpath + "/trampolines/" + trampoline_header
|
||||
dat_to_trampoline(
|
||||
name = name + ".dat2trampoline",
|
||||
dat_file = header_gen.class_name + ".dat",
|
||||
class_name = trampoline_symbol,
|
||||
output_file = output_path,
|
||||
)
|
||||
trampoline_hdrs.append(output_path)
|
||||
cc_library(
|
||||
name = name + ".tmpl_hdrs",
|
||||
hdrs = tmpl_hdrs,
|
||||
strip_include_prefix = DAT_TO_TMPL_HDR_DIR,
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
cc_library(
|
||||
name = name + ".trampoline_hdrs",
|
||||
hdrs = trampoline_hdrs,
|
||||
strip_include_prefix = trampoline_subpath,
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = name + ".generated_srcs",
|
||||
srcs = generated_cc_files,
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = name + ".trampoline_hdr_files",
|
||||
srcs = trampoline_hdrs,
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = name + ".header_gen_files",
|
||||
srcs = tmpl_hdrs + trampoline_hdrs + generated_cc_files,
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
92
shared/bazel/rules/robotpy/semiwrap_tool_helpers.bzl
Normal file
92
shared/bazel/rules/robotpy/semiwrap_tool_helpers.bzl
Normal file
@@ -0,0 +1,92 @@
|
||||
load("@allwpilib_pip_deps//:requirements.bzl", "requirement")
|
||||
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
|
||||
load("@rules_python//python:defs.bzl", "py_test")
|
||||
load("//shared/bazel/rules/robotpy:compatibility_select.bzl", "robotpy_compatibility_select")
|
||||
|
||||
def __update_yaml_files_impl(ctx):
|
||||
output_dir = ctx.actions.declare_directory(ctx.attr.gen_dir)
|
||||
|
||||
args = ctx.actions.args()
|
||||
args.add("semiwrap.tool")
|
||||
args.add("update-yaml")
|
||||
args.add("--write")
|
||||
args.add("-v")
|
||||
args.add("--project_file=" + ctx.files.pyproject_toml[0].path)
|
||||
args.add("--override_output_directory=" + output_dir.path)
|
||||
|
||||
if ctx.files.pkgcfgs:
|
||||
args.add("--pkgcfgs")
|
||||
for f in ctx.files.pkgcfgs:
|
||||
args.add(str(f.path))
|
||||
|
||||
ctx.actions.run(
|
||||
inputs = ctx.files.package_root_file + ctx.files.pyproject_toml + ctx.files.pkgcfgs + ctx.files.extra_hdrs + ctx.files.yaml_files,
|
||||
outputs = [output_dir],
|
||||
executable = ctx.executable._tool,
|
||||
arguments = [args],
|
||||
)
|
||||
|
||||
return [DefaultInfo(files = depset([output_dir]))]
|
||||
|
||||
__update_yaml_files = rule(
|
||||
implementation = __update_yaml_files_impl,
|
||||
attrs = {
|
||||
"extra_hdrs": attr.label_list(allow_files = True),
|
||||
"gen_dir": attr.string(mandatory = True),
|
||||
"package_root_file": attr.label(mandatory = True, allow_files = True),
|
||||
"pkgcfgs": attr.label_list(allow_files = True),
|
||||
"pyproject_toml": attr.label(mandatory = True, allow_files = True),
|
||||
"yaml_files": attr.label_list(mandatory = True, allow_files = True),
|
||||
"_tool": attr.label(
|
||||
default = Label("//shared/bazel/rules/robotpy:wrapper"),
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
def update_yaml_files(name, yaml_output_directory = "src/main/python/semiwrap", **kwargs):
|
||||
__update_yaml_files(
|
||||
name = name,
|
||||
gen_dir = "{}_gen_update_yaml".format(name),
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
write_source_files(
|
||||
name = "write_{}".format(name),
|
||||
files = {
|
||||
yaml_output_directory: ":" + name,
|
||||
},
|
||||
suggested_update_target = "//:write_all",
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
def scan_headers(name, pyproject_toml, package_root_file, extra_hdrs, pkgcfgs):
|
||||
if pkgcfgs:
|
||||
pkgcfg_args = ["--pkgcfgs"]
|
||||
for pkgcfg in pkgcfgs:
|
||||
pkgcfg_args.append(" $(locations " + pkgcfg + ")")
|
||||
else:
|
||||
pkgcfg_args = []
|
||||
|
||||
py_test(
|
||||
name = name,
|
||||
srcs = [
|
||||
"//shared/bazel/rules/robotpy:wrapper.py",
|
||||
],
|
||||
deps = [
|
||||
"//shared/bazel/rules/robotpy:hack_pkgcfgs",
|
||||
requirement("semiwrap"),
|
||||
],
|
||||
args = [
|
||||
"semiwrap.tool",
|
||||
"scan-headers",
|
||||
"--pyproject=$(location " + pyproject_toml + ")",
|
||||
] + pkgcfg_args,
|
||||
data = extra_hdrs + pkgcfgs + [pyproject_toml, package_root_file],
|
||||
main = "shared/bazel/rules/robotpy/wrapper.py",
|
||||
size = "small",
|
||||
target_compatible_with = robotpy_compatibility_select(),
|
||||
)
|
||||
55
shared/bazel/rules/robotpy/wrapper.py
Normal file
55
shared/bazel/rules/robotpy/wrapper.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import importlib
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
from shared.bazel.rules.robotpy.hack_pkgcfgs import hack_pkgconfig
|
||||
|
||||
"""
|
||||
This file will wrap various semiwrap.tools executables. In the event that it fails
|
||||
it will provide more helpful debug information for bazel users.
|
||||
|
||||
It can also "hack" the PKG_CONFIG_PATH environment variable. This allows us to use
|
||||
generated package config files without having to install the libraries which decreases
|
||||
build dependencies and increases the amount of parallelization that can happen during
|
||||
the build.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
tool = sys.argv[1]
|
||||
|
||||
if "--pkgcfgs" in sys.argv[2:]:
|
||||
pkgcfg_index = sys.argv.index("--pkgcfgs")
|
||||
args = sys.argv[2:pkgcfg_index]
|
||||
pkgcfgs = [pathlib.Path(x) for x in sys.argv[pkgcfg_index + 1 :]]
|
||||
else:
|
||||
args = sys.argv[2:]
|
||||
pkgcfgs = []
|
||||
|
||||
hack_pkgconfig(pkgcfgs)
|
||||
|
||||
module = importlib.import_module(tool)
|
||||
tool_main = getattr(module, "main")
|
||||
|
||||
sys.argv = [""] + args
|
||||
try:
|
||||
tool_main()
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
raise Exception(
|
||||
"sys.exit() explicitly called with a non-zero error code", e
|
||||
)
|
||||
except:
|
||||
print("-------------------------------------")
|
||||
print("Failed to run wrapped tool.")
|
||||
print(f"CWD: {os.getcwd()}")
|
||||
print(f"Tool: {tool}, Args:")
|
||||
for a in args:
|
||||
print(" ", a)
|
||||
print("-------------------------------------")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -15,6 +15,9 @@ generatedFileExclude {
|
||||
src/main/native/include/wpinet/http_parser\.h$
|
||||
src/main/native/resources/
|
||||
src/main/native/linux/AvahiClient
|
||||
|
||||
src/main/python/
|
||||
src/test/python/
|
||||
}
|
||||
|
||||
licenseUpdateExclude {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
load("@allwpilib_pip_deps//:requirements.bzl", "requirement")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
load("@rules_pkg//:mappings.bzl", "pkg_files")
|
||||
@@ -6,6 +7,10 @@ load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test")
|
||||
load("//shared/bazel/rules:jni_rules.bzl", "wpilib_jni_cc_library", "wpilib_jni_java_library")
|
||||
load("//shared/bazel/rules:packaging.bzl", "package_minimal_jni_project")
|
||||
load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources")
|
||||
load("//shared/bazel/rules/robotpy:build_info_gen.bzl", "generate_robotpy_native_wrapper_build_info", "generate_robotpy_pybind_build_info")
|
||||
load("//shared/bazel/rules/robotpy:pytest_util.bzl", "robotpy_py_test")
|
||||
load("//wpinet:robotpy_native_build_info.bzl", "define_native_wrapper")
|
||||
load("//wpinet:robotpy_pybind_build_info.bzl", "define_pybind_library", "wpinet_extension")
|
||||
|
||||
filegroup(
|
||||
name = "doxygen-files",
|
||||
@@ -304,6 +309,55 @@ cc_binary(
|
||||
],
|
||||
)
|
||||
|
||||
generate_robotpy_native_wrapper_build_info(
|
||||
name = "robotpy-native-wpinet-generator",
|
||||
pyproject_toml = "src/main/python/native-pyproject.toml",
|
||||
third_party_dirs = [
|
||||
"libuv",
|
||||
"tcpsockets",
|
||||
],
|
||||
)
|
||||
|
||||
define_native_wrapper(
|
||||
name = "robotpy-native-wpinet",
|
||||
pyproject_toml = "src/main/python/native-pyproject.toml",
|
||||
)
|
||||
|
||||
PYBIND_PKGCFG_DEPS = [
|
||||
"//wpinet:native/wpinet/robotpy-native-wpinet.pc",
|
||||
"//wpiutil:native/wpiutil/robotpy-native-wpiutil.pc",
|
||||
"//wpiutil:robotpy-wpiutil.generated_pkgcfg_files",
|
||||
]
|
||||
|
||||
generate_robotpy_pybind_build_info(
|
||||
name = "robotpy-wpinet-generator",
|
||||
additional_srcs = [":robotpy-native-wpinet.copy_headers"],
|
||||
package_root_file = "src/main/python/wpinet/__init__.py",
|
||||
pkgcfgs = PYBIND_PKGCFG_DEPS,
|
||||
yaml_files = glob(["src/main/python/semiwrap/*.yml"]),
|
||||
)
|
||||
|
||||
wpinet_extension(
|
||||
srcs = ["src/main/python/wpinet/src/main.cpp"],
|
||||
includes = [
|
||||
"src/main/python/wpinet/",
|
||||
],
|
||||
)
|
||||
|
||||
define_pybind_library(
|
||||
name = "robotpy-wpinet",
|
||||
pkgcfgs = PYBIND_PKGCFG_DEPS,
|
||||
)
|
||||
|
||||
robotpy_py_test(
|
||||
"python_tests",
|
||||
srcs = glob(["src/test/python/**/*.py"]),
|
||||
deps = [
|
||||
":robotpy-wpinet",
|
||||
requirement("pytest"),
|
||||
],
|
||||
)
|
||||
|
||||
package_minimal_jni_project(
|
||||
name = "wpinet",
|
||||
maven_artifact_name = "wpinet-cpp",
|
||||
|
||||
39
wpinet/robotpy_native_build_info.bzl
generated
Normal file
39
wpinet/robotpy_native_build_info.bzl
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
# THIS FILE IS AUTO GENERATED
|
||||
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "native_wrappery_library")
|
||||
|
||||
def define_native_wrapper(name, pyproject_toml = None):
|
||||
copy_to_directory(
|
||||
name = "{}.copy_headers".format(name),
|
||||
srcs = native.glob(["src/main/native/include/**"]) + native.glob(["src/generated/main/native/include/**"], allow_empty = True) + native.glob([
|
||||
"src/main/native/thirdparty/libuv/include/**",
|
||||
"src/main/native/thirdparty/tcpsockets/include/**",
|
||||
]),
|
||||
out = "native/wpinet/include",
|
||||
root_paths = ["src/main/native/include/"],
|
||||
replace_prefixes = {
|
||||
"wpinet/src/generated/main/native/include": "",
|
||||
"wpinet/src/main/native/include": "",
|
||||
"wpinet/src/main/native/thirdparty/libuv/include": "",
|
||||
"wpinet/src/main/native/thirdparty/tcpsockets/include": "",
|
||||
},
|
||||
verbose = False,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
native_wrappery_library(
|
||||
name = name,
|
||||
pyproject_toml = pyproject_toml or "src/main/python/native-pyproject.toml",
|
||||
libinit_file = "native/wpinet/_init_robotpy_native_wpinet.py",
|
||||
pc_file = "native/wpinet/robotpy-native-wpinet.pc",
|
||||
pc_deps = [
|
||||
"//wpiutil:native/wpiutil/robotpy-native-wpiutil.pc",
|
||||
],
|
||||
deps = [
|
||||
"//wpiutil:robotpy-native-wpiutil",
|
||||
],
|
||||
headers = "{}.copy_headers".format(name),
|
||||
native_shared_library = "shared/wpinet",
|
||||
install_path = "native/wpinet/",
|
||||
)
|
||||
174
wpinet/robotpy_pybind_build_info.bzl
generated
Normal file
174
wpinet/robotpy_pybind_build_info.bzl
generated
Normal file
@@ -0,0 +1,174 @@
|
||||
# THIS FILE IS AUTO GENERATED
|
||||
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "create_pybind_library", "robotpy_library")
|
||||
load("//shared/bazel/rules/robotpy:semiwrap_helpers.bzl", "gen_libinit", "gen_modinit_hpp", "gen_pkgconf", "resolve_casters", "run_header_gen")
|
||||
load("//shared/bazel/rules/robotpy:semiwrap_tool_helpers.bzl", "scan_headers", "update_yaml_files")
|
||||
|
||||
def wpinet_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includes = [], extra_pyi_deps = []):
|
||||
WPINET_HEADER_GEN = [
|
||||
struct(
|
||||
class_name = "PortForwarder",
|
||||
yml_file = "semiwrap/PortForwarder.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpinet.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpinet.copy_headers)/wpinet/PortForwarder.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::PortForwarder", "wpi__PortForwarder.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "WebServer",
|
||||
yml_file = "semiwrap/WebServer.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpinet.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpinet.copy_headers)/wpinet/WebServer.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::WebServer", "wpi__WebServer.hpp"),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
resolve_casters(
|
||||
name = "wpinet.resolve_casters",
|
||||
caster_deps = ["//wpiutil:src/main/python/wpiutil/wpiutil-casters.pybind11.json"],
|
||||
casters_pkl_file = "wpinet.casters.pkl",
|
||||
dep_file = "wpinet.casters.d",
|
||||
)
|
||||
|
||||
gen_libinit(
|
||||
name = "wpinet.gen_lib_init",
|
||||
output_file = "src/main/python/wpinet/_init__wpinet.py",
|
||||
modules = ["native.wpinet._init_robotpy_native_wpinet", "wpiutil._init__wpiutil"],
|
||||
)
|
||||
|
||||
gen_pkgconf(
|
||||
name = "wpinet.gen_pkgconf",
|
||||
libinit_py = "wpinet._init__wpinet",
|
||||
module_pkg_name = "wpinet._wpinet",
|
||||
output_file = "wpinet.pc",
|
||||
pkg_name = "wpinet",
|
||||
install_path = "src/main/python/wpinet",
|
||||
project_file = "src/main/python/pyproject.toml",
|
||||
package_root = "src/main/python/wpinet/__init__.py",
|
||||
)
|
||||
|
||||
gen_modinit_hpp(
|
||||
name = "wpinet.gen_modinit_hpp",
|
||||
input_dats = [x.class_name for x in WPINET_HEADER_GEN],
|
||||
libname = "_wpinet",
|
||||
output_file = "semiwrap_init.wpinet._wpinet.hpp",
|
||||
)
|
||||
|
||||
run_header_gen(
|
||||
name = "wpinet",
|
||||
casters_pickle = "wpinet.casters.pkl",
|
||||
header_gen_config = WPINET_HEADER_GEN,
|
||||
trampoline_subpath = "src/main/python/wpinet",
|
||||
deps = header_to_dat_deps,
|
||||
local_native_libraries = [
|
||||
"//wpinet:robotpy-native-wpinet.copy_headers",
|
||||
"//wpiutil:robotpy-native-wpiutil.copy_headers",
|
||||
],
|
||||
)
|
||||
|
||||
create_pybind_library(
|
||||
name = "wpinet",
|
||||
install_path = "src/main/python/wpinet/",
|
||||
extension_name = "_wpinet",
|
||||
generated_srcs = [":wpinet.generated_srcs"],
|
||||
semiwrap_header = [":wpinet.gen_modinit_hpp"],
|
||||
deps = [
|
||||
":wpinet.tmpl_hdrs",
|
||||
":wpinet.trampoline_hdrs",
|
||||
"//wpinet:wpinet",
|
||||
"//wpiutil:wpiutil",
|
||||
"//wpiutil:wpiutil_pybind_library",
|
||||
],
|
||||
dynamic_deps = [
|
||||
"//wpinet:shared/wpinet",
|
||||
"//wpiutil:shared/wpiutil",
|
||||
],
|
||||
extra_hdrs = extra_hdrs,
|
||||
extra_srcs = srcs,
|
||||
includes = includes,
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = "wpinet.generated_files",
|
||||
srcs = [
|
||||
"wpinet.gen_modinit_hpp.gen",
|
||||
"wpinet.header_gen_files",
|
||||
"wpinet.gen_pkgconf",
|
||||
"wpinet.gen_lib_init",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
def define_pybind_library(name, pkgcfgs = []):
|
||||
# Helper used to generate all files with one target.
|
||||
native.filegroup(
|
||||
name = "{}.generated_files".format(name),
|
||||
srcs = [
|
||||
"wpinet.generated_files",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Files that will be included in the wheel as data deps
|
||||
native.filegroup(
|
||||
name = "{}.generated_pkgcfg_files".format(name),
|
||||
srcs = [
|
||||
"src/main/python/wpinet/wpinet.pc",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Contains all of the non-python files that need to be included in the wheel
|
||||
native.filegroup(
|
||||
name = "{}.extra_files".format(name),
|
||||
srcs = native.glob(["src/main/python/wpinet/**"], exclude = ["src/main/python/wpinet/**/*.py"], allow_empty = True),
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
robotpy_library(
|
||||
name = name,
|
||||
srcs = native.glob(["src/main/python/wpinet/**/*.py"]) + [
|
||||
"src/main/python/wpinet/_init__wpinet.py",
|
||||
],
|
||||
data = [
|
||||
"{}.generated_pkgcfg_files".format(name),
|
||||
"{}.extra_files".format(name),
|
||||
":src/main/python/wpinet/_wpinet",
|
||||
":wpinet.trampoline_hdr_files",
|
||||
],
|
||||
imports = ["src/main/python"],
|
||||
deps = [
|
||||
"//wpinet:robotpy-native-wpinet",
|
||||
"//wpiutil:robotpy-wpiutil",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
update_yaml_files(
|
||||
name = "{}-update-yaml".format(name),
|
||||
yaml_output_directory = "src/main/python/semiwrap",
|
||||
extra_hdrs = native.glob(["src/main/python/**/*.h"], allow_empty = True) + [
|
||||
"//wpinet:robotpy-native-wpinet.copy_headers",
|
||||
],
|
||||
package_root_file = "src/main/python/wpinet/__init__.py",
|
||||
pkgcfgs = pkgcfgs,
|
||||
pyproject_toml = "src/main/python/pyproject.toml",
|
||||
yaml_files = native.glob(["src/main/python/semiwrap/**"]),
|
||||
)
|
||||
|
||||
scan_headers(
|
||||
name = "{}-scan-headers".format(name),
|
||||
extra_hdrs = native.glob(["src/main/python/**/*.h"], allow_empty = True) + [
|
||||
"//wpinet:robotpy-native-wpinet.copy_headers",
|
||||
],
|
||||
package_root_file = "src/main/python/wpinet/__init__.py",
|
||||
pkgcfgs = pkgcfgs,
|
||||
pyproject_toml = "src/main/python/pyproject.toml",
|
||||
)
|
||||
7
wpinet/src/main/python/README.md
Normal file
7
wpinet/src/main/python/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
robotpy-wpinet
|
||||
==============
|
||||
|
||||
Python wrappers for WPILib's wpinet library.
|
||||
|
||||
* Installation instructions can be found in the [RobotPy documentation](https://robotpy.readthedocs.io/en/latest/getting_started.html)
|
||||
* Documentation can be found at [readthedocs](https://robotpy.readthedocs.io/projects/wpinet/en/stable/api.html)
|
||||
17
wpinet/src/main/python/examples/portfwd.py
Executable file
17
wpinet/src/main/python/examples/portfwd.py
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import time
|
||||
import wpinet
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("port", type=int, help="Local port number")
|
||||
parser.add_argument("remoteHost", help="Remote IP address / DNS name")
|
||||
parser.add_argument("remotePort", type=int, help="remote port number")
|
||||
args = parser.parse_args()
|
||||
|
||||
wpinet.PortForwarder.getInstance().add(args.port, args.remoteHost, args.remotePort)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
39
wpinet/src/main/python/native-pyproject.toml
Normal file
39
wpinet/src/main/python/native-pyproject.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = [
|
||||
"hatchling",
|
||||
"hatch-nativelib~=0.2.0",
|
||||
"hatch-robotpy~=0.2.1",
|
||||
"robotpy-native-wpiutil==2027.0.0a2",
|
||||
]
|
||||
|
||||
[project]
|
||||
name = "robotpy-native-wpinet"
|
||||
version = "2027.0.0a2"
|
||||
description = "WPILib Networking Library"
|
||||
license = "BSD-3-Clause"
|
||||
|
||||
dependencies = [
|
||||
"robotpy-native-wpiutil==2027.0.0a2",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/native"]
|
||||
|
||||
[[tool.hatch.build.hooks.robotpy.maven_lib_download]]
|
||||
artifact_id = "wpinet-cpp"
|
||||
group_id = "edu.wpi.first.wpinet"
|
||||
repo_url = "https://frcmaven.wpi.edu/artifactory/release-2027"
|
||||
version = "2027.0.0-alpha-2"
|
||||
|
||||
extract_to = "src/native/wpinet"
|
||||
libs = ["wpinet"]
|
||||
|
||||
[[tool.hatch.build.hooks.nativelib.pcfile]]
|
||||
pcfile = "src/native/wpinet/robotpy-native-wpinet.pc"
|
||||
name = "wpinet"
|
||||
|
||||
includedir = "src/native/wpinet/include"
|
||||
libdir = "src/native/wpinet/lib"
|
||||
shared_libraries = ["wpinet"]
|
||||
requires = ["robotpy-native-wpiutil"]
|
||||
54
wpinet/src/main/python/pyproject.toml
Normal file
54
wpinet/src/main/python/pyproject.toml
Normal file
@@ -0,0 +1,54 @@
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = [
|
||||
"semiwrap~=0.1.7",
|
||||
"hatch-meson~=0.1.0b2",
|
||||
"hatchling",
|
||||
"robotpy-native-wpinet==2027.0.0a2",
|
||||
"robotpy-wpiutil==2027.0.0a2"
|
||||
]
|
||||
|
||||
[project]
|
||||
name = "robotpy-wpinet"
|
||||
version = "2027.0.0a2"
|
||||
description = "Binary wrapper for FRC wpinet library"
|
||||
authors = [
|
||||
{name = "RobotPy Development Team", email = "robotpy@googlegroups.com"},
|
||||
]
|
||||
license = "BSD-3-Clause"
|
||||
dependencies = [
|
||||
"robotpy-native-wpinet==2027.0.0a2",
|
||||
"robotpy-wpiutil==2027.0.0a2"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Source code" = "https://github.com/robotpy/mostrobotpy"
|
||||
|
||||
|
||||
[tool.hatch.build.hooks.robotpy]
|
||||
version_file = "wpinet/version.py"
|
||||
|
||||
[tool.hatch.build.hooks.semiwrap]
|
||||
|
||||
[tool.hatch.build.hooks.meson]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["wpinet"]
|
||||
|
||||
|
||||
[tool.semiwrap]
|
||||
update_init = [
|
||||
"wpinet"
|
||||
]
|
||||
# we don't wrap anything here
|
||||
scan_headers_ignore = ["*"]
|
||||
|
||||
[tool.semiwrap.extension_modules."wpinet._wpinet"]
|
||||
name = "wpinet"
|
||||
wraps = ["robotpy-native-wpinet"]
|
||||
depends = ["wpiutil"]
|
||||
|
||||
[tool.semiwrap.extension_modules."wpinet._wpinet".headers]
|
||||
# wpinet
|
||||
PortForwarder = "wpinet/PortForwarder.h"
|
||||
WebServer = "wpinet/WebServer.h"
|
||||
8
wpinet/src/main/python/semiwrap/PortForwarder.yml
Normal file
8
wpinet/src/main/python/semiwrap/PortForwarder.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
classes:
|
||||
wpi::PortForwarder:
|
||||
nodelete: true
|
||||
methods:
|
||||
GetInstance:
|
||||
return_value_policy: reference
|
||||
Add:
|
||||
Remove:
|
||||
8
wpinet/src/main/python/semiwrap/WebServer.yml
Normal file
8
wpinet/src/main/python/semiwrap/WebServer.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
classes:
|
||||
wpi::WebServer:
|
||||
nodelete: true
|
||||
methods:
|
||||
GetInstance:
|
||||
return_value_policy: reference
|
||||
Start:
|
||||
Stop:
|
||||
7
wpinet/src/main/python/wpinet/__init__.py
Normal file
7
wpinet/src/main/python/wpinet/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from . import _init__wpinet
|
||||
|
||||
|
||||
# autogenerated by 'semiwrap create-imports wpinet wpinet._wpinet'
|
||||
from ._wpinet import PortForwarder, WebServer
|
||||
|
||||
__all__ = ["PortForwarder", "WebServer"]
|
||||
0
wpinet/src/main/python/wpinet/py.typed
Normal file
0
wpinet/src/main/python/wpinet/py.typed
Normal file
4
wpinet/src/main/python/wpinet/src/main.cpp
Normal file
4
wpinet/src/main/python/wpinet/src/main.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
#include <semiwrap_init.wpinet._wpinet.hpp>
|
||||
|
||||
SEMIWRAP_PYBIND11_MODULE(m) { initWrapper(m); }
|
||||
12
wpinet/src/test/python/run_tests.py
Executable file
12
wpinet/src/test/python/run_tests.py
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from os.path import abspath, dirname
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = abspath(dirname(__file__))
|
||||
os.chdir(root)
|
||||
|
||||
subprocess.check_call([sys.executable, "-m", "pytest"])
|
||||
5
wpinet/src/test/python/test_wpinet.py
Normal file
5
wpinet/src/test/python/test_wpinet.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import wpinet
|
||||
|
||||
|
||||
def test_existance():
|
||||
pass
|
||||
@@ -20,6 +20,9 @@ modifiableFileExclude {
|
||||
generatedFileExclude {
|
||||
src/main/native/thirdparty/
|
||||
|
||||
src/main/python/
|
||||
src/test/python/
|
||||
|
||||
src/main/native/include/wpi/fs\.h$
|
||||
src/main/native/include/wpi/FastQueue\.h$
|
||||
src/main/native/cpp/fs\.cpp$
|
||||
|
||||
@@ -2,13 +2,18 @@ load("@allwpilib_pip_deps//:requirements.bzl", "requirement")
|
||||
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
load("@rules_python//python:defs.bzl", "py_binary")
|
||||
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
|
||||
load("//shared/bazel/rules:cc_rules.bzl", "third_party_cc_lib_helper", "wpilib_cc_library", "wpilib_cc_shared_library", "wpilib_cc_static_library")
|
||||
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test")
|
||||
load("//shared/bazel/rules:jni_rules.bzl", "wpilib_jni_cc_library", "wpilib_jni_java_library")
|
||||
load("//shared/bazel/rules:packaging.bzl", "package_default_jni_project")
|
||||
load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources")
|
||||
load("//shared/bazel/rules/robotpy:build_info_gen.bzl", "generate_robotpy_native_wrapper_build_info", "generate_robotpy_pybind_build_info")
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "create_pybind_library")
|
||||
load("//shared/bazel/rules/robotpy:pytest_util.bzl", "robotpy_py_test")
|
||||
load("//wpiutil:generate.bzl", "generate_wpiutil")
|
||||
load("//wpiutil:robotpy_native_build_info.bzl", "define_native_wrapper")
|
||||
load("//wpiutil:robotpy_pybind_build_info.bzl", "define_pybind_library", "publish_library_casters", "wpiutil_extension")
|
||||
|
||||
filegroup(
|
||||
name = "doxygen-files",
|
||||
@@ -315,6 +320,95 @@ java_binary(
|
||||
],
|
||||
)
|
||||
|
||||
generate_robotpy_native_wrapper_build_info(
|
||||
name = "robotpy-native-wpiutil-generator",
|
||||
pyproject_toml = "src/main/python/native-pyproject.toml",
|
||||
third_party_dirs = [
|
||||
"argparse",
|
||||
"debugging",
|
||||
"expected",
|
||||
"fmtlib",
|
||||
"json",
|
||||
"llvm",
|
||||
"mpack",
|
||||
"nanopb",
|
||||
"sigslot",
|
||||
"upb",
|
||||
],
|
||||
)
|
||||
|
||||
define_native_wrapper(
|
||||
name = "robotpy-native-wpiutil",
|
||||
)
|
||||
|
||||
PYBIND_PKGCFG_DEPS = ["//wpiutil:native/wpiutil/robotpy-native-wpiutil.pc"]
|
||||
|
||||
generate_robotpy_pybind_build_info(
|
||||
name = "robotpy-wpiutil-generator",
|
||||
additional_srcs = ["src/main/python/wpiutil/src/wpistruct/wpystruct_fns.h"] + [":robotpy-native-wpiutil.copy_headers"],
|
||||
package_root_file = "src/main/python/wpiutil/__init__.py",
|
||||
pkgcfgs = PYBIND_PKGCFG_DEPS,
|
||||
pyproject_toml = "src/main/python/pyproject.toml",
|
||||
yaml_files = glob(["src/main/python/semiwrap/*.yml"]),
|
||||
)
|
||||
|
||||
publish_library_casters()
|
||||
|
||||
wpiutil_extension(
|
||||
srcs = glob(["src/main/python/wpiutil/src/**/*.cpp"]),
|
||||
extra_hdrs = glob([
|
||||
"src/main/python/wpiutil/src/type_casters/*.h",
|
||||
"src/main/python/wpiutil/src/wpistruct/*.h",
|
||||
]),
|
||||
header_to_dat_deps = ["src/main/python/wpiutil/src/wpistruct/wpystruct_fns.h"],
|
||||
includes = [
|
||||
"src/main/python/wpiutil/",
|
||||
"src/main/python/wpiutil/src/type_casters",
|
||||
"src/main/python/wpiutil/src/wpistruct",
|
||||
],
|
||||
)
|
||||
|
||||
define_pybind_library(
|
||||
name = "robotpy-wpiutil",
|
||||
pkgcfgs = PYBIND_PKGCFG_DEPS,
|
||||
)
|
||||
|
||||
create_pybind_library(
|
||||
name = "module",
|
||||
dynamic_deps = [
|
||||
":shared/wpiutil",
|
||||
],
|
||||
extension_name = "module",
|
||||
extra_srcs = glob(["src/test/python/cpp/wpiutil_test/*.cpp"]),
|
||||
install_path = "src/test/python/cpp/wpiutil_test/",
|
||||
deps = [
|
||||
":wpiutil_pybind_library",
|
||||
],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = "wpiutil_test",
|
||||
srcs = glob(["src/test/python/cpp/wpiutil_test/*.py"]),
|
||||
data = [
|
||||
":src/test/python/cpp/wpiutil_test/module",
|
||||
],
|
||||
imports = ["src/test/python/cpp"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
robotpy_py_test(
|
||||
"wpiutil_tests",
|
||||
srcs = glob(
|
||||
["src/test/python/**/*.py"],
|
||||
exclude = ["src/test/python/cpp/**"],
|
||||
),
|
||||
deps = [
|
||||
":robotpy-wpiutil",
|
||||
":wpiutil_test",
|
||||
requirement("pytest"),
|
||||
],
|
||||
)
|
||||
|
||||
package_default_jni_project(
|
||||
name = "wpiutil",
|
||||
maven_artifact_name = "wpiutil-cpp",
|
||||
|
||||
53
wpiutil/robotpy_native_build_info.bzl
generated
Normal file
53
wpiutil/robotpy_native_build_info.bzl
generated
Normal file
@@ -0,0 +1,53 @@
|
||||
# THIS FILE IS AUTO GENERATED
|
||||
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "native_wrappery_library")
|
||||
|
||||
def define_native_wrapper(name, pyproject_toml = None):
|
||||
copy_to_directory(
|
||||
name = "{}.copy_headers".format(name),
|
||||
srcs = native.glob(["src/main/native/include/**"]) + native.glob(["src/generated/main/native/include/**"], allow_empty = True) + native.glob([
|
||||
"src/main/native/thirdparty/argparse/include/**",
|
||||
"src/main/native/thirdparty/debugging/include/**",
|
||||
"src/main/native/thirdparty/expected/include/**",
|
||||
"src/main/native/thirdparty/fmtlib/include/**",
|
||||
"src/main/native/thirdparty/json/include/**",
|
||||
"src/main/native/thirdparty/llvm/include/**",
|
||||
"src/main/native/thirdparty/mpack/include/**",
|
||||
"src/main/native/thirdparty/nanopb/include/**",
|
||||
"src/main/native/thirdparty/sigslot/include/**",
|
||||
"src/main/native/thirdparty/upb/include/**",
|
||||
]),
|
||||
out = "native/wpiutil/include",
|
||||
root_paths = ["src/main/native/include/"],
|
||||
replace_prefixes = {
|
||||
"wpiutil/src/generated/main/native/include": "",
|
||||
"wpiutil/src/main/native/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/argparse/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/debugging/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/expected/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/fmtlib/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/json/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/llvm/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/mpack/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/nanopb/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/sigslot/include": "",
|
||||
"wpiutil/src/main/native/thirdparty/upb/include": "",
|
||||
},
|
||||
verbose = False,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
native_wrappery_library(
|
||||
name = name,
|
||||
pyproject_toml = pyproject_toml or "src/main/python/native-pyproject.toml",
|
||||
libinit_file = "native/wpiutil/_init_robotpy_native_wpiutil.py",
|
||||
pc_file = "native/wpiutil/robotpy-native-wpiutil.pc",
|
||||
pc_deps = [
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
headers = "{}.copy_headers".format(name),
|
||||
native_shared_library = "shared/wpiutil",
|
||||
install_path = "native/wpiutil/",
|
||||
)
|
||||
234
wpiutil/robotpy_pybind_build_info.bzl
generated
Normal file
234
wpiutil/robotpy_pybind_build_info.bzl
generated
Normal file
@@ -0,0 +1,234 @@
|
||||
# THIS FILE IS AUTO GENERATED
|
||||
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("//shared/bazel/rules/robotpy:pybind_rules.bzl", "create_pybind_library", "robotpy_library")
|
||||
load("//shared/bazel/rules/robotpy:semiwrap_helpers.bzl", "gen_libinit", "gen_modinit_hpp", "gen_pkgconf", "publish_casters", "resolve_casters", "run_header_gen")
|
||||
load("//shared/bazel/rules/robotpy:semiwrap_tool_helpers.bzl", "scan_headers", "update_yaml_files")
|
||||
|
||||
def wpiutil_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includes = [], extra_pyi_deps = []):
|
||||
WPIUTIL_HEADER_GEN = [
|
||||
struct(
|
||||
class_name = "StackTrace",
|
||||
yml_file = "semiwrap/StackTrace.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/StackTrace.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [],
|
||||
),
|
||||
struct(
|
||||
class_name = "Synchronization",
|
||||
yml_file = "semiwrap/Synchronization.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/Synchronization.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [],
|
||||
),
|
||||
struct(
|
||||
class_name = "RawFrame",
|
||||
yml_file = "semiwrap/RawFrame.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/RawFrame.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [],
|
||||
),
|
||||
struct(
|
||||
class_name = "Sendable",
|
||||
yml_file = "semiwrap/Sendable.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/sendable/Sendable.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::Sendable", "wpi__Sendable.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "SendableBuilder",
|
||||
yml_file = "semiwrap/SendableBuilder.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/sendable/SendableBuilder.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::SendableBuilder", "wpi__SendableBuilder.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "SendableRegistry",
|
||||
yml_file = "semiwrap/SendableRegistry.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/sendable/SendableRegistry.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::SendableRegistry", "wpi__SendableRegistry.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "WPyStruct",
|
||||
yml_file = "semiwrap/WPyStruct.yml",
|
||||
header_root = "wpiutil/src/main/python/wpiutil",
|
||||
header_file = "wpiutil/src/main/python/wpiutil/src/wpistruct/wpystruct_fns.h",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [],
|
||||
),
|
||||
]
|
||||
|
||||
resolve_casters(
|
||||
name = "wpiutil.resolve_casters",
|
||||
caster_deps = [":src/main/python/wpiutil/wpiutil-casters.pybind11.json"],
|
||||
casters_pkl_file = "wpiutil.casters.pkl",
|
||||
dep_file = "wpiutil.casters.d",
|
||||
)
|
||||
|
||||
gen_libinit(
|
||||
name = "wpiutil.gen_lib_init",
|
||||
output_file = "src/main/python/wpiutil/_init__wpiutil.py",
|
||||
modules = ["native.wpiutil._init_robotpy_native_wpiutil"],
|
||||
)
|
||||
|
||||
gen_pkgconf(
|
||||
name = "wpiutil.gen_pkgconf",
|
||||
libinit_py = "wpiutil._init__wpiutil",
|
||||
module_pkg_name = "wpiutil._wpiutil",
|
||||
output_file = "wpiutil.pc",
|
||||
pkg_name = "wpiutil",
|
||||
install_path = "src/main/python/wpiutil",
|
||||
project_file = "src/main/python/pyproject.toml",
|
||||
package_root = "src/main/python/wpiutil/__init__.py",
|
||||
)
|
||||
|
||||
gen_modinit_hpp(
|
||||
name = "wpiutil.gen_modinit_hpp",
|
||||
input_dats = [x.class_name for x in WPIUTIL_HEADER_GEN],
|
||||
libname = "_wpiutil",
|
||||
output_file = "semiwrap_init.wpiutil._wpiutil.hpp",
|
||||
)
|
||||
|
||||
run_header_gen(
|
||||
name = "wpiutil",
|
||||
casters_pickle = "wpiutil.casters.pkl",
|
||||
header_gen_config = WPIUTIL_HEADER_GEN,
|
||||
trampoline_subpath = "src/main/python/wpiutil",
|
||||
deps = header_to_dat_deps,
|
||||
local_native_libraries = [
|
||||
"//wpiutil:robotpy-native-wpiutil.copy_headers",
|
||||
],
|
||||
)
|
||||
|
||||
create_pybind_library(
|
||||
name = "wpiutil",
|
||||
install_path = "src/main/python/wpiutil/",
|
||||
extension_name = "_wpiutil",
|
||||
generated_srcs = [":wpiutil.generated_srcs"],
|
||||
semiwrap_header = [":wpiutil.gen_modinit_hpp"],
|
||||
deps = [
|
||||
":wpiutil.tmpl_hdrs",
|
||||
":wpiutil.trampoline_hdrs",
|
||||
"//wpiutil:wpiutil",
|
||||
"//wpiutil:wpiutil-casters",
|
||||
],
|
||||
dynamic_deps = [
|
||||
"//wpiutil:shared/wpiutil",
|
||||
],
|
||||
extra_hdrs = extra_hdrs,
|
||||
extra_srcs = srcs,
|
||||
includes = includes,
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = "wpiutil.generated_files",
|
||||
srcs = [
|
||||
"wpiutil.gen_modinit_hpp.gen",
|
||||
"wpiutil.header_gen_files",
|
||||
"wpiutil.gen_pkgconf",
|
||||
"wpiutil.gen_lib_init",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
def publish_library_casters():
|
||||
publish_casters(
|
||||
name = "publish_casters",
|
||||
caster_name = "wpiutil-casters",
|
||||
output_json = "src/main/python/wpiutil/wpiutil-casters.pybind11.json",
|
||||
output_pc = "src/main/python/wpiutil/wpiutil-casters.pc",
|
||||
project_config = "src/main/python/pyproject.toml",
|
||||
package_root = "src/main/python/wpiutil/__init__.py",
|
||||
typecasters_srcs = native.glob(["src/main/python/wpiutil/src/type_casters/**", "src/main/python/wpiutil/src/wpistruct/**"]),
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "wpiutil-casters",
|
||||
hdrs = native.glob(["src/main/python/wpiutil/src/type_casters/*.h", "src/main/python/wpiutil/src/wpistruct/*.h"]),
|
||||
includes = ["src/main/python/wpiutil/src/type_casters", "src/main/python/wpiutil/src/wpistruct"],
|
||||
visibility = ["//visibility:public"],
|
||||
tags = ["robotpy"],
|
||||
)
|
||||
|
||||
def define_pybind_library(name, pkgcfgs = []):
|
||||
# Helper used to generate all files with one target.
|
||||
native.filegroup(
|
||||
name = "{}.generated_files".format(name),
|
||||
srcs = [
|
||||
"wpiutil.generated_files",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Files that will be included in the wheel as data deps
|
||||
native.filegroup(
|
||||
name = "{}.generated_pkgcfg_files".format(name),
|
||||
srcs = [
|
||||
"src/main/python/wpiutil/wpiutil.pc",
|
||||
"src/main/python/wpiutil/wpiutil-casters.pc",
|
||||
"src/main/python/wpiutil/wpiutil-casters.pybind11.json",
|
||||
],
|
||||
tags = ["manual", "robotpy"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Contains all of the non-python files that need to be included in the wheel
|
||||
native.filegroup(
|
||||
name = "{}.extra_files".format(name),
|
||||
srcs = native.glob(["src/main/python/wpiutil/**"], exclude = ["src/main/python/wpiutil/**/*.py"], allow_empty = True),
|
||||
tags = ["manual", "robotpy"],
|
||||
)
|
||||
|
||||
robotpy_library(
|
||||
name = name,
|
||||
srcs = native.glob(["src/main/python/wpiutil/**/*.py"]) + [
|
||||
"src/main/python/wpiutil/_init__wpiutil.py",
|
||||
],
|
||||
data = [
|
||||
"{}.generated_pkgcfg_files".format(name),
|
||||
"{}.extra_files".format(name),
|
||||
":src/main/python/wpiutil/_wpiutil",
|
||||
":wpiutil.trampoline_hdr_files",
|
||||
],
|
||||
imports = ["src/main/python"],
|
||||
deps = [
|
||||
"//wpiutil:robotpy-native-wpiutil",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
update_yaml_files(
|
||||
name = "{}-update-yaml".format(name),
|
||||
yaml_output_directory = "src/main/python/semiwrap",
|
||||
extra_hdrs = native.glob(["src/main/python/**/*.h"], allow_empty = True) + [
|
||||
"//wpiutil:robotpy-native-wpiutil.copy_headers",
|
||||
],
|
||||
package_root_file = "src/main/python/wpiutil/__init__.py",
|
||||
pkgcfgs = pkgcfgs,
|
||||
pyproject_toml = "src/main/python/pyproject.toml",
|
||||
yaml_files = native.glob(["src/main/python/semiwrap/**"]),
|
||||
)
|
||||
|
||||
scan_headers(
|
||||
name = "{}-scan-headers".format(name),
|
||||
extra_hdrs = native.glob(["src/main/python/**/*.h"], allow_empty = True) + [
|
||||
"//wpiutil:robotpy-native-wpiutil.copy_headers",
|
||||
],
|
||||
package_root_file = "src/main/python/wpiutil/__init__.py",
|
||||
pkgcfgs = pkgcfgs,
|
||||
pyproject_toml = "src/main/python/pyproject.toml",
|
||||
)
|
||||
92
wpiutil/src/main/python/examples/printlog.py
Executable file
92
wpiutil/src/main/python/examples/printlog.py
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) FIRST and other WPILib contributors.
|
||||
# Open Source Software; you can modify and/or share it under the terms of
|
||||
# the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
|
||||
from wpiutil.log import DataLogReader
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("infile")
|
||||
args = parser.parse_args()
|
||||
|
||||
reader = DataLogReader(args.infile)
|
||||
|
||||
entries = {}
|
||||
for record in reader:
|
||||
timestamp = record.getTimestamp() / 1000000
|
||||
if record.isStart():
|
||||
try:
|
||||
data = record.getStartData()
|
||||
print(f"{data} [{timestamp}]")
|
||||
if data.entry in entries:
|
||||
print("...DUPLICATE entry ID, overriding")
|
||||
entries[data.entry] = data
|
||||
except TypeError as e:
|
||||
print("Start(INVALID)")
|
||||
elif record.isFinish():
|
||||
try:
|
||||
entry = record.getFinishEntry()
|
||||
print(f"Finish({entry}) [{timestamp}]")
|
||||
if entry not in entries:
|
||||
print("...ID not found")
|
||||
else:
|
||||
del entries[entry]
|
||||
except TypeError as e:
|
||||
print("Finish(INVALID)")
|
||||
elif record.isSetMetadata():
|
||||
try:
|
||||
data = record.getSetMetadataData()
|
||||
print(f"{data} [{timestamp}]")
|
||||
if data.entry not in entries:
|
||||
print("...ID not found")
|
||||
except TypeError as e:
|
||||
print("SetMetadata(INVALID)")
|
||||
elif record.isControl():
|
||||
print("Unrecognized control record")
|
||||
else:
|
||||
print(f"Data({record.getEntry()}, size={record.getSize()}) ", end="")
|
||||
entry = entries.get(record.getEntry(), None)
|
||||
if entry is None:
|
||||
print("<ID not found>")
|
||||
continue
|
||||
print(f"<name='{entry.name}', type='{entry.type}'> [{timestamp}]")
|
||||
|
||||
try:
|
||||
# handle systemTime specially
|
||||
if entry.name == "systemTime" and entry.type == "int64":
|
||||
dt = datetime.fromtimestamp(record.getInteger() / 1000000)
|
||||
print(" {:%Y-%m-%d %H:%M:%S.%f}".format(dt))
|
||||
continue
|
||||
|
||||
if entry.type == "double":
|
||||
print(f" {record.getDouble()}")
|
||||
elif entry.type == "int64":
|
||||
print(f" {record.getInteger()}")
|
||||
elif entry.type == "string" or entry.type == "json":
|
||||
print(f" '{record.getString()}'")
|
||||
elif entry.type == "boolean":
|
||||
print(f" {record.getBoolean()}")
|
||||
elif entry.type == "boolean[]":
|
||||
arr = record.getBooleanArray()
|
||||
print(f" {arr}")
|
||||
elif entry.type == "double[]":
|
||||
arr = record.getDoubleArray()
|
||||
print(f" {arr}")
|
||||
elif entry.type == "float[]":
|
||||
arr = record.getFloatArray()
|
||||
print(f" {arr}")
|
||||
elif entry.type == "int64[]":
|
||||
arr = record.getIntegerArray()
|
||||
print(f" {arr}")
|
||||
elif entry.type == "string[]":
|
||||
arr = record.getStringArray()
|
||||
print(f" {arr}")
|
||||
elif entry.type == "raw":
|
||||
print(f" {record.getRaw()}")
|
||||
except TypeError as e:
|
||||
print(" invalid", e)
|
||||
29
wpiutil/src/main/python/examples/writelog.py
Executable file
29
wpiutil/src/main/python/examples/writelog.py
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
|
||||
from wpiutil.log import DataLog, BooleanLogEntry, StringArrayLogEntry, RawLogEntry
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("out", type=pathlib.Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.out.is_dir():
|
||||
datalog = DataLog(str(args.out))
|
||||
else:
|
||||
datalog = DataLog(str(args.out.parent), args.out.name)
|
||||
|
||||
bools = BooleanLogEntry(datalog, "/bools")
|
||||
bools.append(True)
|
||||
bools.append(False)
|
||||
|
||||
strings = StringArrayLogEntry(datalog, "/strings")
|
||||
strings.append(["a", "b", "c"])
|
||||
strings.append(["d", "e", "f"])
|
||||
|
||||
raw = RawLogEntry(datalog, "/raws")
|
||||
raw.append(b"\x01\x02\x03")
|
||||
raw.append(b"\x04\x05\x06")
|
||||
52
wpiutil/src/main/python/native-pyproject.toml
Normal file
52
wpiutil/src/main/python/native-pyproject.toml
Normal file
@@ -0,0 +1,52 @@
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = [
|
||||
"hatchling",
|
||||
"hatch-nativelib~=0.2.0",
|
||||
"hatch-robotpy~=0.2.1",
|
||||
]
|
||||
|
||||
[project]
|
||||
name = "robotpy-native-wpiutil"
|
||||
version = "2027.0.0a2"
|
||||
description = "WPILib Utility Library"
|
||||
license = "BSD-3-Clause"
|
||||
|
||||
dependencies = [
|
||||
"msvc-runtime>=14.42.34433; platform_system == 'Windows'"
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/native"]
|
||||
|
||||
[[tool.hatch.build.hooks.robotpy.maven_lib_download]]
|
||||
artifact_id = "wpiutil-cpp"
|
||||
group_id = "edu.wpi.first.wpiutil"
|
||||
repo_url = "https://frcmaven.wpi.edu/artifactory/release-2027"
|
||||
version = "2027.0.0-alpha-2"
|
||||
|
||||
extract_to = "src/native/wpiutil"
|
||||
libs = ["wpiutil"]
|
||||
|
||||
[[tool.hatch.build.hooks.nativelib.pcfile]]
|
||||
pcfile = "src/native/wpiutil/robotpy-native-wpiutil.pc"
|
||||
name = "wpiutil"
|
||||
|
||||
includedir = "src/native/wpiutil/include"
|
||||
libdir = "src/native/wpiutil/lib"
|
||||
shared_libraries = ["wpiutil"]
|
||||
|
||||
enable_if = "platform_system != 'Windows'"
|
||||
|
||||
[[tool.hatch.build.hooks.nativelib.pcfile]]
|
||||
pcfile = "src/native/wpiutil/robotpy-native-wpiutil.pc"
|
||||
name = "wpiutil"
|
||||
|
||||
includedir = "src/native/wpiutil/include"
|
||||
libdir = "src/native/wpiutil/lib"
|
||||
shared_libraries = ["wpiutil"]
|
||||
|
||||
# All wpilib projects require this flag
|
||||
extra_cflags = "/Zc:preprocessor"
|
||||
|
||||
enable_if = "platform_system == 'Windows'"
|
||||
121
wpiutil/src/main/python/pyproject.toml
Normal file
121
wpiutil/src/main/python/pyproject.toml
Normal file
@@ -0,0 +1,121 @@
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = [
|
||||
"semiwrap~=0.1.7",
|
||||
"hatch-meson~=0.1.0b2",
|
||||
"hatch-robotpy~=0.2.1",
|
||||
"hatchling",
|
||||
"robotpy-native-wpiutil==2027.0.0a2",
|
||||
]
|
||||
|
||||
[project]
|
||||
name = "robotpy-wpiutil"
|
||||
version = "2027.0.0a2"
|
||||
description = "Binary wrapper for FRC WPIUtil library"
|
||||
authors = [
|
||||
{name = "RobotPy Development Team", email = "robotpy@googlegroups.com"},
|
||||
]
|
||||
license = "BSD-3-Clause"
|
||||
dependencies = [
|
||||
"robotpy-native-wpiutil==2027.0.0a2",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Source code" = "https://github.com/robotpy/mostrobotpy"
|
||||
|
||||
|
||||
[tool.hatch.build.hooks.robotpy]
|
||||
version_file = "wpiutil/version.py"
|
||||
|
||||
[tool.hatch.build.hooks.semiwrap]
|
||||
|
||||
[tool.hatch.build.hooks.meson]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["wpiutil"]
|
||||
|
||||
|
||||
[tool.semiwrap]
|
||||
update_init = [
|
||||
"wpiutil",
|
||||
# "wpiutil.log wpiutil._wpiutil.log",
|
||||
"wpiutil.sync wpiutil._wpiutil.sync",
|
||||
"wpiutil.wpistruct wpiutil._wpiutil.wpistruct",
|
||||
]
|
||||
scan_headers_ignore = [
|
||||
"debugging.hpp",
|
||||
"debugging/*",
|
||||
"fmt/*",
|
||||
"google/*",
|
||||
"wpi/*",
|
||||
"wpystruct_fns.h",
|
||||
"pb.h",
|
||||
"pb_common.h",
|
||||
"pb_decode.h",
|
||||
"pb_encode.h",
|
||||
]
|
||||
|
||||
[tool.semiwrap.extension_modules."wpiutil._wpiutil"]
|
||||
name = "wpiutil"
|
||||
includes = [
|
||||
"wpiutil/src/wpistruct",
|
||||
]
|
||||
wraps = ["robotpy-native-wpiutil"]
|
||||
depends = ["wpiutil-casters"]
|
||||
|
||||
[tool.semiwrap.extension_modules."wpiutil._wpiutil".headers]
|
||||
# wpi
|
||||
StackTrace = "wpi/StackTrace.h"
|
||||
Synchronization = "wpi/Synchronization.h"
|
||||
RawFrame = "wpi/RawFrame.h"
|
||||
|
||||
# wpi/sendable
|
||||
Sendable = "wpi/sendable/Sendable.h"
|
||||
SendableBuilder = "wpi/sendable/SendableBuilder.h"
|
||||
#SendableHelper = "wpi/sendable/SendableHelper.h"
|
||||
SendableRegistry = "wpi/sendable/SendableRegistry.h"
|
||||
|
||||
WPyStruct = "src/wpistruct/wpystruct_fns.h"
|
||||
|
||||
[tool.semiwrap.export_type_casters.wpiutil-casters]
|
||||
pypackage = "wpiutil"
|
||||
includedir = [
|
||||
"wpiutil/src/type_casters",
|
||||
"wpiutil/src/wpistruct",
|
||||
]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_array_type_caster.h"
|
||||
types = ["wpi::array"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_json_type_caster.h"
|
||||
types = ["wpi::json"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_span_type_caster.h"
|
||||
types = ["std::span"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_smallset_type_caster.h"
|
||||
types = ["wpi::SmallSet"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_smallvector_type_caster.h"
|
||||
types = ["wpi::SmallVector"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_smallvectorimpl_type_caster.h"
|
||||
types = ["wpi::SmallVectorImpl"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_string_map_caster.h"
|
||||
types = ["wpi::StringMap"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpi_ct_string_type_caster.h"
|
||||
types = ["wpi::ct_string"]
|
||||
|
||||
[[tool.semiwrap.export_type_casters.wpiutil-casters.headers]]
|
||||
header = "wpystruct.h"
|
||||
types = ["WPyStruct"]
|
||||
7
wpiutil/src/main/python/semiwrap/RawFrame.yml
Normal file
7
wpiutil/src/main/python/semiwrap/RawFrame.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
defaults:
|
||||
ignore: true
|
||||
|
||||
enums:
|
||||
WPI_TimestampSource:
|
||||
value_prefix: WPI_TIMESRC
|
||||
rename: TimestampSource
|
||||
12
wpiutil/src/main/python/semiwrap/Sendable.yml
Normal file
12
wpiutil/src/main/python/semiwrap/Sendable.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
extra_includes:
|
||||
- wpi/sendable/SendableBuilder.h
|
||||
|
||||
classes:
|
||||
wpi::Sendable:
|
||||
methods:
|
||||
InitSendable:
|
||||
virtual_xform: |-
|
||||
[&](py::function fn) {
|
||||
auto builderHandle = py::cast(builder, py::return_value_policy::reference);
|
||||
fn(builderHandle);
|
||||
}
|
||||
40
wpiutil/src/main/python/semiwrap/SendableBuilder.yml
Normal file
40
wpiutil/src/main/python/semiwrap/SendableBuilder.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
classes:
|
||||
wpi::SendableBuilder:
|
||||
enums:
|
||||
BackendKind:
|
||||
methods:
|
||||
SetSmartDashboardType:
|
||||
SetActuator:
|
||||
AddBooleanProperty:
|
||||
PublishConstBoolean:
|
||||
AddIntegerProperty:
|
||||
PublishConstInteger:
|
||||
AddFloatProperty:
|
||||
PublishConstFloat:
|
||||
AddDoubleProperty:
|
||||
PublishConstDouble:
|
||||
AddStringProperty:
|
||||
PublishConstString:
|
||||
AddBooleanArrayProperty:
|
||||
PublishConstBooleanArray:
|
||||
AddIntegerArrayProperty:
|
||||
PublishConstIntegerArray:
|
||||
AddFloatArrayProperty:
|
||||
PublishConstFloatArray:
|
||||
AddDoubleArrayProperty:
|
||||
PublishConstDoubleArray:
|
||||
AddStringArrayProperty:
|
||||
PublishConstStringArray:
|
||||
AddRawProperty:
|
||||
PublishConstRaw:
|
||||
AddSmallStringProperty:
|
||||
AddSmallBooleanArrayProperty:
|
||||
AddSmallIntegerArrayProperty:
|
||||
AddSmallFloatArrayProperty:
|
||||
AddSmallDoubleArrayProperty:
|
||||
AddSmallStringArrayProperty:
|
||||
AddSmallRawProperty:
|
||||
GetBackendKind:
|
||||
IsPublished:
|
||||
Update:
|
||||
ClearProperties:
|
||||
54
wpiutil/src/main/python/semiwrap/SendableRegistry.yml
Normal file
54
wpiutil/src/main/python/semiwrap/SendableRegistry.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
extra_includes:
|
||||
- wpi/sendable/Sendable.h
|
||||
- wpi/sendable/SendableBuilder.h
|
||||
|
||||
classes:
|
||||
wpi::SendableRegistry:
|
||||
nodelete: true
|
||||
methods:
|
||||
Add:
|
||||
overloads:
|
||||
Sendable*, std::string_view:
|
||||
keepalive:
|
||||
- [1, 2]
|
||||
Sendable*, std::string_view, int:
|
||||
keepalive:
|
||||
- [1, 2]
|
||||
Sendable*, std::string_view, int, int:
|
||||
keepalive:
|
||||
- [1, 2]
|
||||
Sendable*, std::string_view, std::string_view:
|
||||
keepalive:
|
||||
- [1, 2]
|
||||
AddChild:
|
||||
overloads:
|
||||
Sendable*, Sendable*:
|
||||
keepalive:
|
||||
- [1, 2]
|
||||
- [2, 3]
|
||||
Sendable*, void*:
|
||||
ignore: true
|
||||
Remove:
|
||||
Move:
|
||||
ignore: true
|
||||
Contains:
|
||||
GetName:
|
||||
SetName:
|
||||
overloads:
|
||||
Sendable*, std::string_view:
|
||||
Sendable*, std::string_view, int:
|
||||
Sendable*, std::string_view, int, int:
|
||||
Sendable*, std::string_view, std::string_view:
|
||||
GetSubsystem:
|
||||
SetSubsystem:
|
||||
GetDataHandle:
|
||||
ignore: true
|
||||
SetData:
|
||||
ignore: true
|
||||
GetData:
|
||||
ignore: true
|
||||
GetUniqueId:
|
||||
GetSendable:
|
||||
Publish:
|
||||
Update:
|
||||
EnsureInitialized:
|
||||
5
wpiutil/src/main/python/semiwrap/StackTrace.yml
Normal file
5
wpiutil/src/main/python/semiwrap/StackTrace.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
functions:
|
||||
GetStackTrace:
|
||||
GetStackTraceDefault:
|
||||
SetGetStackTraceImpl:
|
||||
ignore: true
|
||||
59
wpiutil/src/main/python/semiwrap/Synchronization.yml
Normal file
59
wpiutil/src/main/python/semiwrap/Synchronization.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
defaults:
|
||||
ignore: true
|
||||
|
||||
subpackage: sync
|
||||
extra_includes:
|
||||
- pybind11/stl.h
|
||||
|
||||
functions:
|
||||
CreateEvent:
|
||||
DestroyEvent:
|
||||
SetEvent:
|
||||
ResetEvent:
|
||||
CreateSemaphore:
|
||||
DestroySemaphore:
|
||||
ReleaseSemaphore:
|
||||
param_override:
|
||||
prevCount:
|
||||
default: "0"
|
||||
WaitForObject:
|
||||
overloads:
|
||||
WPI_Handle:
|
||||
WPI_Handle, double, bool*:
|
||||
WaitForObjects:
|
||||
overloads:
|
||||
std::span<const WPI_Handle>, std::span<WPI_Handle>:
|
||||
param_override:
|
||||
signaled:
|
||||
ignore: true
|
||||
cpp_code: |
|
||||
[](std::span<const WPI_Handle> handles) {
|
||||
py::gil_scoped_release release;
|
||||
std::vector<WPI_Handle> signaled(handles.size());
|
||||
auto result = wpi::WaitForObjects(handles, signaled);
|
||||
signaled.resize(result.size());
|
||||
return signaled;
|
||||
}
|
||||
std::initializer_list<WPI_Handle>, std::span<WPI_Handle>:
|
||||
ignore: true
|
||||
std::span<const WPI_Handle>, std::span<WPI_Handle>, double, bool*:
|
||||
param_override:
|
||||
signaled:
|
||||
ignore: true
|
||||
timedOut:
|
||||
ignore: true
|
||||
cpp_code: |
|
||||
[](std::span<const WPI_Handle> handles, double timeout) {
|
||||
py::gil_scoped_release release;
|
||||
std::vector<WPI_Handle> signaled(handles.size());
|
||||
bool timedOut = false;
|
||||
auto result = wpi::WaitForObjects(handles, signaled, timeout, &timedOut);
|
||||
signaled.resize(result.size());
|
||||
return std::make_tuple(signaled, timedOut);
|
||||
}
|
||||
std::initializer_list<WPI_Handle>, std::span<WPI_Handle>, double, bool*:
|
||||
ignore: true
|
||||
CreateSignalObject:
|
||||
SetSignalObject:
|
||||
ResetSignalObject:
|
||||
DestroySignalObject:
|
||||
22
wpiutil/src/main/python/semiwrap/WPyStruct.yml
Normal file
22
wpiutil/src/main/python/semiwrap/WPyStruct.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
defaults:
|
||||
subpackage: wpistruct
|
||||
|
||||
functions:
|
||||
forEachNested:
|
||||
no_release_gil: true
|
||||
getTypeName:
|
||||
no_release_gil: true
|
||||
getSchema:
|
||||
no_release_gil: true
|
||||
getSize:
|
||||
no_release_gil: true
|
||||
pack:
|
||||
no_release_gil: true
|
||||
packArray:
|
||||
no_release_gil: true
|
||||
packInto:
|
||||
no_release_gil: true
|
||||
unpack:
|
||||
no_release_gil: true
|
||||
unpackArray:
|
||||
no_release_gil: true
|
||||
28
wpiutil/src/main/python/wpiutil/__init__.py
Normal file
28
wpiutil/src/main/python/wpiutil/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from . import _init__wpiutil
|
||||
|
||||
# autogenerated by 'semiwrap create-imports wpiutil wpiutil._wpiutil'
|
||||
from ._wpiutil import (
|
||||
Sendable,
|
||||
SendableBuilder,
|
||||
SendableRegistry,
|
||||
TimestampSource,
|
||||
getStackTrace,
|
||||
getStackTraceDefault,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Sendable",
|
||||
"SendableBuilder",
|
||||
"SendableRegistry",
|
||||
"TimestampSource",
|
||||
"getStackTrace",
|
||||
"getStackTraceDefault",
|
||||
]
|
||||
|
||||
# Imported for side effects only
|
||||
from . import _stacktrace
|
||||
|
||||
# Type alias
|
||||
import typing
|
||||
|
||||
json = typing.Union[None, bool, int, float, str, typing.List, typing.Dict]
|
||||
27
wpiutil/src/main/python/wpiutil/_stacktrace.py
Normal file
27
wpiutil/src/main/python/wpiutil/_stacktrace.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from traceback import extract_stack, format_list
|
||||
from ._wpiutil import getStackTraceDefault, _setup_stack_trace_hook
|
||||
from os.path import join
|
||||
|
||||
_start_py = join("wpilib", "_impl", "start.py")
|
||||
|
||||
|
||||
def _stack_trace_hook(offset: int) -> str:
|
||||
# note: this implementation ignores offset because it's not
|
||||
# actually meaningful when crossing the python/C++ boundary
|
||||
|
||||
stack = extract_stack()[:-1]
|
||||
if not stack:
|
||||
return "\tat <no python frames>\n" + getStackTraceDefault(offset)
|
||||
|
||||
# filter out any frames before start.py (except for one of them) to
|
||||
# make stack frames more useful for users
|
||||
for i in range(len(stack) - 1, 0, -1):
|
||||
if stack[i].filename.endswith(_start_py):
|
||||
stack = stack[i:]
|
||||
break
|
||||
|
||||
trace = format_list(stack)
|
||||
return "\n".join(trace)
|
||||
|
||||
|
||||
_setup_stack_trace_hook(_stack_trace_hook)
|
||||
0
wpiutil/src/main/python/wpiutil/py.typed
Normal file
0
wpiutil/src/main/python/wpiutil/py.typed
Normal file
41
wpiutil/src/main/python/wpiutil/src/main.cpp
Normal file
41
wpiutil/src/main/python/wpiutil/src/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
#include <semiwrap_init.wpiutil._wpiutil.hpp>
|
||||
|
||||
void setup_stack_trace_hook(py::object fn);
|
||||
void cleanup_stack_trace_hook();
|
||||
|
||||
void setup_safethread_gil();
|
||||
void cleanup_safethread_gil();
|
||||
|
||||
#ifndef __FRC_SYSTEMCORE__
|
||||
|
||||
namespace wpi::impl {
|
||||
void ResetSendableRegistry();
|
||||
} // namespace wpi::impl
|
||||
|
||||
void cleanup_sendable_registry() {
|
||||
py::gil_scoped_release unlock;
|
||||
wpi::impl::ResetSendableRegistry();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void cleanup_sendable_registry() {}
|
||||
|
||||
#endif
|
||||
|
||||
SEMIWRAP_PYBIND11_MODULE(m) {
|
||||
initWrapper(m);
|
||||
|
||||
static int unused;
|
||||
py::capsule cleanup(&unused, [](void *) {
|
||||
cleanup_sendable_registry();
|
||||
cleanup_stack_trace_hook();
|
||||
cleanup_safethread_gil();
|
||||
});
|
||||
|
||||
setup_safethread_gil();
|
||||
|
||||
m.def("_setup_stack_trace_hook", &setup_stack_trace_hook);
|
||||
m.add_object("_st_cleanup", cleanup);
|
||||
}
|
||||
65
wpiutil/src/main/python/wpiutil/src/safethread_gil.cpp
Normal file
65
wpiutil/src/main/python/wpiutil/src/safethread_gil.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <gilsafe_object.h>
|
||||
#include <semiwrap.h>
|
||||
|
||||
using OnThreadStartFn = void *(*)();
|
||||
using OnThreadEndFn = void (*)(void *);
|
||||
|
||||
namespace wpi::impl {
|
||||
void SetSafeThreadNotifiers(OnThreadStartFn OnStart, OnThreadEndFn OnEnd);
|
||||
}
|
||||
|
||||
struct SafeThreadState {
|
||||
py::gil_scoped_acquire *acquire = nullptr;
|
||||
py::gil_scoped_release *release = nullptr;
|
||||
};
|
||||
|
||||
std::atomic<bool> g_gilstate_managed = false;
|
||||
|
||||
void *on_safe_thread_start() {
|
||||
if (Py_IsFinalizing() // python is shutting down
|
||||
|| !g_gilstate_managed.load() // python has shutdown)
|
||||
) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *st = new SafeThreadState;
|
||||
|
||||
// acquires the GIL and creates pybind11's thread state for this thread
|
||||
st->acquire = new py::gil_scoped_acquire;
|
||||
// releases the GIL so the thread can start without it
|
||||
st->release = new py::gil_scoped_release;
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
void on_safe_thread_end(void *opaque) {
|
||||
// on entry, GIL should not be acquired
|
||||
|
||||
// don't cleanup if it's unsafe to do so. Several possibilities here:
|
||||
if (!opaque // internal error?
|
||||
|| Py_IsFinalizing() // python is shutting down
|
||||
|| !g_gilstate_managed.load() // python has shutdown
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *st = (SafeThreadState *)opaque;
|
||||
delete st->release; // causes GIL to be acquired
|
||||
delete st->acquire; // causes GIL to be released and thread state deleted
|
||||
delete st;
|
||||
}
|
||||
|
||||
void setup_safethread_gil() {
|
||||
g_gilstate_managed = true;
|
||||
|
||||
// atexit handlers get called before the interpreter finalizes -- so
|
||||
// we disable on_safe_thread_end before finalizing starts
|
||||
auto atexit = py::module_::import("atexit");
|
||||
atexit.attr("register")(
|
||||
py::cpp_function([]() { g_gilstate_managed = false; }));
|
||||
|
||||
wpi::impl::SetSafeThreadNotifiers(on_safe_thread_start, on_safe_thread_end);
|
||||
}
|
||||
|
||||
void cleanup_safethread_gil() { g_gilstate_managed = false; }
|
||||
45
wpiutil/src/main/python/wpiutil/src/stacktracehook.cpp
Normal file
45
wpiutil/src/main/python/wpiutil/src/stacktracehook.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
#include <semiwrap.h>
|
||||
#include <wpi/StackTrace.h>
|
||||
|
||||
py::object &get_hook_ref() {
|
||||
static py::object hook;
|
||||
return hook;
|
||||
}
|
||||
|
||||
std::string final_py_stack_trace_hook(int offset) {
|
||||
std::string msg = "\tat <python stack trace not available due to interpreter shutdown>\n";
|
||||
msg += wpi::GetStackTraceDefault(offset);
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::string py_stack_trace_hook(int offset) {
|
||||
py::gil_scoped_acquire gil;
|
||||
|
||||
try {
|
||||
auto &hook = get_hook_ref();
|
||||
if (hook) {
|
||||
return py::cast<std::string>(hook(offset));
|
||||
}
|
||||
} catch (py::error_already_set &e) {
|
||||
e.discard_as_unraisable("wpiutil._stacktrace._stack_trace_hook");
|
||||
}
|
||||
|
||||
return wpi::GetStackTraceDefault(offset);
|
||||
}
|
||||
|
||||
void setup_stack_trace_hook(py::object fn) {
|
||||
get_hook_ref() = fn;
|
||||
wpi::SetGetStackTraceImpl(py_stack_trace_hook);
|
||||
}
|
||||
|
||||
void cleanup_stack_trace_hook() {
|
||||
wpi::SetGetStackTraceImpl(final_py_stack_trace_hook);
|
||||
|
||||
// release the function during interpreter shutdown
|
||||
auto &hook = get_hook_ref();
|
||||
if (hook) {
|
||||
hook.dec_ref();
|
||||
hook.release();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <wpi/array.h>
|
||||
|
||||
namespace pybind11 {
|
||||
namespace detail {
|
||||
|
||||
template <size_t N>
|
||||
struct wpi_array_name_maker {
|
||||
template <typename T>
|
||||
static constexpr auto make(const T &t) {
|
||||
return concat(t, wpi_array_name_maker<N-1>::make(t));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi_array_name_maker<1> {
|
||||
template <typename T>
|
||||
static constexpr auto make(const T &t) {
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Type, size_t Size>
|
||||
struct type_caster<wpi::array<Type, Size>> {
|
||||
using value_conv = make_caster<Type>;
|
||||
|
||||
// Have to copy/paste PYBIND11_TYPE_CASTER implementation because wpi::array
|
||||
// is not default constructable
|
||||
//
|
||||
// begin PYBIND11_TYPE_CASTER
|
||||
protected:
|
||||
wpi::array<Type, Size> value{wpi::empty_array_t{}};
|
||||
|
||||
// An empty tuple is pretty useless
|
||||
static_assert(Size > 0, "empty array not supported");
|
||||
|
||||
public:
|
||||
static constexpr auto name = const_name("Tuple[") + wpi_array_name_maker<Size>::make(value_conv::name) + const_name("]");
|
||||
template <
|
||||
typename T_,
|
||||
enable_if_t<std::is_same<wpi::array<Type, Size>, remove_cv_t<T_>>::value,
|
||||
int> = 0>
|
||||
static handle cast(T_ *src, return_value_policy policy, handle parent) {
|
||||
if (!src)
|
||||
return none().release();
|
||||
if (policy == return_value_policy::take_ownership) {
|
||||
auto h = cast(std::move(*src), policy, parent);
|
||||
delete src;
|
||||
return h;
|
||||
} else {
|
||||
return cast(*src, policy, parent);
|
||||
}
|
||||
}
|
||||
operator wpi::array<Type, Size> *() { return &value; }
|
||||
operator wpi::array<Type, Size> &() { return value; }
|
||||
operator wpi::array<Type, Size> &&() && { return std::move(value); }
|
||||
template <typename T_>
|
||||
using cast_op_type = pybind11::detail::movable_cast_op_type<T_>;
|
||||
// end PYBIND11_TYPE_CASTER
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<sequence>(src))
|
||||
return false;
|
||||
auto l = reinterpret_borrow<sequence>(src);
|
||||
if (l.size() != Size)
|
||||
return false;
|
||||
size_t ctr = 0;
|
||||
for (auto it : l) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert))
|
||||
return false;
|
||||
value[ctr++] = cast_op<Type &&>(std::move(conv));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
tuple l(src.size());
|
||||
size_t index = 0;
|
||||
for (auto &&value : src) {
|
||||
auto value_ = reinterpret_steal<object>(
|
||||
value_conv::cast(forward_like<T>(value), policy, parent));
|
||||
if (!value_)
|
||||
return handle();
|
||||
PyTuple_SET_ITEM(l.ptr(), (ssize_t)index++,
|
||||
value_.release().ptr()); // steals a reference
|
||||
}
|
||||
return l.release();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <wpi/ct_string.h>
|
||||
|
||||
namespace pybind11 {
|
||||
namespace detail {
|
||||
|
||||
template <typename CharT, typename Traits, size_t N>
|
||||
struct type_caster<wpi::ct_string<CharT, Traits, N>> {
|
||||
using str_type = wpi::ct_string<CharT, Traits, N>;
|
||||
PYBIND11_TYPE_CASTER(str_type, const_name(PYBIND11_STRING_NAME));
|
||||
|
||||
// TODO
|
||||
bool load(handle src, bool convert) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static handle cast(const str_type& src,
|
||||
py::return_value_policy policy,
|
||||
py::handle parent) {
|
||||
const char *buffer = reinterpret_cast<const char *>(src.data());
|
||||
auto nbytes = ssize_t(src.size() * sizeof(CharT));
|
||||
handle s = decode_utfN(buffer, nbytes);
|
||||
if (!s) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// copied from py::string_caster
|
||||
static constexpr size_t UTF_N = 8 * sizeof(CharT);
|
||||
|
||||
static handle decode_utfN(const char *buffer, ssize_t nbytes) {
|
||||
#if !defined(PYPY_VERSION)
|
||||
return UTF_N == 8 ? PyUnicode_DecodeUTF8(buffer, nbytes, nullptr)
|
||||
: UTF_N == 16 ? PyUnicode_DecodeUTF16(buffer, nbytes, nullptr, nullptr)
|
||||
: PyUnicode_DecodeUTF32(buffer, nbytes, nullptr, nullptr);
|
||||
#else
|
||||
// PyPy segfaults when on PyUnicode_DecodeUTF16 (and possibly on PyUnicode_DecodeUTF32 as
|
||||
// well), so bypass the whole thing by just passing the encoding as a string value, which
|
||||
// works properly:
|
||||
return PyUnicode_Decode(buffer,
|
||||
nbytes,
|
||||
UTF_N == 8 ? "utf-8"
|
||||
: UTF_N == 16 ? "utf-16"
|
||||
: "utf-32",
|
||||
nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// template <typename Char, typename Traits, size_t N>
|
||||
// struct type_caster<wpi::ct_string<Char, Traits, N>>
|
||||
// : string_caster<wpi::ct_string<Char, Traits, N>, false> {};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
@@ -0,0 +1,259 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2019, Martin Renou *
|
||||
* *
|
||||
Copyright (c) 2019,
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/json.h"
|
||||
|
||||
#include "pybind11/pybind11.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace pyjson
|
||||
{
|
||||
using number_unsigned_t = uint64_t;
|
||||
using number_integer_t = int64_t;
|
||||
|
||||
inline py::object from_json(const wpi::json& j)
|
||||
{
|
||||
if (j.is_null())
|
||||
{
|
||||
return py::none();
|
||||
}
|
||||
else if (j.is_boolean())
|
||||
{
|
||||
return py::bool_(j.get<bool>());
|
||||
}
|
||||
else if (j.is_number_unsigned())
|
||||
{
|
||||
return py::int_(j.get<number_unsigned_t>());
|
||||
}
|
||||
else if (j.is_number_integer())
|
||||
{
|
||||
return py::int_(j.get<number_integer_t>());
|
||||
}
|
||||
else if (j.is_number_float())
|
||||
{
|
||||
return py::float_(j.get<double>());
|
||||
}
|
||||
else if (j.is_string())
|
||||
{
|
||||
return py::str(j.get<std::string>());
|
||||
}
|
||||
else if (j.is_array())
|
||||
{
|
||||
py::list obj(j.size());
|
||||
for (std::size_t i = 0; i < j.size(); i++)
|
||||
{
|
||||
obj[i] = from_json(j[i]);
|
||||
}
|
||||
return std::move(obj);
|
||||
}
|
||||
else // Object
|
||||
{
|
||||
py::dict obj;
|
||||
for (wpi::json::const_iterator it = j.cbegin(); it != j.cend(); ++it)
|
||||
{
|
||||
obj[py::str(it.key())] = from_json(it.value());
|
||||
}
|
||||
return std::move(obj);
|
||||
}
|
||||
}
|
||||
|
||||
inline wpi::json to_json(const py::handle& obj)
|
||||
{
|
||||
if (obj.ptr() == nullptr || obj.is_none())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (py::isinstance<py::bool_>(obj))
|
||||
{
|
||||
return obj.cast<bool>();
|
||||
}
|
||||
if (py::isinstance<py::int_>(obj))
|
||||
{
|
||||
try
|
||||
{
|
||||
number_integer_t s = obj.cast<number_integer_t>();
|
||||
if (py::int_(s).equal(obj))
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
number_unsigned_t u = obj.cast<number_unsigned_t>();
|
||||
if (py::int_(u).equal(obj))
|
||||
{
|
||||
return u;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
throw py::value_error("to_json received an integer out of range for both number_integer_t and number_unsigned_t type: " + py::repr(obj).cast<std::string>());
|
||||
}
|
||||
if (py::isinstance<py::float_>(obj))
|
||||
{
|
||||
return obj.cast<double>();
|
||||
}
|
||||
// if (py::isinstance<py::bytes>(obj))
|
||||
// {
|
||||
// py::module base64 = py::module::import("base64");
|
||||
// return base64.attr("b64encode")(obj).attr("decode")("utf-8").cast<std::string>();
|
||||
// }
|
||||
if (py::isinstance<py::str>(obj))
|
||||
{
|
||||
return obj.cast<std::string>();
|
||||
}
|
||||
if (py::isinstance<py::tuple>(obj) || py::isinstance<py::list>(obj))
|
||||
{
|
||||
auto out = wpi::json::array();
|
||||
for (const py::handle value : obj)
|
||||
{
|
||||
out.push_back(to_json(value));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (py::isinstance<py::dict>(obj))
|
||||
{
|
||||
auto out = wpi::json::object();
|
||||
for (const py::handle key : obj)
|
||||
{
|
||||
if (py::isinstance<py::str>(key)) {
|
||||
out[key.cast<std::string>()] = to_json(obj[key]);
|
||||
|
||||
} else if (py::isinstance<py::int_>(key) || py::isinstance<py::float_>(key) ||
|
||||
py::isinstance<py::bool_>(key) || py::isinstance<py::none>(key)) {
|
||||
// only allow the same implicit conversions python allows
|
||||
out[py::str(key).cast<std::string>()] = to_json(obj[key]);
|
||||
} else {
|
||||
throw py::type_error("JSON keys must be str, int, float, bool, or None, not " + py::repr(key).cast<std::string>());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
throw py::type_error("Object of type " + py::type::of(obj).attr("__name__").cast<std::string>() + " is not JSON serializable");
|
||||
}
|
||||
}
|
||||
|
||||
// nlohmann_json serializers
|
||||
namespace wpi
|
||||
{
|
||||
#define MAKE_NLJSON_SERIALIZER_DESERIALIZER(T) \
|
||||
template <> \
|
||||
struct adl_serializer<T> \
|
||||
{ \
|
||||
inline static void to_json(json& j, const T& obj) \
|
||||
{ \
|
||||
j = pyjson::to_json(obj); \
|
||||
} \
|
||||
\
|
||||
inline static T from_json(const json& j) \
|
||||
{ \
|
||||
return pyjson::from_json(j); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define MAKE_NLJSON_SERIALIZER_ONLY(T) \
|
||||
template <> \
|
||||
struct adl_serializer<T> \
|
||||
{ \
|
||||
inline static void to_json(json& j, const T& obj) \
|
||||
{ \
|
||||
j = pyjson::to_json(obj); \
|
||||
} \
|
||||
}
|
||||
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::object);
|
||||
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::bool_);
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::int_);
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::float_);
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::str);
|
||||
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::list);
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::tuple);
|
||||
MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::dict);
|
||||
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::handle);
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::detail::item_accessor);
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::detail::list_accessor);
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::detail::tuple_accessor);
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::detail::sequence_accessor);
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::detail::str_attr_accessor);
|
||||
MAKE_NLJSON_SERIALIZER_ONLY(py::detail::obj_attr_accessor);
|
||||
|
||||
#undef MAKE_NLJSON_SERIALIZER
|
||||
#undef MAKE_NLJSON_SERIALIZER_ONLY
|
||||
}
|
||||
|
||||
// pybind11 caster
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <> struct type_caster<wpi::json>
|
||||
{
|
||||
public:
|
||||
PYBIND11_TYPE_CASTER(wpi::json, _("wpiutil.json"));
|
||||
|
||||
bool load(handle src, bool convert)
|
||||
{
|
||||
// TODO: raising errors gives the user informative error messages,
|
||||
// but at the expense of proper argument parsing..
|
||||
// try
|
||||
// {
|
||||
value = pyjson::to_json(src);
|
||||
return true;
|
||||
// }
|
||||
// catch (...)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
static handle cast(wpi::json src, return_value_policy /* policy */, handle /* parent */)
|
||||
{
|
||||
object obj = pyjson::from_json(src);
|
||||
return obj.release();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <wpi/SmallSet.h>
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <typename Type, unsigned Size> struct type_caster<wpi::SmallSet<Type, Size>>
|
||||
: set_caster<wpi::SmallSet<Type, Size>, Type> { };
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <typename Type, unsigned Size> struct type_caster<wpi::SmallVector<Type, Size>>
|
||||
: list_caster<wpi::SmallVector<Type, Size>, Type> { };
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
|
||||
template <typename Type> struct type_caster<wpi::SmallVectorImpl<Type>> {
|
||||
using value_conv = make_caster<Type>;
|
||||
|
||||
// Have to copy/paste PYBIND11_TYPE_CASTER implementation because SmallVectorImpl
|
||||
// is not default constructable
|
||||
//
|
||||
// begin PYBIND11_TYPE_CASTER
|
||||
protected:
|
||||
wpi::SmallVector<Type, 16> value;
|
||||
public:
|
||||
static constexpr auto name = _("List[") + value_conv::name + _("]");
|
||||
template <typename T_, enable_if_t<std::is_same<wpi::SmallVectorImpl<Type>, remove_cv_t<T_>>::value, int> = 0>
|
||||
static handle cast(T_ *src, return_value_policy policy, handle parent) {
|
||||
if (!src) return none().release();
|
||||
if (policy == return_value_policy::take_ownership) {
|
||||
auto h = cast(std::move(*src), policy, parent); delete src; return h;
|
||||
} else {
|
||||
return cast(*src, policy, parent);
|
||||
}
|
||||
}
|
||||
operator wpi::SmallVectorImpl<Type>*() { return &value; }
|
||||
operator wpi::SmallVectorImpl<Type>&() { return value; }
|
||||
operator wpi::SmallVectorImpl<Type>&&() && { return std::move(value); }
|
||||
template <typename T_> using cast_op_type = pybind11::detail::movable_cast_op_type<T_>;
|
||||
// end PYBIND11_TYPE_CASTER
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<sequence>(src) || isinstance<str>(src))
|
||||
return false;
|
||||
auto s = reinterpret_borrow<sequence>(src);
|
||||
value.reserve(s.size());
|
||||
for (auto it : s) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert))
|
||||
return false;
|
||||
value.push_back(cast_op<Type &&>(std::move(conv)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
if (!std::is_lvalue_reference<T>::value)
|
||||
policy = return_value_policy_override<Type>::policy(policy);
|
||||
list l(src.size());
|
||||
size_t index = 0;
|
||||
for (auto &&value : src) {
|
||||
auto value_ = reinterpret_steal<object>(value_conv::cast(forward_like<T>(value), policy, parent));
|
||||
if (!value_)
|
||||
return handle();
|
||||
PyList_SET_ITEM(l.ptr(), (ssize_t) index++, value_.release().ptr()); // steals a reference
|
||||
}
|
||||
return l.release();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
@@ -0,0 +1,172 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <span>
|
||||
|
||||
namespace pybind11 {
|
||||
namespace detail {
|
||||
|
||||
template <size_t N>
|
||||
struct span_name_maker {
|
||||
template <typename T>
|
||||
static constexpr auto make(const T &t) {
|
||||
return concat(t, span_name_maker<N-1>::make(t));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct span_name_maker<1> {
|
||||
template <typename T>
|
||||
static constexpr auto make(const T &t) {
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
// span with fixed size converts to a tuple
|
||||
template <typename Type, size_t Extent> struct type_caster<std::span<Type, Extent>> {
|
||||
using span_type = typename std::span<Type, Extent>;
|
||||
using value_conv = make_caster<Type>;
|
||||
using value_type = typename std::remove_cv<Type>::type;
|
||||
|
||||
value_type backing_array[Extent] = {};
|
||||
|
||||
PYBIND11_TYPE_CASTER(span_type, _("Tuple[") + span_name_maker<Extent>::make(value_conv::name) + _("]"));
|
||||
|
||||
type_caster() : value(backing_array) {}
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<sequence>(src) || isinstance<str>(src))
|
||||
return false;
|
||||
auto s = reinterpret_borrow<sequence>(src);
|
||||
if (s.size() != Extent)
|
||||
return false;
|
||||
size_t i = 0;
|
||||
for (auto it : s) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert))
|
||||
return false;
|
||||
backing_array[i] = cast_op<Type &&>(std::move(conv));
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
if (!std::is_lvalue_reference<T>::value)
|
||||
policy = return_value_policy_override<Type>::policy(policy);
|
||||
tuple l(Extent);
|
||||
size_t index = 0;
|
||||
for (auto &&value : src) {
|
||||
auto value_ = reinterpret_steal<object>(
|
||||
value_conv::cast(forward_like<T>(value), policy, parent));
|
||||
if (!value_)
|
||||
return handle();
|
||||
PyTuple_SET_ITEM(l.ptr(), (ssize_t)index++,
|
||||
value_.release().ptr()); // steals a reference
|
||||
}
|
||||
return l.release();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// span with dynamic extent
|
||||
template <typename Type> struct type_caster<std::span<Type, std::dynamic_extent>> {
|
||||
using span_type = typename std::span<Type, std::dynamic_extent>;
|
||||
using value_conv = make_caster<Type>;
|
||||
using value_type = typename std::remove_cv<Type>::type;
|
||||
PYBIND11_TYPE_CASTER(span_type, _("List[") + value_conv::name + _("]"));
|
||||
|
||||
wpi::SmallVector<value_type, 32> vec;
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<sequence>(src) || isinstance<str>(src))
|
||||
return false;
|
||||
auto s = reinterpret_borrow<sequence>(src);
|
||||
vec.reserve(s.size());
|
||||
for (auto it : s) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert))
|
||||
return false;
|
||||
vec.push_back(cast_op<Type &&>(std::move(conv)));
|
||||
}
|
||||
value = span_type(std::data(vec), std::size(vec));
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
if (!std::is_lvalue_reference<T>::value)
|
||||
policy = return_value_policy_override<Type>::policy(policy);
|
||||
list l(src.size());
|
||||
size_t index = 0;
|
||||
for (auto &&value : src) {
|
||||
auto value_ = reinterpret_steal<object>(
|
||||
value_conv::cast(forward_like<T>(value), policy, parent));
|
||||
if (!value_)
|
||||
return handle();
|
||||
PyList_SET_ITEM(l.ptr(), (ssize_t)index++,
|
||||
value_.release().ptr()); // steals a reference
|
||||
}
|
||||
return l.release();
|
||||
}
|
||||
};
|
||||
|
||||
// span specialization: accepts any readonly buffers
|
||||
template <> struct type_caster<std::span<const uint8_t, std::dynamic_extent>> {
|
||||
using span_type = typename std::span<const uint8_t, std::dynamic_extent>;
|
||||
PYBIND11_TYPE_CASTER(span_type, _("Buffer"));
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<buffer>(src))
|
||||
return false;
|
||||
auto buf = reinterpret_borrow<buffer>(src);
|
||||
auto req = buf.request();
|
||||
if (req.ndim != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = span_type((const uint8_t*)req.ptr, req.size*req.itemsize);
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
return bytes((char*)src.data(), src.size()).release();
|
||||
}
|
||||
};
|
||||
|
||||
// span specialization: writeable buffer
|
||||
template <> struct type_caster<std::span<uint8_t, std::dynamic_extent>> {
|
||||
using span_type = typename std::span<const uint8_t, std::dynamic_extent>;
|
||||
PYBIND11_TYPE_CASTER(std::span<uint8_t>, _("Buffer"));
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<buffer>(src))
|
||||
return false;
|
||||
auto buf = reinterpret_borrow<buffer>(src);
|
||||
auto req = buf.request(true); // buffer must be writeable
|
||||
if (req.ndim != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = std::span<uint8_t>((uint8_t*)req.ptr, req.size*req.itemsize);
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
// TODO: should this be a memoryview instead?
|
||||
return bytes((char*)src.data(), src.size()).release();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <typename Value>
|
||||
struct type_caster<wpi::StringMap<Value>>
|
||||
: map_caster<wpi::StringMap<Value>, std::string, Value> { };
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
317
wpiutil/src/main/python/wpiutil/src/wpistruct/wpystruct.h
Normal file
317
wpiutil/src/main/python/wpiutil/src/wpistruct/wpystruct.h
Normal file
@@ -0,0 +1,317 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/struct/Struct.h>
|
||||
|
||||
#include <pybind11/functional.h>
|
||||
#include <pybind11/typing.h>
|
||||
#include <semiwrap.h>
|
||||
|
||||
static inline std::string pytypename(const py::type &t) {
|
||||
return ((PyTypeObject *)t.ptr())->tp_name;
|
||||
}
|
||||
|
||||
//
|
||||
// Dynamic struct + type caster
|
||||
//
|
||||
|
||||
// This merely holds the python object being operated on, the actual
|
||||
// serialization work is done in WPyStructConverter
|
||||
struct WPyStruct {
|
||||
|
||||
WPyStruct() = default;
|
||||
|
||||
WPyStruct(const WPyStruct &other) {
|
||||
py::gil_scoped_acquire gil;
|
||||
py = other.py;
|
||||
}
|
||||
|
||||
WPyStruct &operator=(const WPyStruct &other) {
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
py = other.py;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
WPyStruct(WPyStruct &&) = default;
|
||||
|
||||
WPyStruct(const py::object &py) : py(py) {}
|
||||
|
||||
~WPyStruct() {
|
||||
py::gil_scoped_acquire gil;
|
||||
py.release().dec_ref();
|
||||
}
|
||||
|
||||
py::object py;
|
||||
};
|
||||
|
||||
namespace pybind11 {
|
||||
namespace detail {
|
||||
|
||||
template <> struct type_caster<WPyStruct> {
|
||||
// TODO: wpiutil.struct.T/TV?
|
||||
PYBIND11_TYPE_CASTER(WPyStruct, const_name("object"));
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
// TODO: validation?
|
||||
value.py = py::reinterpret_borrow<py::object>(src);
|
||||
return true;
|
||||
}
|
||||
|
||||
static handle cast(const WPyStruct &src, py::return_value_policy policy,
|
||||
py::handle parent) {
|
||||
py::handle v = src.py;
|
||||
v.inc_ref();
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
|
||||
//
|
||||
// Struct info class implementation
|
||||
//
|
||||
|
||||
// two types of converters: static C++ converter, and dynamic python converter
|
||||
struct WPyStructConverter {
|
||||
virtual ~WPyStructConverter() = default;
|
||||
virtual std::string_view GetTypeName() const = 0;
|
||||
|
||||
virtual size_t GetSize() const = 0;
|
||||
|
||||
virtual std::string_view GetSchema() const = 0;
|
||||
|
||||
virtual void Pack(std::span<uint8_t> data, const WPyStruct &value) const = 0;
|
||||
|
||||
virtual WPyStruct Unpack(std::span<const uint8_t> data) const = 0;
|
||||
|
||||
// virtual void UnpackInto(WPyStruct *pyv,
|
||||
// std::span<const uint8_t> data) const = 0;
|
||||
|
||||
virtual void ForEachNested(
|
||||
const std::function<void(std::string_view, std::string_view)> &fn)
|
||||
const = 0;
|
||||
};
|
||||
|
||||
// static C++ converter
|
||||
template <typename T> struct WPyStructCppConverter : WPyStructConverter {
|
||||
std::string_view GetTypeName() const override {
|
||||
return wpi::Struct<T>::GetTypeName();
|
||||
}
|
||||
|
||||
size_t GetSize() const override { return wpi::Struct<T>::GetSize(); }
|
||||
|
||||
std::string_view GetSchema() const override {
|
||||
return wpi::Struct<T>::GetSchema();
|
||||
}
|
||||
|
||||
void Pack(std::span<uint8_t> data, const WPyStruct &value) const override {
|
||||
py::gil_scoped_acquire gil;
|
||||
const T &v = value.py.cast<const T &>();
|
||||
wpi::Struct<T>::Pack(data, v);
|
||||
}
|
||||
|
||||
WPyStruct Unpack(std::span<const uint8_t> data) const override {
|
||||
py::gil_scoped_acquire gil;
|
||||
return WPyStruct{py::cast(wpi::UnpackStruct<T>(data))};
|
||||
}
|
||||
|
||||
// void UnpackInto(WPyStruct *pyv,
|
||||
// std::span<const uint8_t> data) const override {
|
||||
// py::gil_scoped_acquire gil;
|
||||
// T *v = pyv->py.cast<T *>();
|
||||
// wpi::UnpackStructInto(v, data);
|
||||
// }
|
||||
|
||||
void ForEachNested(
|
||||
const std::function<void(std::string_view, std::string_view)> &fn)
|
||||
const override {
|
||||
if constexpr (wpi::HasNestedStruct<T>) {
|
||||
wpi::Struct<T>::ForEachNested(fn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> void SetupWPyStruct(auto pycls) {
|
||||
|
||||
auto *sptr =
|
||||
new std::shared_ptr<WPyStructConverter>(new WPyStructCppConverter<T>());
|
||||
|
||||
py::capsule c(sptr, "WPyStruct", [](void *ptr) {
|
||||
delete (std::shared_ptr<WPyStructConverter> *)ptr;
|
||||
});
|
||||
|
||||
pycls.def_property_readonly_static("WPIStruct",
|
||||
[c](py::object pycls) { return c; });
|
||||
}
|
||||
|
||||
// dynamic python converter
|
||||
struct WPyStructPyConverter : WPyStructConverter {
|
||||
|
||||
WPyStructPyConverter(py::object o) {
|
||||
m_typename = o.attr("typename").cast<std::string>();
|
||||
m_schema = o.attr("schema").cast<std::string>();
|
||||
m_size = o.attr("size").cast<size_t>();
|
||||
|
||||
m_pack = py::reinterpret_borrow<py::function>(o.attr("pack"));
|
||||
m_packInto = py::reinterpret_borrow<py::function>(o.attr("packInto"));
|
||||
m_unpack = py::reinterpret_borrow<py::function>(o.attr("unpack"));
|
||||
// m_unpackInto = py::reinterpret_borrow<py::function>(o.attr("unpackInto"));
|
||||
m_forEachNested =
|
||||
py::reinterpret_borrow<py::function>(o.attr("forEachNested"));
|
||||
}
|
||||
|
||||
// copy all the relevant attributes locally
|
||||
std::string m_typename;
|
||||
std::string m_schema;
|
||||
size_t m_size;
|
||||
|
||||
py::function m_pack;
|
||||
py::function m_packInto;
|
||||
py::function m_unpack;
|
||||
// py::function m_unpackInto;
|
||||
py::function m_forEachNested; // might be none
|
||||
|
||||
std::string_view GetTypeName() const override { return m_typename; }
|
||||
|
||||
size_t GetSize() const override { return m_size; }
|
||||
|
||||
std::string_view GetSchema() const override { return m_schema; }
|
||||
|
||||
void Pack(std::span<uint8_t> data, const WPyStruct &value) const override {
|
||||
py::gil_scoped_acquire gil;
|
||||
py::bytes result = m_pack(value.py);
|
||||
std::string_view rview = result;
|
||||
if (rview.size() != data.size()) {
|
||||
std::string msg = fmt::format(
|
||||
"{} pack did not return {} bytes (returned {})",
|
||||
pytypename(py::type::of(value.py)), data.size(), rview.size());
|
||||
throw py::value_error(msg);
|
||||
}
|
||||
|
||||
rview.copy((char *)data.data(), rview.size());
|
||||
}
|
||||
|
||||
WPyStruct Unpack(std::span<const uint8_t> data) const override {
|
||||
py::gil_scoped_acquire gil;
|
||||
auto view =
|
||||
py::memoryview::from_memory((const void *)data.data(), data.size());
|
||||
return WPyStruct(m_unpack(view));
|
||||
}
|
||||
|
||||
// void UnpackInto(WPyStruct *pyv,
|
||||
// std::span<const uint8_t> data) const override {
|
||||
// py::gil_scoped_acquire gil;
|
||||
// auto view =
|
||||
// py::memoryview::from_memory((const void *)data.data(), data.size());
|
||||
// m_unpackInto(pyv->py, view);
|
||||
// }
|
||||
|
||||
void ForEachNested(
|
||||
const std::function<void(std::string_view, std::string_view)> &fn)
|
||||
const override {
|
||||
py::gil_scoped_acquire gil;
|
||||
if (!m_forEachNested.is_none()) {
|
||||
m_forEachNested(fn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// passed as I... to the wpi::Struct methods
|
||||
struct WPyStructInfo {
|
||||
WPyStructInfo() = default;
|
||||
WPyStructInfo(const py::type &t) {
|
||||
if (!py::hasattr(t, "WPIStruct")) {
|
||||
|
||||
throw py::type_error(
|
||||
fmt::format("{} is not struct serializable (does not have WPIStruct)",
|
||||
pytypename(t)));
|
||||
}
|
||||
|
||||
py::object s = t.attr("WPIStruct");
|
||||
|
||||
// C++ version
|
||||
void *c = PyCapsule_GetPointer(s.ptr(), "WPyStruct");
|
||||
if (c != NULL) {
|
||||
cvt = *(std::shared_ptr<WPyStructConverter> *)c;
|
||||
return;
|
||||
}
|
||||
|
||||
PyErr_Clear();
|
||||
|
||||
// Python version
|
||||
try {
|
||||
cvt = std::make_shared<WPyStructPyConverter>(s);
|
||||
} catch (py::error_already_set &e) {
|
||||
std::string msg = fmt::format(
|
||||
"{} is not struct serializable (invalid WPIStruct)", pytypename(t));
|
||||
py::raise_from(e, PyExc_TypeError, msg.c_str());
|
||||
throw py::error_already_set();
|
||||
}
|
||||
}
|
||||
|
||||
WPyStructInfo(const WPyStruct &v) : WPyStructInfo(py::type::of(v.py)) {}
|
||||
|
||||
const WPyStructConverter* operator->() const {
|
||||
const auto *c = cvt.get();
|
||||
if (c == nullptr) {
|
||||
// TODO: would be nice to have a better error here, but we don't have
|
||||
// a good way to know our current context
|
||||
throw py::value_error("Object is closed");
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private:
|
||||
// holds something used to do serialization
|
||||
std::shared_ptr<WPyStructConverter> cvt;
|
||||
};
|
||||
|
||||
// Leverages the converter stored in WPyStructInfo to do the actual work
|
||||
template <> struct wpi::Struct<WPyStruct, WPyStructInfo> {
|
||||
static std::string_view GetTypeName(const WPyStructInfo &info) {
|
||||
return info->GetTypeName();
|
||||
}
|
||||
|
||||
static size_t GetSize(const WPyStructInfo &info) {
|
||||
return info->GetSize();
|
||||
}
|
||||
|
||||
static std::string_view GetSchema(const WPyStructInfo &info) {
|
||||
return info->GetSchema();
|
||||
}
|
||||
|
||||
static WPyStruct Unpack(std::span<const uint8_t> data,
|
||||
const WPyStructInfo &info) {
|
||||
return info->Unpack(data);
|
||||
}
|
||||
|
||||
// static void UnpackInto(WPyStruct *v, std::span<const uint8_t> data,
|
||||
// const WPyStructInfo &info) {
|
||||
// info->UnpackInto(v, data);
|
||||
// }
|
||||
|
||||
static void Pack(std::span<uint8_t> data, const WPyStruct &value,
|
||||
const WPyStructInfo &info) {
|
||||
info->Pack(data, value);
|
||||
}
|
||||
|
||||
static void
|
||||
ForEachNested(std::invocable<std::string_view, std::string_view> auto fn,
|
||||
const WPyStructInfo &info) {
|
||||
info->ForEachNested(fn);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(wpi::StructSerializable<WPyStruct, WPyStructInfo>);
|
||||
static_assert(wpi::HasNestedStruct<WPyStruct, WPyStructInfo>);
|
||||
|
||||
// This breaks on readonly structs, so we disable for now
|
||||
// static_assert(wpi::MutableStructSerializable<WPyStruct, WPyStructInfo>);
|
||||
166
wpiutil/src/main/python/wpiutil/src/wpistruct/wpystruct_fns.cpp
Normal file
166
wpiutil/src/main/python/wpiutil/src/wpistruct/wpystruct_fns.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
|
||||
#include "wpystruct.h"
|
||||
|
||||
void forEachNested(
|
||||
const py::type &t,
|
||||
const std::function<void(std::string_view, std::string_view)> &fn) {
|
||||
WPyStructInfo info(t);
|
||||
wpi::ForEachStructSchema<WPyStruct, WPyStructInfo>(fn, info);
|
||||
}
|
||||
|
||||
py::str getTypeName(const py::type &t) {
|
||||
WPyStructInfo info(t);
|
||||
return wpi::GetStructTypeName<WPyStruct, WPyStructInfo>(info);
|
||||
}
|
||||
|
||||
py::str getSchema(const py::type &t) {
|
||||
WPyStructInfo info(t);
|
||||
return wpi::GetStructSchema<WPyStruct, WPyStructInfo>(info);
|
||||
}
|
||||
|
||||
size_t getSize(const py::type &t) {
|
||||
WPyStructInfo info(t);
|
||||
return wpi::GetStructSize<WPyStruct>(info);
|
||||
}
|
||||
|
||||
py::bytes pack(const WPyStruct &v) {
|
||||
WPyStructInfo info(v);
|
||||
|
||||
auto sz = wpi::GetStructSize<WPyStruct>(info);
|
||||
PyObject *b = PyBytes_FromStringAndSize(NULL, sz);
|
||||
if (b == NULL) {
|
||||
throw py::error_already_set();
|
||||
}
|
||||
|
||||
char *pybuf;
|
||||
py::ssize_t pysz;
|
||||
if (PyBytes_AsStringAndSize(b, &pybuf, &pysz) != 0) {
|
||||
Py_DECREF(b);
|
||||
throw py::error_already_set();
|
||||
}
|
||||
|
||||
auto s = std::span((uint8_t *)pybuf, pysz);
|
||||
wpi::PackStruct(s, v, info);
|
||||
|
||||
return py::reinterpret_steal<py::bytes>(b);
|
||||
}
|
||||
|
||||
py::bytes packArray(const py::sequence &seq) {
|
||||
auto len = seq.size();
|
||||
if (len == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
WPyStructInfo info(py::type::of(seq[0]));
|
||||
auto sz = wpi::GetStructSize<WPyStruct>(info);
|
||||
auto total = sz*len;
|
||||
|
||||
PyObject *b = PyBytes_FromStringAndSize(NULL, total);
|
||||
if (b == NULL) {
|
||||
throw py::error_already_set();
|
||||
}
|
||||
|
||||
char *pybuf;
|
||||
py::ssize_t pysz;
|
||||
if (PyBytes_AsStringAndSize(b, &pybuf, &pysz) != 0) {
|
||||
Py_DECREF(b);
|
||||
throw py::error_already_set();
|
||||
}
|
||||
|
||||
auto bytes_obj = py::reinterpret_steal<py::bytes>(b);
|
||||
|
||||
for (const auto &v: seq) {
|
||||
WPyStruct wv(v);
|
||||
auto s = std::span((uint8_t *)pybuf, sz);
|
||||
wpi::PackStruct(s, wv, info);
|
||||
pybuf += sz;
|
||||
}
|
||||
|
||||
return bytes_obj;
|
||||
}
|
||||
|
||||
void packInto(const WPyStruct &v, py::buffer &b) {
|
||||
WPyStructInfo info(v);
|
||||
py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);
|
||||
|
||||
auto req = b.request();
|
||||
if (req.itemsize != 1) {
|
||||
throw py::value_error("buffer must only contain bytes");
|
||||
} else if (req.ndim != 1) {
|
||||
throw py::value_error("buffer must only have a single dimension");
|
||||
}
|
||||
|
||||
if (req.size != sz) {
|
||||
throw py::value_error("buffer must be " + std::to_string(sz) + " bytes");
|
||||
}
|
||||
|
||||
auto s = std::span((uint8_t *)req.ptr, req.size);
|
||||
wpi::PackStruct(s, v, info);
|
||||
}
|
||||
|
||||
WPyStruct unpack(const py::type &t, const py::buffer &b) {
|
||||
WPyStructInfo info(t);
|
||||
py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);
|
||||
|
||||
auto req = b.request();
|
||||
if (req.itemsize != 1) {
|
||||
throw py::value_error("buffer must only contain bytes");
|
||||
} else if (req.ndim != 1) {
|
||||
throw py::value_error("buffer must only have a single dimension");
|
||||
}
|
||||
|
||||
if (req.size != sz) {
|
||||
throw py::value_error("buffer must be " + std::to_string(sz) + " bytes");
|
||||
}
|
||||
|
||||
auto s = std::span((const uint8_t *)req.ptr, req.size);
|
||||
return wpi::UnpackStruct<WPyStruct, WPyStructInfo>(s, info);
|
||||
}
|
||||
|
||||
py::typing::List<WPyStruct> unpackArray(const py::type &t, const py::buffer &b) {
|
||||
WPyStructInfo info(t);
|
||||
py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);
|
||||
|
||||
auto req = b.request();
|
||||
if (req.itemsize != 1) {
|
||||
throw py::value_error("buffer must only contain bytes");
|
||||
} else if (req.ndim != 1) {
|
||||
throw py::value_error("buffer must only have a single dimension");
|
||||
}
|
||||
|
||||
if (req.size % sz != 0) {
|
||||
throw py::value_error("buffer must be multiple of " + std::to_string(sz) + " bytes");
|
||||
}
|
||||
|
||||
auto items = req.size / sz;
|
||||
py::list a(items);
|
||||
const uint8_t *ptr = (const uint8_t *)req.ptr;
|
||||
for (py::ssize_t i = 0; i < items; i++) {
|
||||
auto s = std::span(ptr, sz);
|
||||
auto v = wpi::UnpackStruct<WPyStruct, WPyStructInfo>(s, info);
|
||||
// steals a reference
|
||||
PyList_SET_ITEM(a.ptr(), i, v.py.inc_ref().ptr());
|
||||
ptr += sz;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
// void unpackInto(const py::buffer &b, WPyStruct *v) {
|
||||
// WPyStructInfo info(*v);
|
||||
// py::ssize_t sz = wpi::GetStructSize<WPyStruct>(info);
|
||||
|
||||
// auto req = b.request();
|
||||
// if (req.itemsize != 1) {
|
||||
// throw py::value_error("buffer must only contain bytes");
|
||||
// } else if (req.ndim != 1) {
|
||||
// throw py::value_error("buffer must only have a single dimension");
|
||||
// }
|
||||
|
||||
// if (req.size != sz) {
|
||||
// throw py::value_error("buffer must be " + std::to_string(sz) + " bytes");
|
||||
// }
|
||||
|
||||
// auto s = std::span((const uint8_t *)req.ptr, req.size);
|
||||
// wpi::UnpackStructInto<WPyStruct, WPyStructInfo>(v, s, info);
|
||||
// }
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wpystruct.h"
|
||||
|
||||
/**
|
||||
Call a function to retrieve the (type string, schema) for each nested struct
|
||||
*/
|
||||
void forEachNested(
|
||||
const py::type &t,
|
||||
const std::function<void(std::string_view, std::string_view)> &fn);
|
||||
|
||||
/**
|
||||
Retrieve the type name for the specified type
|
||||
*/
|
||||
py::str getTypeName(const py::type &t);
|
||||
|
||||
/**
|
||||
Retrieve schema for the specified type
|
||||
*/
|
||||
py::str getSchema(const py::type &t);
|
||||
|
||||
/**
|
||||
Returns the serialized size in bytes
|
||||
*/
|
||||
size_t getSize(const py::type &t);
|
||||
|
||||
/**
|
||||
Serialize object into byte buffer
|
||||
*/
|
||||
py::bytes pack(const WPyStruct &v);
|
||||
|
||||
/**
|
||||
Serialize objects into byte buffer
|
||||
*/
|
||||
py::bytes packArray(const py::sequence &seq);
|
||||
|
||||
/**
|
||||
Serialize object into byte buffer. Buffer must be exact size.
|
||||
*/
|
||||
void packInto(const WPyStruct &v, py::buffer &b);
|
||||
|
||||
/**
|
||||
Convert byte buffer into object of specified type. Buffer must be exact
|
||||
size.
|
||||
*/
|
||||
WPyStruct unpack(const py::type &t, const py::buffer &b);
|
||||
|
||||
/**
|
||||
Convert byte buffer into list of objects of specified type. Buffer must be
|
||||
exact size.
|
||||
*/
|
||||
py::typing::List<WPyStruct> unpackArray(const py::type &t, const py::buffer &b);
|
||||
|
||||
// /**
|
||||
// Convert byte buffer into passed in object. Buffer must be exact
|
||||
// size.
|
||||
// */
|
||||
// void unpackInto(const py::buffer &b, WPyStruct *v);
|
||||
32
wpiutil/src/main/python/wpiutil/sync/__init__.py
Normal file
32
wpiutil/src/main/python/wpiutil/sync/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# autogenerated by 'semiwrap create-imports wpiutil.sync wpiutil._wpiutil.sync'
|
||||
from .._wpiutil.sync import (
|
||||
createEvent,
|
||||
createSemaphore,
|
||||
createSignalObject,
|
||||
destroyEvent,
|
||||
destroySemaphore,
|
||||
destroySignalObject,
|
||||
releaseSemaphore,
|
||||
resetEvent,
|
||||
resetSignalObject,
|
||||
setEvent,
|
||||
setSignalObject,
|
||||
waitForObject,
|
||||
waitForObjects,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"createEvent",
|
||||
"createSemaphore",
|
||||
"createSignalObject",
|
||||
"destroyEvent",
|
||||
"destroySemaphore",
|
||||
"destroySignalObject",
|
||||
"releaseSemaphore",
|
||||
"resetEvent",
|
||||
"resetSignalObject",
|
||||
"setEvent",
|
||||
"setSignalObject",
|
||||
"waitForObject",
|
||||
"waitForObjects",
|
||||
]
|
||||
58
wpiutil/src/main/python/wpiutil/wpistruct/__init__.py
Normal file
58
wpiutil/src/main/python/wpiutil/wpistruct/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
This package contains the WPILib Struct serialization functions, and a
|
||||
mechanism to implement your own custom structs using Python (see :func:`wpiutil.wpistruct.make_wpistruct`).
|
||||
"""
|
||||
|
||||
# autogenerated by 'semiwrap create-imports wpiutil.wpistruct wpiutil._wpiutil.wpistruct'
|
||||
from .._wpiutil.wpistruct import (
|
||||
forEachNested,
|
||||
getSchema,
|
||||
getSize,
|
||||
getTypeName,
|
||||
pack,
|
||||
packArray,
|
||||
packInto,
|
||||
unpack,
|
||||
unpackArray,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"forEachNested",
|
||||
"getSchema",
|
||||
"getSize",
|
||||
"getTypeName",
|
||||
"pack",
|
||||
"packArray",
|
||||
"packInto",
|
||||
"unpack",
|
||||
"unpackArray",
|
||||
]
|
||||
|
||||
from .desc import StructDescriptor
|
||||
|
||||
from .dataclass import (
|
||||
make_wpistruct,
|
||||
int8,
|
||||
uint8,
|
||||
int16,
|
||||
uint16,
|
||||
int32,
|
||||
uint32,
|
||||
int64,
|
||||
uint64,
|
||||
double,
|
||||
)
|
||||
|
||||
__all__ += [
|
||||
"StructDescriptor",
|
||||
"make_wpistruct",
|
||||
"int8",
|
||||
"uint8",
|
||||
"int16",
|
||||
"uint16",
|
||||
"int32",
|
||||
"uint32",
|
||||
"int64",
|
||||
"uint64",
|
||||
"double",
|
||||
]
|
||||
229
wpiutil/src/main/python/wpiutil/wpistruct/dataclass.py
Normal file
229
wpiutil/src/main/python/wpiutil/wpistruct/dataclass.py
Normal file
@@ -0,0 +1,229 @@
|
||||
import dataclasses
|
||||
import inspect
|
||||
import struct
|
||||
import typing
|
||||
|
||||
|
||||
from .desc import StructDescriptor
|
||||
from .._wpiutil import wpistruct
|
||||
|
||||
#
|
||||
# Use these types to specify explicitly sized integers, but you can
|
||||
# also use int/bool/float
|
||||
#
|
||||
|
||||
# fmt: off
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
int8 = int
|
||||
uint8 = int
|
||||
int16 = int
|
||||
uint16 = int
|
||||
int32 = int
|
||||
uint32 = int
|
||||
int64 = int
|
||||
uint64 = int
|
||||
double = float
|
||||
else:
|
||||
class int8(int): pass
|
||||
class uint8(int): pass
|
||||
class int16(int): pass
|
||||
class uint16(int): pass
|
||||
class int32(int): pass
|
||||
class uint32(int): pass
|
||||
class int64(int): pass
|
||||
class uint64(int): pass
|
||||
|
||||
class double(float): pass
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def make_wpistruct(cls=None, /, *, name: typing.Optional[str] = None):
|
||||
"""
|
||||
This decorator allows you to easily define a custom type that can be
|
||||
used with wpilib's custom serialization protocol (for use in datalog
|
||||
and networktables). Just create a normal python dataclass, and apply
|
||||
this decorator to the class.
|
||||
|
||||
For example, here's how you define a dataclass that contains an integer,
|
||||
a boolean, and a double::
|
||||
|
||||
@wpiutil.wpistruct.make_wpistruct(name="mystruct")
|
||||
@dataclasses.dataclass
|
||||
class MyStruct:
|
||||
x: wpiutil.wpistruct.int32
|
||||
y: bool
|
||||
z: wpiutil.struct.double
|
||||
|
||||
The types defined in the dataclass can be another WPIStruct compatible class
|
||||
(either builtin or user defined); one of int, bool, or float; or you can
|
||||
use one of the ``wpiutil.wpistruct.[u]int*`` values for explicitly sized
|
||||
integer types.
|
||||
"""
|
||||
|
||||
def wrap(cls):
|
||||
return _process_class(cls, name)
|
||||
|
||||
if cls is None:
|
||||
return wrap
|
||||
|
||||
return wrap(cls)
|
||||
|
||||
|
||||
#
|
||||
# Internals
|
||||
#
|
||||
|
||||
_type_to_fmt = {
|
||||
bool: ("?", "bool"),
|
||||
int8: ("b", "int8"),
|
||||
uint8: ("B", "uint8"),
|
||||
int16: ("h", "int16"),
|
||||
uint16: ("H", "uint16"),
|
||||
int: ("i", "int32"),
|
||||
int32: ("i", "int32"),
|
||||
uint32: ("I", "uint32"),
|
||||
int64: ("q", "int64"),
|
||||
uint64: ("Q", "uint64"),
|
||||
float: ("f", "float"),
|
||||
double: ("d", "double"),
|
||||
}
|
||||
|
||||
|
||||
def _process_class(cls, struct_name: typing.Optional[str]):
|
||||
resolved_hints = typing.get_type_hints(cls)
|
||||
field_names = [field.name for field in dataclasses.fields(cls)]
|
||||
resolved_field_types = {name: resolved_hints[name] for name in field_names}
|
||||
|
||||
name_parts = []
|
||||
name_parts.append(getattr(cls, "__module__", None))
|
||||
name_parts.append(getattr(cls, "__qualname__", cls.__name__))
|
||||
cls_name = ".".join([n for n in name_parts if n])
|
||||
|
||||
if struct_name is None:
|
||||
struct_name = cls.__name__
|
||||
err_name = cls_name
|
||||
else:
|
||||
err_name = f"{struct_name} ({cls_name})"
|
||||
|
||||
fmts = []
|
||||
schema = []
|
||||
cvvals = []
|
||||
vvals = []
|
||||
packs = []
|
||||
unpacks = []
|
||||
# unpackIntos = []
|
||||
forEachNested = []
|
||||
|
||||
ctx: typing.Dict[str, typing.Any] = {"cls": cls}
|
||||
|
||||
for name, ftype in resolved_field_types.items():
|
||||
if ftype in _type_to_fmt:
|
||||
fmt, stype = _type_to_fmt[ftype]
|
||||
|
||||
fmts.append(fmt)
|
||||
schema.append(f"{stype} {name}")
|
||||
cvvals.append(f"arg_{name}")
|
||||
vvals.append(f"v.{name}")
|
||||
|
||||
elif hasattr(ftype, "WPIStruct"):
|
||||
# nested struct
|
||||
argn = f"arg_{name}"
|
||||
typn = f"type_{name}"
|
||||
|
||||
ctx[typn] = ftype
|
||||
ts = wpistruct.getTypeName(ftype)
|
||||
schema.append(f"{ts} {name}")
|
||||
sz = wpistruct.getSize(ftype)
|
||||
fmts.append(f"{sz}s")
|
||||
vvals.append(argn)
|
||||
cvvals.append(argn)
|
||||
packs.append(f"{argn} = wpistruct.pack(v.{name})")
|
||||
unpacks.append(f"{argn} = wpistruct.unpack({typn}, {argn})")
|
||||
# unpackIntos.append(f"wpistruct.unpackInto(v.{name}, {argn})")
|
||||
forEachNested.append(f"wpistruct.forEachNested({typn}, fn)")
|
||||
|
||||
else:
|
||||
supported_names = ", ".join(t.__name__ for t in _type_to_fmt.keys())
|
||||
raise TypeError(
|
||||
f"{cls_name}.{name} is not a wpistruct or does not have a supported type hint "
|
||||
f"(supported: {supported_names})"
|
||||
) from None
|
||||
|
||||
s = struct.Struct(f"<{''.join(fmts)}")
|
||||
cvals = ", ".join(cvvals)
|
||||
vals = ", ".join(vvals)
|
||||
|
||||
padding = "\n" + " " * 16
|
||||
pack_stmts = padding.join(packs)
|
||||
unpack_stmts = padding.join(unpacks)
|
||||
# unpackInto_stmts = padding.join(unpackIntos)
|
||||
|
||||
if not forEachNested:
|
||||
forEachNested_stmt = "_forEachNested = None"
|
||||
else:
|
||||
forEachNested_stmt = f"def _forEachNested(fn):"
|
||||
forEachNested_stmt += "\n" + " " * 12
|
||||
forEachNested_stmt += f"try:{padding}"
|
||||
forEachNested_stmt += padding.join(forEachNested)
|
||||
forEachNested_stmt += "\n" + " " * 12
|
||||
forEachNested_stmt += f"except Exception as e:"
|
||||
forEachNested_stmt += (
|
||||
f"{padding}raise ValueError(f'{err_name}: error in forEachNested') from e"
|
||||
)
|
||||
|
||||
ctx["_s"] = s
|
||||
|
||||
# Construct the serialization functions using the same hack NamedTuple uses
|
||||
fnsrc = inspect.cleandoc(
|
||||
f"""
|
||||
from wpiutil import wpistruct
|
||||
|
||||
def _pack(v):
|
||||
try:
|
||||
{pack_stmts}
|
||||
return _s.pack({vals})
|
||||
except Exception as e:
|
||||
raise ValueError(f"{err_name}: error packing data") from e
|
||||
|
||||
def _packInto(v, b):
|
||||
try:
|
||||
{pack_stmts}
|
||||
return _s.pack_into(b, 0, {vals})
|
||||
except Exception as e:
|
||||
raise ValueError(f"{err_name}: error packing data") from e
|
||||
|
||||
def _unpack(b):
|
||||
try:
|
||||
{cvals} = _s.unpack(b)
|
||||
{unpack_stmts}
|
||||
return cls({cvals})
|
||||
except Exception as e:
|
||||
raise ValueError(f"{err_name}: error unpacking data") from e
|
||||
|
||||
#def _unpackInto(v, b):
|
||||
# try:
|
||||
# {vals} = _s.unpack(b)
|
||||
# {{unpackInto_stmts}}
|
||||
# except Exception as e:
|
||||
# raise ValueError(f"{err_name}: error unpacking data") from e
|
||||
|
||||
{forEachNested_stmt}
|
||||
"""
|
||||
)
|
||||
|
||||
exec(fnsrc, ctx, ctx)
|
||||
|
||||
cls.WPIStruct = StructDescriptor(
|
||||
typename=struct_name,
|
||||
schema="; ".join(schema),
|
||||
size=s.size,
|
||||
pack=ctx["_pack"],
|
||||
packInto=ctx["_packInto"],
|
||||
unpack=ctx["_unpack"],
|
||||
# unpackInto=ctx["_unpackInto"],
|
||||
forEachNested=ctx["_forEachNested"],
|
||||
)
|
||||
|
||||
return cls
|
||||
48
wpiutil/src/main/python/wpiutil/wpistruct/desc.py
Normal file
48
wpiutil/src/main/python/wpiutil/wpistruct/desc.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing_extensions import Buffer
|
||||
else:
|
||||
# Avoiding typing_extensions runtime dependency
|
||||
Buffer = bytearray
|
||||
|
||||
|
||||
class StructDescriptor(typing.NamedTuple):
|
||||
"""
|
||||
To define a type in python that can use the wpilib serialization, the type must
|
||||
have an attribute `WPIStruct` that contains this class (but C++ classes
|
||||
do not have this).
|
||||
|
||||
It is not intended that you should create this class directly, something
|
||||
else should generate it for you.
|
||||
|
||||
See :func:`wpiutil.wpistruct.make_wpistruct` for a easy to use mechanism
|
||||
for defining custom structs using a dataclass.
|
||||
"""
|
||||
|
||||
#: The type name
|
||||
typename: str
|
||||
|
||||
#: The struct schema
|
||||
schema: str
|
||||
|
||||
#: Size in bytes of binary representation of this struct
|
||||
size: int
|
||||
|
||||
#: A function that converts the type to bytes
|
||||
pack: typing.Callable[[typing.Any], bytes]
|
||||
|
||||
#: A function that converts the type to bytes
|
||||
packInto: typing.Callable[[typing.Any, Buffer], None]
|
||||
|
||||
#: A function that converts bytes to an instance
|
||||
unpack: typing.Callable[[Buffer], typing.Any]
|
||||
|
||||
#: A function that updates the given instance using the deserialized bytes
|
||||
#: .. not supported
|
||||
# unpackInto: typing.Callable[[typing.Any, Buffer], None]
|
||||
|
||||
#: If this contains nested structs, calls wpiutil.wpistruct.forEachNested for each
|
||||
forEachNested: typing.Optional[
|
||||
typing.Callable[[typing.Callable[[str, str], None]], None]
|
||||
]
|
||||
23
wpiutil/src/main/python/wpiutil/wpistruct/typing.py
Normal file
23
wpiutil/src/main/python/wpiutil/wpistruct/typing.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import ClassVar, Protocol
|
||||
|
||||
try:
|
||||
from typing import TypeGuard
|
||||
except ImportError:
|
||||
try:
|
||||
from typing_extensions import TypeGuard
|
||||
except ImportError:
|
||||
# Runtime fallback for Python 3.9 without typing_extensions
|
||||
class TypeGuard:
|
||||
def __class_getitem__(cls, key):
|
||||
return bool
|
||||
|
||||
|
||||
class StructSerializable(Protocol):
|
||||
"""Any type that can be serialized or deserialized as a WPILib Struct."""
|
||||
|
||||
WPIStruct: ClassVar
|
||||
|
||||
|
||||
def is_wpistruct_type(cls: type) -> TypeGuard[type[StructSerializable]]:
|
||||
"""Returns True if the given type supports WPILib Struct serialization."""
|
||||
return hasattr(cls, "WPIStruct")
|
||||
1
wpiutil/src/test/python/conftest.py
Normal file
1
wpiutil/src/test/python/conftest.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
11
wpiutil/src/test/python/cpp/pyproject.toml
Normal file
11
wpiutil/src/test/python/cpp/pyproject.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = [
|
||||
"hatch-meson", "hatchling"
|
||||
]
|
||||
|
||||
[project]
|
||||
name = "wpiutil_test"
|
||||
version = "0.0.1"
|
||||
|
||||
[tool.hatch.build.hooks.meson]
|
||||
1
wpiutil/src/test/python/cpp/wpiutil_test/__init__.py
Normal file
1
wpiutil/src/test/python/cpp/wpiutil_test/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
import wpiutil
|
||||
195
wpiutil/src/test/python/cpp/wpiutil_test/module.cpp
Normal file
195
wpiutil/src/test/python/cpp/wpiutil_test/module.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
// clang-format off
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <wpi_array_type_caster.h>
|
||||
#include <wpi_span_type_caster.h>
|
||||
#include <wpi_smallset_type_caster.h>
|
||||
#include <wpi_smallvector_type_caster.h>
|
||||
#include <wpi_smallvectorimpl_type_caster.h>
|
||||
#include <wpi_string_map_caster.h>
|
||||
#include <wpi_json_type_caster.h>
|
||||
#include <wpi_ct_string_type_caster.h>
|
||||
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
|
||||
#include <pybind11/functional.h>
|
||||
|
||||
/*
|
||||
array tests
|
||||
*/
|
||||
wpi::array<int, 4> load_array_int(wpi::array<int, 4> data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
wpi::array<int, 1> load_array_int1(wpi::array<int, 1> data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
span Tests
|
||||
*/
|
||||
std::span<const int> load_span_int(std::span<const int> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
std::span<const bool> load_span_bool(std::span<const bool> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
std::span<std::string> load_span_string(std::span<std::string> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
std::span<const std::string> load_span_string_const(std::span<const std::string> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
std::span<std::string_view> load_span_string_view(std::span<std::string_view> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
std::span<std::vector<std::string>> load_span_vector(std::span<std::vector<std::string>> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
std::span<const double, 3> load_span_fixed_double(std::span<const double, 3> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
std::span<int> cast_span() {
|
||||
static std::vector<int> vec{1, 2, 3};
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::span<const std::string> make_string_span() {
|
||||
static std::vector<std::string> vec{"hi", "there"};
|
||||
return vec;
|
||||
}
|
||||
|
||||
py::object cast_string_span() {
|
||||
return py::cast(make_string_span());
|
||||
}
|
||||
|
||||
std::span<const uint8_t> load_span_bytes(std::span<const uint8_t> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
void modify_span_buffer(std::span<uint8_t> ref) {
|
||||
ref[0] = 0x4;
|
||||
}
|
||||
|
||||
/*
|
||||
SmallSet tests
|
||||
*/
|
||||
|
||||
wpi::SmallSet<int, 4> load_smallset_int(wpi::SmallSet<int, 4> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
wpi::SmallSet<int, 4> cast_smallset() {
|
||||
static wpi::SmallSet<int, 4> set;
|
||||
set.insert(1);
|
||||
set.insert(2);
|
||||
set.insert(3);
|
||||
set.insert(4);
|
||||
return set;
|
||||
}
|
||||
|
||||
/*
|
||||
SmallVector tests
|
||||
*/
|
||||
|
||||
wpi::SmallVector<int, 4> load_smallvec_int(wpi::SmallVector<int, 4> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
wpi::SmallVector<int, 4> cast_smallvec() {
|
||||
static wpi::SmallVector<int, 4> set;
|
||||
set.append({1, 2, 3, 4});
|
||||
return set;
|
||||
}
|
||||
|
||||
/*
|
||||
SmallVectorImpl tests
|
||||
|
||||
.. seems like references are the only useful things to do with them
|
||||
*/
|
||||
|
||||
wpi::SmallVectorImpl<int>& load_smallvecimpl_int(wpi::SmallVectorImpl<int>& ref) {
|
||||
static wpi::SmallVector<int, 4> set(ref.begin(), ref.end());
|
||||
return set;
|
||||
}
|
||||
|
||||
/*
|
||||
StringMap tests
|
||||
*/
|
||||
wpi::StringMap<int> load_stringmap_int(wpi::StringMap<int> ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
wpi::StringMap<int> cast_stringmap() {
|
||||
static wpi::StringMap<int> m;
|
||||
m["one"] = 1;
|
||||
m["two"] = 2;
|
||||
return m;
|
||||
}
|
||||
|
||||
/* JSON tests */
|
||||
wpi::json cast_json_arg(const wpi::json &j) {
|
||||
return j;
|
||||
}
|
||||
|
||||
wpi::json cast_json_val(std::function<wpi::json()> fn) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
constexpr auto const_string() {
|
||||
return wpi::ct_string<char, std::char_traits<char>, 3>{{'#', '1', '2'}};
|
||||
}
|
||||
|
||||
void sendable_test(py::module &m);
|
||||
void struct_test(py::module &m);
|
||||
|
||||
|
||||
PYBIND11_MODULE(module, m) {
|
||||
|
||||
sendable_test(m);
|
||||
struct_test(m);
|
||||
|
||||
// array
|
||||
m.def("load_array_int", &load_array_int);
|
||||
m.def("load_array_int1", &load_array_int1);
|
||||
// span
|
||||
m.def("load_span_int", &load_span_int);
|
||||
m.def("load_span_bool", &load_span_bool);
|
||||
m.def("load_span_fixed_double", &load_span_fixed_double);
|
||||
m.def("load_span_string", &load_span_string);
|
||||
m.def("load_span_string_const", &load_span_string_const);
|
||||
m.def("load_span_string_view", &load_span_string_view);
|
||||
m.def("load_span_vector", &load_span_vector);
|
||||
m.def("cast_span", &cast_span);
|
||||
m.def("cast_string_span", &cast_string_span);
|
||||
m.def("load_span_bytes", &load_span_bytes);
|
||||
m.def("modify_span_buffer", &modify_span_buffer);
|
||||
// SmallSet
|
||||
m.def("load_smallset_int", &load_smallset_int);
|
||||
m.def("cast_smallset", &cast_smallset);
|
||||
// SmallVector
|
||||
m.def("load_smallvec_int", &load_smallvec_int);
|
||||
m.def("cast_smallvec", &cast_smallvec);
|
||||
// SmallVectorImpl
|
||||
m.def("load_smallvecimpl_int", &load_smallvecimpl_int);
|
||||
// StringMap
|
||||
m.def("load_stringmap_int", &load_stringmap_int);
|
||||
m.def("cast_stringmap", &cast_stringmap);
|
||||
// JSON
|
||||
m.def("cast_json_arg", &cast_json_arg);
|
||||
m.def("cast_json_val", &cast_json_val);
|
||||
m.attr("max_uint64") = std::numeric_limits<uint64_t>::max();
|
||||
m.attr("max_int64") = std::numeric_limits<int64_t>::max();
|
||||
m.attr("min_int64") = std::numeric_limits<int64_t>::min();
|
||||
// ct_string
|
||||
m.def("const_string", &const_string);
|
||||
};
|
||||
154
wpiutil/src/test/python/cpp/wpiutil_test/sendable_test.cpp
Normal file
154
wpiutil/src/test/python/cpp/wpiutil_test/sendable_test.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
|
||||
#include <pybind11/functional.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <semiwrap.h>
|
||||
#include <wpi/sendable/SendableBuilder.h>
|
||||
#include <wpi/sendable/SendableRegistry.h>
|
||||
|
||||
class MySendableBuilder : public wpi::SendableBuilder {
|
||||
public:
|
||||
MySendableBuilder(py::dict keys) : keys(keys) {}
|
||||
|
||||
~MySendableBuilder() {
|
||||
// leak this so the python interpreter doesn't crash on shutdown
|
||||
keys.release();
|
||||
}
|
||||
|
||||
void SetSmartDashboardType(std::string_view type) override {}
|
||||
|
||||
void SetActuator(bool value) override {}
|
||||
|
||||
void AddBooleanProperty(std::string_view key, std::function<bool()> getter,
|
||||
std::function<void(bool)> setter) override {}
|
||||
|
||||
void PublishConstBoolean(std::string_view key, bool value) override {}
|
||||
|
||||
void AddIntegerProperty(std::string_view key, std::function<int64_t()> getter,
|
||||
std::function<void(int64_t)> setter) override {}
|
||||
|
||||
void PublishConstInteger(std::string_view key, int64_t value) override {}
|
||||
|
||||
void AddFloatProperty(std::string_view key, std::function<float()> getter,
|
||||
std::function<void(float)> setter) override {}
|
||||
|
||||
void PublishConstFloat(std::string_view key, float value) override {}
|
||||
|
||||
void AddDoubleProperty(std::string_view key, std::function<double()> getter,
|
||||
std::function<void(double)> setter) override {
|
||||
py::gil_scoped_acquire gil;
|
||||
py::object pykey = py::cast(key);
|
||||
keys[pykey] = std::make_tuple(getter, setter);
|
||||
}
|
||||
|
||||
void PublishConstDouble(std::string_view key, double value) override {}
|
||||
|
||||
void
|
||||
AddStringProperty(std::string_view key, std::function<std::string()> getter,
|
||||
std::function<void(std::string_view)> setter) override {}
|
||||
|
||||
void PublishConstString(std::string_view key,
|
||||
std::string_view value) override {}
|
||||
|
||||
void AddBooleanArrayProperty(
|
||||
std::string_view key, std::function<std::vector<int>()> getter,
|
||||
std::function<void(std::span<const int>)> setter) override {}
|
||||
|
||||
void PublishConstBooleanArray(std::string_view key,
|
||||
std::span<const int> value) override {}
|
||||
|
||||
void AddIntegerArrayProperty(
|
||||
std::string_view key, std::function<std::vector<int64_t>()> getter,
|
||||
std::function<void(std::span<const int64_t>)> setter) override {}
|
||||
|
||||
void PublishConstIntegerArray(std::string_view key,
|
||||
std::span<const int64_t> value) override {}
|
||||
|
||||
void AddFloatArrayProperty(
|
||||
std::string_view key, std::function<std::vector<float>()> getter,
|
||||
std::function<void(std::span<const float>)> setter) override {}
|
||||
|
||||
void PublishConstFloatArray(std::string_view key,
|
||||
std::span<const float> value) override {}
|
||||
|
||||
void AddDoubleArrayProperty(
|
||||
std::string_view key, std::function<std::vector<double>()> getter,
|
||||
std::function<void(std::span<const double>)> setter) override {}
|
||||
|
||||
void PublishConstDoubleArray(std::string_view key,
|
||||
std::span<const double> value) override {}
|
||||
|
||||
void AddStringArrayProperty(
|
||||
std::string_view key, std::function<std::vector<std::string>()> getter,
|
||||
std::function<void(std::span<const std::string>)> setter) override {}
|
||||
|
||||
void PublishConstStringArray(std::string_view key,
|
||||
std::span<const std::string> value) override {}
|
||||
|
||||
void AddRawProperty(
|
||||
std::string_view key, std::string_view typeString,
|
||||
std::function<std::vector<uint8_t>()> getter,
|
||||
std::function<void(std::span<const uint8_t>)> setter) override {}
|
||||
|
||||
void PublishConstRaw(std::string_view key, std::string_view typeString,
|
||||
std::span<const uint8_t> value) override {}
|
||||
|
||||
void AddSmallStringProperty(
|
||||
std::string_view key,
|
||||
std::function<std::string_view(wpi::SmallVectorImpl<char> &buf)> getter,
|
||||
std::function<void(std::string_view)> setter) override {}
|
||||
|
||||
void AddSmallBooleanArrayProperty(
|
||||
std::string_view key,
|
||||
std::function<std::span<const int>(wpi::SmallVectorImpl<int> &buf)>
|
||||
getter,
|
||||
std::function<void(std::span<const int>)> setter) override {}
|
||||
|
||||
void AddSmallIntegerArrayProperty(
|
||||
std::string_view key,
|
||||
std::function<
|
||||
std::span<const int64_t>(wpi::SmallVectorImpl<int64_t> &buf)>
|
||||
getter,
|
||||
std::function<void(std::span<const int64_t>)> setter) override {}
|
||||
|
||||
void AddSmallFloatArrayProperty(
|
||||
std::string_view key,
|
||||
std::function<std::span<const float>(wpi::SmallVectorImpl<float> &buf)>
|
||||
getter,
|
||||
std::function<void(std::span<const float>)> setter) override {}
|
||||
|
||||
void AddSmallDoubleArrayProperty(
|
||||
std::string_view key,
|
||||
std::function<std::span<const double>(wpi::SmallVectorImpl<double> &buf)>
|
||||
getter,
|
||||
std::function<void(std::span<const double>)> setter) override {}
|
||||
|
||||
void AddSmallStringArrayProperty(
|
||||
std::string_view key,
|
||||
std::function<
|
||||
std::span<const std::string>(wpi::SmallVectorImpl<std::string> &buf)>
|
||||
getter,
|
||||
std::function<void(std::span<const std::string>)> setter) override {}
|
||||
|
||||
void AddSmallRawProperty(
|
||||
std::string_view key, std::string_view typeString,
|
||||
std::function<std::span<uint8_t>(wpi::SmallVectorImpl<uint8_t> &buf)>
|
||||
getter,
|
||||
std::function<void(std::span<const uint8_t>)> setter) override {}
|
||||
|
||||
wpi::SendableBuilder::BackendKind GetBackendKind() const override {
|
||||
return wpi::SendableBuilder::BackendKind::kUnknown;
|
||||
}
|
||||
|
||||
bool IsPublished() const override { return false; }
|
||||
void Update() override {}
|
||||
void ClearProperties() override {}
|
||||
|
||||
py::dict keys;
|
||||
};
|
||||
|
||||
void Publish(wpi::SendableRegistry::UID sendableUid, py::dict keys) {
|
||||
auto builder = std::make_unique<MySendableBuilder>(keys);
|
||||
wpi::SendableRegistry::Publish(sendableUid, std::move(builder));
|
||||
}
|
||||
|
||||
void sendable_test(py::module &m) { m.def("publish", Publish); }
|
||||
84
wpiutil/src/test/python/cpp/wpiutil_test/struct_test.cpp
Normal file
84
wpiutil/src/test/python/cpp/wpiutil_test/struct_test.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
#include <pybind11/operators.h>
|
||||
#include <wpystruct.h>
|
||||
|
||||
//
|
||||
// Thing to serialize
|
||||
//
|
||||
|
||||
struct ThingA {
|
||||
ThingA() = default;
|
||||
ThingA(int x) : x(x) {}
|
||||
|
||||
const int x = 0;
|
||||
|
||||
bool operator==(const ThingA &other) const { return x == other.x; }
|
||||
};
|
||||
|
||||
template <> struct wpi::Struct<ThingA> {
|
||||
static constexpr std::string_view GetTypeName() { return "ThingA"; }
|
||||
static constexpr size_t GetSize() { return 1; }
|
||||
static constexpr std::string_view GetSchema() { return "uint8 value"; }
|
||||
static ThingA Unpack(std::span<const uint8_t> data) {
|
||||
return ThingA{data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingA &value) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
struct Outer {
|
||||
Outer() = default;
|
||||
Outer(const ThingA &t, int c) : inner(t), c(c) {}
|
||||
|
||||
ThingA inner;
|
||||
int c = 0;
|
||||
|
||||
bool operator==(const Outer &other) const {
|
||||
return inner == other.inner && c == other.c;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<Outer> {
|
||||
static constexpr std::string_view GetTypeName() { return "Outer"; }
|
||||
static constexpr size_t GetSize() { return wpi::GetStructSize<ThingA>() + 4; }
|
||||
static constexpr std::string_view GetSchema() {
|
||||
return "ThingA inner; int32 c";
|
||||
}
|
||||
|
||||
static Outer Unpack(std::span<const uint8_t> data) {
|
||||
constexpr size_t innerSize = wpi::GetStructSize<ThingA>();
|
||||
return {wpi::UnpackStruct<ThingA, 0>(data),
|
||||
wpi::UnpackStruct<int32_t, innerSize>(data)};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const Outer& value) {
|
||||
constexpr size_t innerSize = wpi::GetStructSize<ThingA>();
|
||||
wpi::PackStruct<0>(data, value.inner);
|
||||
wpi::PackStruct<innerSize>(data, value.c);
|
||||
}
|
||||
static void ForEachNested(
|
||||
std::invocable<std::string_view, std::string_view> auto fn) {
|
||||
wpi::ForEachStructSchema<ThingA>(fn);
|
||||
}
|
||||
};
|
||||
|
||||
void struct_test(py::module &m) {
|
||||
|
||||
py::class_<ThingA> thingCls(m, "ThingA");
|
||||
thingCls.def(py::init<>());
|
||||
thingCls.def(py::init<int>());
|
||||
thingCls.def_readonly("x", &ThingA::x);
|
||||
thingCls.def(py::self == py::self);
|
||||
|
||||
SetupWPyStruct<ThingA>(thingCls);
|
||||
|
||||
py::class_<Outer> outerCls(m, "Outer");
|
||||
outerCls.def(py::init<>());
|
||||
outerCls.def(py::init<ThingA, int>());
|
||||
outerCls.def_readonly("inner", &Outer::inner);
|
||||
outerCls.def_readwrite("c", &Outer::c);
|
||||
outerCls.def(py::self == py::self);
|
||||
|
||||
SetupWPyStruct<Outer>(outerCls);
|
||||
}
|
||||
26
wpiutil/src/test/python/run_tests.py
Executable file
26
wpiutil/src/test/python/run_tests.py
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from os.path import abspath, dirname
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = abspath(dirname(__file__))
|
||||
os.chdir(root)
|
||||
|
||||
subprocess.check_call(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"--disable-pip-version-check",
|
||||
"install",
|
||||
"-v",
|
||||
"--force-reinstall",
|
||||
"--no-build-isolation",
|
||||
"./cpp",
|
||||
],
|
||||
)
|
||||
|
||||
subprocess.check_call([sys.executable, "-m", "pytest"])
|
||||
17
wpiutil/src/test/python/test_array.py
Normal file
17
wpiutil/src/test/python/test_array.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from wpiutil_test import module
|
||||
|
||||
|
||||
def test_load_array_int():
|
||||
assert module.load_array_int((1, 2, 3, 4)) == (1, 2, 3, 4)
|
||||
assert module.load_array_int([1, 2, 3, 4]) == (1, 2, 3, 4)
|
||||
|
||||
|
||||
def test_load_array_annotation():
|
||||
assert (
|
||||
module.load_array_int.__doc__
|
||||
== "load_array_int(arg0: Tuple[typing.SupportsInt, typing.SupportsInt, typing.SupportsInt, typing.SupportsInt]) -> Tuple[int, int, int, int]\n"
|
||||
)
|
||||
assert (
|
||||
module.load_array_int1.__doc__
|
||||
== "load_array_int1(arg0: Tuple[typing.SupportsInt]) -> Tuple[int]\n"
|
||||
)
|
||||
5
wpiutil/src/test/python/test_ct_string.py
Normal file
5
wpiutil/src/test/python/test_ct_string.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from wpiutil_test import module
|
||||
|
||||
|
||||
def test_const_string():
|
||||
assert module.const_string() == "#12"
|
||||
71
wpiutil/src/test/python/test_json.py
Normal file
71
wpiutil/src/test/python/test_json.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from wpiutil_test.module import (
|
||||
cast_json_arg,
|
||||
cast_json_val,
|
||||
max_int64,
|
||||
min_int64,
|
||||
max_uint64,
|
||||
)
|
||||
import pytest
|
||||
import math
|
||||
|
||||
|
||||
def test_json_invalid():
|
||||
with pytest.raises(TypeError):
|
||||
cast_json_val(lambda: object())
|
||||
|
||||
|
||||
def test_json_none():
|
||||
assert cast_json_arg(None) == None
|
||||
|
||||
|
||||
def test_json_bool():
|
||||
assert cast_json_arg(True) == True
|
||||
assert cast_json_arg(False) == False
|
||||
|
||||
|
||||
def test_json_int():
|
||||
assert cast_json_arg(36) == 36
|
||||
|
||||
assert cast_json_arg(min_int64) == min_int64
|
||||
with pytest.raises(ValueError):
|
||||
cast_json_arg(min_int64 - 1)
|
||||
|
||||
assert cast_json_arg(max_int64) == max_int64
|
||||
assert cast_json_arg(max_uint64) == max_uint64
|
||||
with pytest.raises(ValueError):
|
||||
cast_json_arg(max_uint64 + 1)
|
||||
|
||||
|
||||
def test_json_float():
|
||||
assert cast_json_arg(36.37) == 36.37
|
||||
assert cast_json_arg(math.inf) == math.inf
|
||||
assert math.isnan(cast_json_arg(math.nan))
|
||||
|
||||
|
||||
def test_json_string():
|
||||
assert cast_json_arg("hi") == "hi"
|
||||
|
||||
|
||||
def test_json_list():
|
||||
v = [36, "hello", False]
|
||||
assert cast_json_arg(v) == v
|
||||
|
||||
assert cast_json_arg([]) == []
|
||||
|
||||
tv = (36, "hello", False)
|
||||
assert cast_json_arg(tv) == v
|
||||
|
||||
|
||||
def test_json_dict():
|
||||
d = {"number": 1234, "hello": "world"}
|
||||
assert cast_json_arg(d) == d
|
||||
|
||||
assert cast_json_arg({}) == {}
|
||||
|
||||
assert cast_json_arg({1: 2}) == {"1": 2}
|
||||
assert cast_json_arg({None: 2}) == {"None": 2}
|
||||
assert cast_json_arg({1.2: 2}) == {"1.2": 2}
|
||||
assert cast_json_arg({False: 2}) == {"False": 2}
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
cast_json_arg({object(): 2})
|
||||
35
wpiutil/src/test/python/test_sendable.py
Normal file
35
wpiutil/src/test/python/test_sendable.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import typing
|
||||
import wpiutil
|
||||
from wpiutil_test import module
|
||||
|
||||
|
||||
class MySendable(wpiutil.Sendable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
wpiutil.SendableRegistry.add(self, "Test", 1)
|
||||
self.value = 0
|
||||
|
||||
def initSendable(self, builder: wpiutil.SendableBuilder):
|
||||
builder.addDoubleProperty("key", self._get, self._set)
|
||||
|
||||
def _set(self, value: float):
|
||||
self.value = value
|
||||
|
||||
def _get(self) -> float:
|
||||
return self.value
|
||||
|
||||
|
||||
def test_custom_sendable():
|
||||
ms = MySendable()
|
||||
|
||||
uid = wpiutil.SendableRegistry.getUniqueId(ms)
|
||||
keys = {}
|
||||
|
||||
module.publish(uid, keys)
|
||||
assert ms.value == 0
|
||||
|
||||
getter, setter = keys["key"]
|
||||
assert getter() == 0
|
||||
setter(1)
|
||||
assert getter() == 1
|
||||
assert ms.value == 1
|
||||
18
wpiutil/src/test/python/test_smallset.py
Normal file
18
wpiutil/src/test/python/test_smallset.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from wpiutil_test import module
|
||||
|
||||
|
||||
def test_smallset_load():
|
||||
assert module.load_smallset_int({1, 2, 3, 4}) == {1, 2, 3, 4}
|
||||
|
||||
|
||||
def test_smallsetbool_load():
|
||||
assert module.load_smallset_int({True, True, False, True}) == {
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
}
|
||||
|
||||
|
||||
def test_smallset_cast():
|
||||
assert module.cast_smallset() == {1, 2, 3, 4}
|
||||
22
wpiutil/src/test/python/test_smallvector.py
Normal file
22
wpiutil/src/test/python/test_smallvector.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from wpiutil_test import module
|
||||
|
||||
|
||||
def test_smallvec_load():
|
||||
assert module.load_smallvec_int([1, 2, 3, 4]) == [1, 2, 3, 4]
|
||||
|
||||
|
||||
def test_smallvecbool_load():
|
||||
assert module.load_smallvec_int([True, True, False, True]) == [
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
|
||||
def test_smallvec_cast():
|
||||
assert module.cast_smallvec() == [1, 2, 3, 4]
|
||||
|
||||
|
||||
def test_smallvecimpl_load():
|
||||
assert module.load_smallvecimpl_int([1, 2, 3, 4]) == [1, 2, 3, 4]
|
||||
75
wpiutil/src/test/python/test_span.py
Normal file
75
wpiutil/src/test/python/test_span.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import pytest
|
||||
from wpiutil_test import module
|
||||
import array
|
||||
|
||||
|
||||
def test_span_load_int():
|
||||
assert module.load_span_int([1, 2, 3, 4]) == [1, 2, 3, 4]
|
||||
|
||||
|
||||
def test_span_load_int():
|
||||
assert module.load_span_int([1, 2, 3]) == [1, 2, 3]
|
||||
|
||||
|
||||
def test_span_load_bool():
|
||||
assert module.load_span_bool([True, False, True]) == [True, False, True]
|
||||
|
||||
|
||||
def test_span_load_string():
|
||||
assert module.load_span_string(["a", "b", "c"]) == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_span_load_string_const():
|
||||
assert module.load_span_string_const(["a", "b", "c"]) == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_span_load_stringview():
|
||||
assert module.load_span_string_view(["a", "b", "c"]) == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_span_load_vector():
|
||||
assert module.load_span_vector([["a"], ["b"], ["c"]]) == [["a"], ["b"], ["c"]]
|
||||
|
||||
|
||||
def test_span_load_buffer_bytes():
|
||||
assert module.load_span_bytes(b"abc") == b"abc"
|
||||
|
||||
|
||||
def test_span_modify_buffer_bytes():
|
||||
b = b"abc"
|
||||
with pytest.raises(BufferError):
|
||||
module.modify_span_buffer(b)
|
||||
|
||||
|
||||
def test_span_load_buffer_bytearray():
|
||||
assert module.load_span_bytes(bytearray([1, 2, 3])) == b"\x01\x02\x03"
|
||||
|
||||
|
||||
def test_span_modify_buffer_bytearray():
|
||||
b = bytearray([1, 2, 3])
|
||||
module.modify_span_buffer(b)
|
||||
assert b == bytearray([4, 2, 3])
|
||||
|
||||
|
||||
def test_span_load_buffer_array():
|
||||
a = array.array("l")
|
||||
a.append(1)
|
||||
a2 = array.array("l")
|
||||
a2.frombytes(module.load_span_bytes(a))
|
||||
assert len(a2) == 1
|
||||
assert a2[0] == 1
|
||||
|
||||
|
||||
def test_span_cast():
|
||||
assert module.cast_span() == [1, 2, 3]
|
||||
|
||||
|
||||
def test_string_span():
|
||||
assert module.cast_string_span() == ["hi", "there"]
|
||||
|
||||
|
||||
def test_fixed_double_span():
|
||||
assert module.load_span_fixed_double([1, 2, 3]) == (1, 2, 3)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
assert module.load_span_fixed_double([1, 2, 3, 4])
|
||||
9
wpiutil/src/test/python/test_stacktrace.py
Normal file
9
wpiutil/src/test/python/test_stacktrace.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import wpiutil
|
||||
|
||||
|
||||
def test_python_stack_trace():
|
||||
st = wpiutil._wpiutil.getStackTrace(0)
|
||||
assert __file__ in st
|
||||
|
||||
st = wpiutil._wpiutil.getStackTraceDefault(0)
|
||||
assert __file__ not in st
|
||||
13
wpiutil/src/test/python/test_stringmap.py
Normal file
13
wpiutil/src/test/python/test_stringmap.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from wpiutil_test import module
|
||||
|
||||
|
||||
def test_stringmap_load():
|
||||
assert module.load_stringmap_int({"one": 11, "two": 22, "three": 33}) == {
|
||||
"one": 11,
|
||||
"two": 22,
|
||||
"three": 33,
|
||||
}
|
||||
|
||||
|
||||
def test_stringmap_cast():
|
||||
assert module.cast_stringmap() == {"one": 1, "two": 2}
|
||||
242
wpiutil/src/test/python/test_struct.py
Normal file
242
wpiutil/src/test/python/test_struct.py
Normal file
@@ -0,0 +1,242 @@
|
||||
import dataclasses
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from wpiutil import wpistruct
|
||||
from wpiutil_test import module
|
||||
|
||||
#
|
||||
# Static serialization
|
||||
#
|
||||
|
||||
|
||||
# ensure that a type that doesn't work has a sane error message
|
||||
def test_invalid_type():
|
||||
with pytest.raises(
|
||||
TypeError,
|
||||
match=re.escape("str is not struct serializable (does not have WPIStruct)"),
|
||||
):
|
||||
wpistruct.getSchema(str)
|
||||
|
||||
|
||||
def test_for_each_nested():
|
||||
l = []
|
||||
|
||||
def _fn(*args):
|
||||
l.append(args)
|
||||
|
||||
wpistruct.forEachNested(module.ThingA, _fn)
|
||||
assert l == [("struct:ThingA", "uint8 value")]
|
||||
|
||||
|
||||
def test_get_type_string():
|
||||
assert wpistruct.getTypeName(module.ThingA) == "ThingA"
|
||||
|
||||
|
||||
def test_get_schema():
|
||||
assert wpistruct.getSchema(module.ThingA) == "uint8 value"
|
||||
|
||||
|
||||
def test_get_size():
|
||||
assert wpistruct.getSize(module.ThingA) == 1
|
||||
|
||||
|
||||
def test_pack():
|
||||
assert wpistruct.pack(module.ThingA(1)) == b"\x01"
|
||||
|
||||
|
||||
def test_pack_array():
|
||||
assert wpistruct.packArray([module.ThingA(1), module.ThingA(2)]) == b"\x01\x02"
|
||||
|
||||
|
||||
def test_pack_into():
|
||||
buf = bytearray(1)
|
||||
wpistruct.packInto(module.ThingA(1), buf)
|
||||
assert buf == b"\x01"
|
||||
|
||||
|
||||
def test_pack_into_err():
|
||||
buf = bytearray(2)
|
||||
with pytest.raises(ValueError, match=re.escape("buffer must be 1 bytes")):
|
||||
wpistruct.packInto(module.ThingA(1), buf)
|
||||
|
||||
|
||||
def test_unpack():
|
||||
assert wpistruct.unpack(module.ThingA, b"\x01") == module.ThingA(1)
|
||||
|
||||
|
||||
def test_unpack_array():
|
||||
assert wpistruct.unpackArray(module.ThingA, b"\x01\x02") == [
|
||||
module.ThingA(1),
|
||||
module.ThingA(2),
|
||||
]
|
||||
|
||||
|
||||
# def test_unpack_into():
|
||||
# r1 = module.ThingA(1)
|
||||
# r2 = module.ThingA(2)
|
||||
# assert r1 != r2
|
||||
# wpistruct.unpackInto(b"\x01", r2)
|
||||
# assert r1 == r2
|
||||
|
||||
|
||||
#
|
||||
# Nested struct
|
||||
#
|
||||
|
||||
|
||||
def test_nested_for_each_nested():
|
||||
l = []
|
||||
|
||||
def _fn(*args):
|
||||
l.append(args)
|
||||
|
||||
wpistruct.forEachNested(module.Outer, _fn)
|
||||
assert l == [
|
||||
("struct:ThingA", "uint8 value"),
|
||||
("struct:Outer", "ThingA inner; int32 c"),
|
||||
]
|
||||
|
||||
|
||||
def test_nested_get_type_string():
|
||||
assert wpistruct.getTypeName(module.ThingA) == "ThingA"
|
||||
|
||||
|
||||
def test_nested_get_schema():
|
||||
assert wpistruct.getSchema(module.Outer) == "ThingA inner; int32 c"
|
||||
|
||||
|
||||
def test_nested_get_size():
|
||||
assert wpistruct.getSize(module.Outer) == 5
|
||||
|
||||
|
||||
def test_nested_pack():
|
||||
v = module.Outer(module.ThingA(2), 4)
|
||||
assert wpistruct.pack(v) == b"\x02\x04\x00\x00\x00"
|
||||
|
||||
|
||||
def test_nested_pack_into():
|
||||
v = module.Outer(module.ThingA(3), 5)
|
||||
buf = bytearray(5)
|
||||
wpistruct.packInto(v, buf)
|
||||
assert buf == b"\x03\x05\x00\x00\x00"
|
||||
|
||||
|
||||
def test_nested_unpack():
|
||||
assert wpistruct.unpack(module.ThingA, b"\x01") == module.ThingA(1)
|
||||
|
||||
|
||||
#
|
||||
# User defined serialization
|
||||
#
|
||||
|
||||
|
||||
@wpistruct.make_wpistruct(name="mystruct")
|
||||
@dataclasses.dataclass
|
||||
class MyStruct:
|
||||
x: int
|
||||
y: bool
|
||||
z: float
|
||||
|
||||
|
||||
def test_user_for_each_nested():
|
||||
l = []
|
||||
|
||||
def _fn(*args):
|
||||
l.append(args)
|
||||
|
||||
wpistruct.forEachNested(MyStruct, _fn)
|
||||
assert l == [("struct:mystruct", "int32 x; bool y; float z")]
|
||||
|
||||
|
||||
def test_user_get_type_string():
|
||||
assert wpistruct.getTypeName(MyStruct) == "mystruct"
|
||||
|
||||
|
||||
def test_user_get_schema():
|
||||
assert wpistruct.getSchema(MyStruct) == "int32 x; bool y; float z"
|
||||
|
||||
|
||||
def test_user_get_size():
|
||||
assert wpistruct.getSize(MyStruct) == 9
|
||||
|
||||
|
||||
def test_user_pack():
|
||||
v = MyStruct(2, True, 3.5)
|
||||
assert wpistruct.pack(v) == b"\x02\x00\x00\x00\x01\x00\x00\x60\x40"
|
||||
|
||||
|
||||
def test_user_pack_into():
|
||||
v = MyStruct(2, True, 3.5)
|
||||
buf = bytearray(9)
|
||||
wpistruct.packInto(v, buf)
|
||||
assert buf == b"\x02\x00\x00\x00\x01\x00\x00\x60\x40"
|
||||
|
||||
|
||||
def test_user_unpack():
|
||||
v = MyStruct(2, True, 3.5)
|
||||
assert wpistruct.unpack(MyStruct, b"\x02\x00\x00\x00\x01\x00\x00\x60\x40") == v
|
||||
|
||||
|
||||
# def test_user_unpack_into():
|
||||
# v1 = MyStruct(2, True, 3.5)
|
||||
# v2 = MyStruct(3, True, 4.5)
|
||||
# assert v1 != v2
|
||||
# wpistruct.unpackInto(b"\x02\x00\x00\x00\x01\x00\x00\x60\x40", v2)
|
||||
# assert v1 == v2
|
||||
|
||||
|
||||
#
|
||||
# User defined serialization (nested)
|
||||
#
|
||||
|
||||
|
||||
@wpistruct.make_wpistruct
|
||||
@dataclasses.dataclass
|
||||
class Outer:
|
||||
x: int
|
||||
inner: MyStruct
|
||||
|
||||
|
||||
def test_user_nested_for_each_nested():
|
||||
l = []
|
||||
|
||||
def _fn(*args):
|
||||
l.append(args)
|
||||
|
||||
wpistruct.forEachNested(Outer, _fn)
|
||||
assert l == [
|
||||
("struct:mystruct", "int32 x; bool y; float z"),
|
||||
("struct:Outer", "int32 x; mystruct inner"),
|
||||
]
|
||||
|
||||
|
||||
def test_user_nested_get_type_string():
|
||||
assert wpistruct.getTypeName(Outer) == "Outer"
|
||||
|
||||
|
||||
def test_user_nested_get_schema():
|
||||
assert wpistruct.getSchema(Outer) == "int32 x; mystruct inner"
|
||||
|
||||
|
||||
def test_user_nested_get_size():
|
||||
assert wpistruct.getSize(Outer) == 4 + 9
|
||||
|
||||
|
||||
def test_user_nested_pack():
|
||||
v = Outer(2, MyStruct(3, True, 4.0))
|
||||
assert wpistruct.pack(v) == b"\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x80\x40"
|
||||
|
||||
|
||||
def test_user_nested_pack_into():
|
||||
v = Outer(2, MyStruct(3, True, 4.0))
|
||||
buf = bytearray(4 + 9)
|
||||
wpistruct.packInto(v, buf)
|
||||
assert buf == b"\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x80\x40"
|
||||
|
||||
|
||||
def test_user_nested_unpack():
|
||||
assert wpistruct.unpack(
|
||||
Outer, b"\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x80\x40"
|
||||
) == Outer(2, MyStruct(3, True, 4.0))
|
||||
Reference in New Issue
Block a user