diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2aefb914a3719a..95f5c661534eba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -166,8 +166,82 @@ jobs: - name: Perform CodeQL Analysis if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/master' }} uses: github/codeql-action/analyze@v1 + build_linux_python_lib: + name: Build on Linux (python_lib) + timeout-minutes: 60 + + runs-on: ubuntu-latest + if: github.actor != 'restyled-io[bot]' + + container: + image: connectedhomeip/chip-build:0.5.0 + volumes: + - "/tmp/log_output:/tmp/test_logs" + options: + --sysctl "net.ipv6.conf.all.disable_ipv6=0 + net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1" + + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Dump Concurrency context + env: + CONCURRENCY_CONTEXT: ${{ github.ref }}-${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.event.number) || (github.event_name == 'workflow_dispatch' && github.run_number) || github.sha }} + run: echo "$CONCURRENCY_CONTEXT" + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + - name: Initialize CodeQL + if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/master' }} + uses: github/codeql-action/init@v1 + with: + languages: "cpp" + + - name: Bootstrap + timeout-minutes: 10 + run: scripts/build/gn_bootstrap.sh + # - name: Uploading bootstrap logs + # uses: actions/upload-artifact@v2 + # if: ${{ always() }} + # with: + # name: bootstrap-logs + # path: | + # .environment/gn_out/.ninja_log + # .environment/pigweed-venv/*.log + - name: Setup Build, Run Build and Run Tests + timeout-minutes: 50 + run: | + for BUILD_TYPE in python_lib; do + case $BUILD_TYPE in + "python_lib") GN_ARGS='enable_rtti=true enable_pylib=true chip_config_memory_debug_checks=false chip_config_memory_debug_dmalloc=false';; + esac + + scripts/build/gn_gen.sh --args="$GN_ARGS" + scripts/build/gn_build.sh + scripts/tests/gn_tests.sh + done + # TODO Log Upload https://github.com/project-chip/connectedhomeip/issues/2227 + # TODO https://github.com/project-chip/connectedhomeip/issues/1512 + # - name: Run Code Coverage + # if: ${{ contains('main', env.BUILD_TYPE) }} + # run: scripts/tools/codecoverage.sh + # - name: Upload Code Coverage + # if: ${{ contains('main', env.BUILD_TYPE) }} + # run: bash <(curl -s https://codecov.io/bash) + - name: Remove third_party binaries for CodeQL Analysis + run: find out -type d -name "third_party" -exec rm -rf {} + + - name: Remove dbus binaries for CodeQL Analysis + run: find out -type d -name "dbus" -exec rm -rf {} + + # - name: Remove nrfxlib binaries for CodeQL Analysis + # run: find . -type d -name "nrfxlib" -exec rm -rf {} + + - name: Perform CodeQL Analysis + if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/master' }} + uses: github/codeql-action/analyze@v1 build_darwin: - name: Build on Darwin + name: Build on Darwin (clang, python_lib) timeout-minutes: 60 env: @@ -202,19 +276,22 @@ jobs: path: | .environment/gn_out/.ninja_log .environment/pigweed-venv/*.log - - name: Setup Build + - name: Setup Build, Run Build and Run Tests + timeout-minutes: 50 # Just go ahead and do the "all" build; on Darwin that's fairly # fast. If this ever becomes slow, we can think about ways to do # the examples-linux-standalone.yaml tests on darwin without too # much code duplication. run: | - scripts/build/gn_gen.sh --args='is_clang=true target_os="all"' - - name: Run Build - timeout-minutes: 30 - run: scripts/build/gn_build.sh - - name: Run Tests - timeout-minutes: 10 - run: scripts/tests/gn_tests.sh + for BUILD_TYPE in clang python_lib; do + case $BUILD_TYPE in + "clang") GN_ARGS='is_clang=true target_os="all"';; + "python_lib") GN_ARGS='enable_rtti=true enable_pylib=true';; + esac + scripts/build/gn_gen.sh --args="$GN_ARGS" + scripts/build/gn_build.sh + scripts/tests/gn_tests.sh + done # TODO Log Upload https://github.com/project-chip/connectedhomeip/issues/2227 # TODO https://github.com/project-chip/connectedhomeip/issues/1512 # - name: Run Code Coverage diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index 8fdcb9140e224f..417329feac3810 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -37,7 +37,7 @@ jobs: with: submodules: true - name: Setup Environment - run: brew install openssl pkg-config + run: brew install openssl pkg-config python@3.9 - name: Fix pkgconfig link working-directory: /usr/local/lib/pkgconfig run: | diff --git a/.gitmodules b/.gitmodules index 314cc034008bc3..1a555556f64788 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,79 +1,79 @@ [submodule "nlassert"] path = third_party/nlassert/repo url = https://github.com/nestlabs/nlassert.git - branch = master + branch = master [submodule "nlfaultinjection"] path = third_party/nlfaultinjection/repo url = https://github.com/nestlabs/nlfaultinjection.git - branch = master + branch = master [submodule "nlio"] path = third_party/nlio/repo url = https://github.com/nestlabs/nlio.git - branch = master + branch = master [submodule "nlunit-test"] path = third_party/nlunit-test/repo url = https://github.com/nestlabs/nlunit-test.git - branch = master + branch = master [submodule "mbedtls"] path = third_party/mbedtls/repo url = https://github.com/ARMmbed/mbedtls.git - branch = mbedtls-2.25 + branch = mbedtls-2.25 [submodule "qrcode"] path = examples/common/QRCode/repo url = https://github.com/nayuki/QR-Code-generator.git - branch = master + branch = master [submodule "m5stack-tft"] path = examples/common/m5stack-tft/repo url = https://github.com/jeremyjh/ESP32_TFT_library.git - branch = master + branch = master [submodule "pigweed"] path = third_party/pigweed/repo url = https://github.com/google/pigweed.git - branch = main + branch = main [submodule "openthread"] path = third_party/openthread/repo url = https://github.com/openthread/openthread.git - branch = main + branch = main [submodule "ot-br-posix"] path = third_party/ot-br-posix/repo url = https://github.com/openthread/ot-br-posix.git - branch = main + branch = main [submodule "bluez"] path = third_party/bluez/repo url = https://github.com/bluez/bluez.git - branch = master + branch = master [submodule "cirque"] path = third_party/cirque/repo url = https://github.com/openweave/cirque.git - branch = master + branch = master [submodule "happy"] path = third_party/happy/repo url = https://github.com/openweave/happy.git - branch = master + branch = master [submodule "nanopb"] path = third_party/nanopb/repo url = https://github.com/nanopb/nanopb.git - branch = master + branch = master [submodule "qpg_sdk"] path = third_party/qpg_sdk/repo url = https://github.com/Qorvo/qpg-connectedhomeip - branch = master + branch = master [submodule "zap"] path = third_party/zap/repo url = https://github.com/project-chip/zap.git - branch = master + branch = master [submodule "freertos"] path = third_party/freertos/repo url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git - branch = V10.3.1-kernel-only + branch = V10.3.1-kernel-only [submodule "efr32_sdk"] path = third_party/efr32_sdk/repo url = https://github.com/SiliconLabs/sdk_support.git - branch = master + branch = master [submodule "simw-top-mini"] path = third_party/simw-top-mini/repo url = https://github.com/NXP/plug-and-trust.git - branch = int/CHIPSE_Release + branch = int/CHIPSE_Release [submodule "third_party/openthread/ot-nxp"] path = third_party/openthread/ot-nxp url = https://github.com/openthread/ot-nxp.git @@ -86,22 +86,22 @@ [submodule "third_party/mbed-os/repo"] path = third_party/mbed-os/repo url = https://github.com/ARMmbed/mbed-os.git - branch = feature-chip + branch = feature-chip [submodule "third_party/wifi-ism43362/repo"] path = third_party/wifi-ism43362/repo url = https://github.com/ATmobica/wifi-ism43362.git - branch = master + branch = master [submodule "third_party/mbed-os-posix-socket/repo"] path = third_party/mbed-os-posix-socket/repo url = https://github.com/ARMmbed/mbed-os-posix-socket.git - branch = main + branch = main [submodule "p6/abstraction-rtos"] path = third_party/p6/p6_sdk/libs/abstraction-rtos url = https://github.com/Infineon/abstraction-rtos [submodule "p6/bluetooth-freertos"] path = third_party/p6/p6_sdk/libs/bluetooth-freertos url = https://github.com/Infineon/bluetooth-freertos - branch = v2.X + branch = v2.X [submodule "p6/btstack"] path = third_party/p6/p6_sdk/libs/btstack url = https://github.com/Infineon/btstack @@ -159,3 +159,7 @@ [submodule "p6/recipe-make-cat1a"] path = third_party/p6/p6_sdk/libs/recipe-make-cat1a url = https://github.com/Infineon/recipe-make-cat1a +[submodule "third_party/pybind11/repo"] + path = third_party/pybind11/repo + url = https://github.com/pybind/pybind11 + branch = stable diff --git a/BUILD.gn b/BUILD.gn index 07be5c986a6de9..8b2fbc3937feeb 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -37,6 +37,7 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { chip_enable_python_modules = (current_os == "mac" || current_os == "linux") && (host_cpu == "x64" || host_cpu == "arm64") + enable_pylib = false } # Python packages for supporting specific targets. @@ -112,6 +113,9 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { deps += [ "${chip_root}/src/tools/chip-cert" ] } if (chip_enable_python_modules) { + if (enable_pylib) { + deps += [ "${chip_root}/src/pybindings/pycontroller" ] + } deps += [ "${chip_root}/src/controller/python" ] } } @@ -129,6 +133,9 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { data_deps = [ "${chip_root}/examples/chip-tool" ] if (chip_enable_python_modules) { + if (enable_pylib) { + data_deps += [ "${chip_root}/src/pybindings/pycontroller" ] + } data_deps += [ "${chip_root}/src/controller/python" ] } diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 9514db60332582..d6a1377159a8ab 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -356,6 +356,11 @@ config("sanitize_default") { config("fuzzing_default") { } +declare_args() { + # Enable Runtime Type Information (RTTI) + enable_rtti = false +} + config("no_rtti") { cflags_cc = [ "-fno-rtti" ] } @@ -365,7 +370,11 @@ config("rtti") { } config("rtti_default") { - configs = [ ":no_rtti" ] + if (enable_rtti) { + configs = [ ":rtti" ] + } else { + configs = [ ":no_rtti" ] + } } config("no_exceptions") { diff --git a/integrations/docker/images/chip-build/Dockerfile b/integrations/docker/images/chip-build/Dockerfile index d9ea2c5432434c..ca6b3a091793ce 100644 --- a/integrations/docker/images/chip-build/Dockerfile +++ b/integrations/docker/images/chip-build/Dockerfile @@ -50,10 +50,9 @@ RUN set -x \ ninja-build \ openjdk-8-jdk \ pkg-config \ - python3 \ - python3-dev \ - python3-pip \ - python3-venv \ + python3.9 \ + python3.9-dev \ + python3.9-venv \ rsync \ shellcheck \ strace \ @@ -74,14 +73,15 @@ RUN set -x \ && exec bash \ && : # last line -# Python 2 and PIP +# Python 3.9 and PIP RUN set -x \ - && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y libgirepository1.0-dev \ && DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common \ && add-apt-repository universe \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y python python2 \ - && curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py \ - && python2 get-pip.py \ + && curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \ + && python3.9 get-pip.py \ + && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 1 \ && rm -rf /var/lib/apt/lists/ \ && : # last line diff --git a/integrations/docker/images/chip-build/version b/integrations/docker/images/chip-build/version index 3f3e54fc64cea2..8f0916f768f048 100644 --- a/integrations/docker/images/chip-build/version +++ b/integrations/docker/images/chip-build/version @@ -1 +1 @@ -0.4.37 +0.5.0 diff --git a/scripts/build_python.sh b/scripts/build_python.sh index e1a29a3ffe64a6..b03ed4c28d48ff 100755 --- a/scripts/build_python.sh +++ b/scripts/build_python.sh @@ -39,12 +39,13 @@ OUTPUT_ROOT="$CHIP_ROOT/out/python_lib" ENVIRONMENT_ROOT="$CHIP_ROOT/out/python_env" declare chip_detail_logging=false +declare enable_pybindings=false declare chip_mdns declare clusters=true help() { - echo "Usage: $file_name [ options ... ] [ -chip_detail_logging ChipDetailLoggingValue ] [ -chip_mdns ChipMDNSValue ]" + echo "Usage: $file_name [ options ... ] [ -chip_detail_logging ChipDetailLoggingValue ] [ -chip_mdns ChipMDNSValue ] [-enable_pybindings EnableValue]" echo "General Options: -h, --help Display this information. @@ -55,6 +56,7 @@ Input Options: By default it is minimal. -c, --clusters_for_ip_commissioning true/false Specify whether to use clusters for IP commissioning. By default it is true. + -p, --enable_pybindings EnableValue Specify whether to enable pybindings as python controller. " } @@ -78,6 +80,10 @@ while (($#)); do clusters=$2 shift ;; + --enable_pybindings | -p) + enable_pybindings=$2 + shift + ;; -*) help echo "Unknown Option \"$1\"" @@ -88,25 +94,38 @@ while (($#)); do done # Print input values -echo "Input values: chip_detail_logging = $chip_detail_logging , chip_mdns = \"$chip_mdns\"" +echo "Input values: chip_detail_logging = $chip_detail_logging , chip_mdns = \"$chip_mdns\", enable_pybindings = $enable_pybindings" # Ensure we have a compilation environment source "$CHIP_ROOT/scripts/activate.sh" # Generates ninja files [[ -n "$chip_mdns" ]] && chip_mdns_arg="chip_mdns=\"$chip_mdns\"" || chip_mdns_arg="" -gn --root="$CHIP_ROOT" gen "$OUTPUT_ROOT" --args="chip_detail_logging=$chip_detail_logging chip_use_clusters_for_ip_commissioning=$clusters $chip_mdns_arg" + +gn --root="$CHIP_ROOT" gen "$OUTPUT_ROOT" --args="chip_detail_logging=$chip_detail_logging enable_pylib=$enable_pybindings enable_rtti=$enable_pybindings chip_use_clusters_for_ip_commissioning=$clusters $chip_mdns_arg" # Compiles python files -ninja -C "$OUTPUT_ROOT" python +# Check pybindings was requested +if [ "$enable_pybindings" == true ]; then + ninja -v -C "$OUTPUT_ROOT" pycontroller +else + ninja -v -C "$OUTPUT_ROOT" python +fi # Create a virtual environment that has access to the built python tools virtualenv --clear "$ENVIRONMENT_ROOT" # Activate the new enviroment to register the python WHL + +if [ "$enable_pybindings" == true ]; then + WHEEL=$(ls "$OUTPUT_ROOT"/pybindings/pycontroller/pychip-*.whl | head -n 1) +else + WHEEL=$(ls "$OUTPUT_ROOT"/controller/python/chip-*.whl | head -n 1) +fi + source "$ENVIRONMENT_ROOT"/bin/activate "$ENVIRONMENT_ROOT"/bin/python -m pip install --upgrade pip -"$ENVIRONMENT_ROOT"/bin/pip install --upgrade --force-reinstall --no-cache-dir "$OUTPUT_ROOT"/controller/python/chip-*.whl +"$ENVIRONMENT_ROOT"/bin/pip install --upgrade --force-reinstall --no-cache-dir "$WHEEL" echo "" echo_green "Compilation completed and WHL package installed in: " diff --git a/src/pybindings/pycontroller/BUILD.gn b/src/pybindings/pycontroller/BUILD.gn new file mode 100644 index 00000000000000..48bc73ea38b4c4 --- /dev/null +++ b/src/pybindings/pycontroller/BUILD.gn @@ -0,0 +1,190 @@ +# Copyright (c) 2021 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") +import("//build_overrides/pigweed.gni") + +import("$dir_pw_build/python.gni") + +import("${chip_root}/build/chip/tools.gni") +import("${chip_root}/src/platform/device.gni") +import("${dir_pw_unit_test}/test.gni") + +if (current_os == "mac") { + import("${build_root}/config/mac/mac_sdk.gni") +} + +config("controller_wno_deprecate") { + cflags = [ + "-Wno-deprecated-declarations", + "-Wno-shadow", + "-Wno-unused-result", + "-Wsign-compare", + "-Wunreachable-code", + "-Wno-macro-redefined", + ] +} + +shared_library("CHIPController") { + configs -= [ "//build/config/compiler:exceptions_default" ] + + output_name = "PyChip" + output_dir = "${target_out_dir}/pychip" + + # TODO: Update to use GN tools to get actual paths + include_dirs = [ "${chip_root}/third_party/pybind11/repo/include" ] + if (current_os == "mac") { + include_dirs += + [ "${chip_root}/.environment/cipd/python/include/python3.8" ] + } else if (current_os == "linux") { + include_dirs += [ "/usr/include/python3.9" ] + } else { + assert(false, "OS not supprted.") + } + + sources = [ + "${root_gen_dir}/include/pybindings/pycontroller/CHIPErrorToExceptionBindings.cpp", + "${root_gen_dir}/include/pybindings/pycontroller/CHIPErrorToExceptionBindings.h", + "ControllerBindings/PyChip_Main.cpp", + ] + + public_deps = [ + ":build_exceptions", + "${chip_root}/src/app", + "${chip_root}/src/controller/data_model", + "${chip_root}/src/lib", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/mdns", + "${chip_root}/src/lib/support", + "${chip_root}/src/platform", + "${chip_root}/src/setup_payload", + "${chip_root}/src/transport", + ] + configs += [ ":controller_wno_deprecate" ] + if (current_os == "mac") { + ldflags = [ + "-undefined", + "dynamic_lookup", + ] + } + if (current_os == "linux") { + libs = [ "python3.9" ] + } +} +pw_python_action("build_exceptions") { + script = "create_error_wrapper.py" + + header_file = "${root_gen_dir}/include/pybindings/pycontroller/CHIPErrorToExceptionBindings.h" + cpp_file = "${root_gen_dir}/include/pybindings/pycontroller/CHIPErrorToExceptionBindings.cpp" + inputs = [ + "${chip_root}/src/lib/core/CHIPError.h", + "${chip_root}/src/lib/core/CHIPConfig.h", + ] + outputs = [ + header_file, + cpp_file, + ] + + args = [ + "--output_cpp_file=" + rebase_path(cpp_file, root_build_dir), + "--output_header_file=" + rebase_path(header_file, root_build_dir), + "--error_header=" + + rebase_path("${chip_root}/src/lib/core/CHIPError.h", root_build_dir), + "--config_header=" + + rebase_path("${chip_root}/src/lib/core/CHIPConfig.h", root_build_dir), + ] +} + +pw_python_action("pycontroller") { + script = "build-chip-wheel.py" + + _py_manifest_files = [ + { + src_dir = "." + sources = [] + }, + { + src_dir = target_out_dir + sources = [ "${target_out_dir}/pychip/PyChip.so" ] + }, + { + src_dir = "//" + sources = [ "//LICENSE" ] + }, + ] + + _py_manifest_file = "${target_gen_dir}/${target_name}.py_manifest.json" + + inputs = [ + "${root_gen_dir}/include/pybindings/pycontroller/CHIPErrorToExceptionBindings.cpp", + "${root_gen_dir}/include/pybindings/pycontroller/CHIPErrorToExceptionBindings.h", + ] + _py_manifest_files_rebased = [] + foreach(_manifest_entry, _py_manifest_files) { + inputs += _manifest_entry.sources + _py_manifest_files_rebased += [ + { + src_dir = rebase_path(_manifest_entry.src_dir, + get_path_info(_py_manifest_file, "dir")) + sources = rebase_path(_manifest_entry.sources, _manifest_entry.src_dir) + }, + ] + } + + _py_manifest = { + files = _py_manifest_files_rebased + } + + write_file(_py_manifest_file, _py_manifest, "json") + + _dist_dir = "${root_out_dir}/pybindings/pycontroller" + + if (current_cpu == "x64") { + cpu_tag = "x86_64" + } else if (current_cpu == "arm64") { + cpu_tag = "aarch64" + } else { + cpu_tag = current_cpu + } + + if (current_os == "mac") { + platform_tag = "macosx_" + string_replace(mac_deployment_target, ".", "_") + } else { + platform_tag = current_os + } + + platform_tag = platform_tag + "_" + cpu_tag + + tags = "cp37-" + platform_tag + + args = [ + "--package_name", + "pychip", + "--build_number", + "0.0", + "--build_dir", + rebase_path("${target_gen_dir}/${target_name}.py_build", root_build_dir), + "--dist_dir", + rebase_path(_dist_dir, root_build_dir), + "--manifest", + rebase_path(_py_manifest_file, root_build_dir), + "--plat-name", + platform_tag, + ] + + public_deps = [ ":CHIPController" ] + + output_name = "pychip-0.0.dist-info-0.0-${tags}.whl" + outputs = [ "${_dist_dir}/$output_name" ] +} diff --git a/src/pybindings/pycontroller/ControllerBindings/PyChip_Main.cpp b/src/pybindings/pycontroller/ControllerBindings/PyChip_Main.cpp new file mode 100644 index 00000000000000..7398aeff54351e --- /dev/null +++ b/src/pybindings/pycontroller/ControllerBindings/PyChip_Main.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +#include + +typedef std::function ModuleGetter; + +void bind_CHIPController_ChipExceptions(std::function & M); + +PYBIND11_MODULE(PyChip, root_module) +{ + root_module.doc() = "PyChip module"; + + std::map modules; + ModuleGetter M = [&](std::string const & namespace_) -> pybind11::module & { + auto it = modules.find(namespace_); + if (it == modules.end()) + throw std::runtime_error("Attempt to access pybind11::module for namespace " + namespace_ + " that does not exist!!!"); + return it->second; + }; + + modules[""] = root_module; + + std::vector> sub_modules{ { "", "ChipExceptions" } }; + for (auto & p : sub_modules) + modules[p.first.size() ? p.first + "::" + p.second : p.second] = + modules[p.first].def_submodule(p.second.c_str(), ("Bindings for " + p.first + "::" + p.second + " namespace").c_str()); + + bind_CHIPController_ChipExceptions(M); +} diff --git a/src/pybindings/pycontroller/build-chip-wheel.py b/src/pybindings/pycontroller/build-chip-wheel.py new file mode 100644 index 00000000000000..41a450b79147f0 --- /dev/null +++ b/src/pybindings/pycontroller/build-chip-wheel.py @@ -0,0 +1,194 @@ +# +# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2019 Google LLC. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Description: +# Builds a Python wheel package for CHIP. +# + +from __future__ import absolute_import +from datetime import datetime +from setuptools import setup +from wheel.bdist_wheel import bdist_wheel + +import argparse +import json +import os +import platform +import shutil + + +parser = argparse.ArgumentParser( + description='build the pip package for chip using chip components generated during the build and python source code') +parser.add_argument('--package_name', default='chip', + help='configure the python package name') +parser.add_argument('--build_number', default='0.0', + help='configure the chip build number') +parser.add_argument('--build_dir', help='directory to build in') +parser.add_argument('--dist_dir', help='directory to place distribution in') +parser.add_argument('--manifest', help='list of files to package') +parser.add_argument( + '--plat-name', help='platform name to embed in generated filenames') + +args = parser.parse_args() + + +class InstalledScriptInfo: + """Information holder about a script that is to be installed.""" + + def __init__(self, name): + self.name = name + self.installName = os.path.splitext(name)[0] + + +chipDLLName = 'PyChip.so' +packageName = args.package_name +chipPackageVer = args.build_number + +installScripts = [ + # InstalledScriptInfo('chip-device-ctrl.py'), + # InstalledScriptInfo('chip-repl.py'), +] + +# Record the current directory at the start of execution. +curDir = os.curdir + +manifestFile = os.path.abspath(args.manifest) +buildDir = os.path.abspath(args.build_dir) +distDir = os.path.abspath(args.dist_dir) + +# Use a temporary directory within the build directory to assemble the components +# for the installable package. +tmpDir = os.path.join(buildDir, 'pychip-wheel-components') + +manifest = json.load(open(manifestFile, 'r')) + +try: + + # + # Perform a series of setup steps prior to creating the chip package... + # + + # Create the temporary components directory. + if os.path.isdir(tmpDir): + shutil.rmtree(tmpDir) + os.makedirs(tmpDir, exist_ok=True) + + # Switch to the temporary directory. (Foolishly, setuptools relies on the current directory + # for many of its features.) + os.chdir(tmpDir) + + manifestBase = os.path.dirname(manifestFile) + for entry in manifest['files']: + srcDir = os.path.join(manifestBase, entry['src_dir']) + for path in entry['sources']: + srcFile = os.path.join(srcDir, path) + dstFile = os.path.join(tmpDir, path) + os.makedirs(os.path.dirname(dstFile), exist_ok=True) + shutil.copyfile(srcFile, dstFile) + + for script in installScripts: + os.rename(os.path.join(tmpDir, script.name), + os.path.join(tmpDir, script.installName)) + + # Define a custom version of the bdist_wheel command that configures the + # resultant wheel as platform-specific (i.e. not "pure"). + class bdist_wheel_override(bdist_wheel): + def finalize_options(self): + bdist_wheel.finalize_options(self) + self.root_is_pure = False + + requiredPackages = [ + "coloredlogs", + 'construct', + 'ipython', + ] + + if platform.system() == 'Darwin': + requiredPackages.append('pyobjc-framework-corebluetooth') + + if platform.system() == 'Linux': + requiredPackages.append('dbus-python') + requiredPackages.append('pygobject') + + # + # Build the chip package... + # + packages = [ + 'pychip', + ] + + # Invoke the setuptools 'bdist_wheel' command to generate a wheel containing + # the CHIP python packages, shared libraries and scripts. + setup( + name=packageName, + version=chipPackageVer, + description='Python-base APIs and tools for CHIP.', + url='https://github.com/project-chip/connectedhomeip', + license='Apache', + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + ], + python_requires='>=2.7', + packages=packages, + package_dir={ + # By default, look in the tmp directory for packages/modules to be included. + '': tmpDir, + }, + package_data={ + packageName: [ + # Include the wrapper DLL as package data in the "chip" package. + chipDLLName + ] + }, + scripts=[name for name in map( + lambda script: os.path.join(tmpDir, script.installName), + installScripts + )], + install_requires=requiredPackages, + options={ + 'bdist_wheel': { + 'universal': False, + # Place the generated .whl in the dist directory. + 'dist_dir': distDir, + 'py_limited_api': 'cp37', + 'plat_name': args.plat_name, + }, + 'egg_info': { + # Place the .egg-info subdirectory in the tmp directory. + 'egg_base': tmpDir + } + }, + cmdclass={ + 'bdist_wheel': bdist_wheel_override + }, + script_args=['clean', '--all', 'bdist_wheel'] + ) + +finally: + + # Switch back to the initial current directory. + os.chdir(curDir) + + # Remove the temporary directory. + if os.path.isdir(tmpDir): + shutil.rmtree(tmpDir) diff --git a/src/pybindings/pycontroller/create_error_wrapper.py b/src/pybindings/pycontroller/create_error_wrapper.py new file mode 100644 index 00000000000000..a8accf745627db --- /dev/null +++ b/src/pybindings/pycontroller/create_error_wrapper.py @@ -0,0 +1,168 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import sys +import re +import os +import argparse + + +class ErrorCode(object): + def __init__(self, error_name, error_code): + self.name = error_name.replace('_', ' ').title().replace(' ', '') + self.message = error_name + self.code = int(error_code, 16) + + def __str__(self): + return ''.format(self.name, self.code) + + def __repr__(self): + return ''.format(self.name, self.code) + + def create_exception_class(self): + return ''' +struct : public std::exception +{ + const char * what () const throw () + { + return ""; + } +}; +'''.replace('', self.name).replace('', self.message) + + def create_py_bind(self, module_name): + return 'py::register_exception({}, "{}");'.format(self.name, module_name, self.name) + + def create_else_if(self, var_name): + return ''' + else if ( == ) { + throw chip::PythonBindings::(); + }'''.replace('', var_name).replace('', str(self.code)).replace('', self.name) + + +def generate_header(error_codes): + structs = "".join([x.create_exception_class() for x in error_codes]) + return ''' +#include +#include + +namespace chip { +namespace PythonBindings { + + +void CHIPErrorToException(chip::ChipError err); +} +} +'''.replace('', structs) + + +def generate_cpp(error_codes): + exception_binding = '\n\t'.join( + [x.create_py_bind('M("ChipExceptions")') for x in error_codes]) + elseifs = ''.join([x.create_else_if('err.AsInteger()') + for x in error_codes]) + return ''' +#include "pybind11/pybind11.h" +#include "CHIPErrorToExceptionBindings.h" +#include + +namespace py = pybind11; + +void CHIPErrorToException(chip::ChipError err) { + if (err == CHIP_NO_ERROR) { + //Do Nothing + } +} + +void bind_CHIPController_ChipExceptions(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + M("ChipExceptions").def("CHIPErrorToException", &CHIPErrorToException, "Converts a chip::ChipError to an Exception"); + + +} +'''.replace('', exception_binding).replace('', elseifs) + + +def create_error_bindings(error_path, config_path, header_out, cpp_out): + error_header = None + config_header = None + + with open(error_path, 'r') as f: + error_header = f.read() + + with open(config_path, 'r') as f: + config_header = f.read() + + if error_header != None and config_header != None: + error_code_re = re.compile( + r'#define ([^\s]+).*CHIP_CORE_ERROR\((0[xX][0-9a-fA-F]+)\)') + error_code_tuples = error_code_re.findall(error_header) + error_codes = [ErrorCode(x[0], x[1]) + for x in error_code_tuples] + header = generate_header(error_codes) + cpp = generate_cpp(error_codes) + + print("Creating Header file: {}".format(header_out)) + with open(header_out, 'w') as f: + f.write(header) + + print("Creating CPP file {}".format(cpp_out)) + with open(cpp_out, 'w') as f: + f.write(cpp) + else: + print("Unable to open required files :(") + sys.exit(1) + + +def main(): + p = argparse.ArgumentParser() + + p.add_argument('--output_cpp_file', required=True, + help='Output location for .cpp file') + p.add_argument('--output_header_file', required=True, + help='Output location for .h file') + p.add_argument('--error_header', required=False, + help='Location of Error Header file (ex CHIPError.h)') + p.add_argument('--config_header', required=False, + help='Location of Config Header file (ex CHIPConfig.h)') + + args = p.parse_args() + + output_cpp_file = args.output_cpp_file + output_header_file = args.output_header_file + + error_path = '' + config_path = '' + + if args.error_header != None and args.error_header != "": + error_path = args.error_header + + if not os.path.exists(error_path): + print("Error Header Path {} does not exist".format(error_path)) + sys.exit(1) + + if args.config_header != None and args.config_header != "": + config_path = args.config_header + + if not os.path.exists(config_path): + print("Config Header Path {} does not exist!".format(config_path)) + + create_error_bindings(error_path, config_path, + output_header_file, output_cpp_file) + + +if __name__ == '__main__': + main() diff --git a/third_party/pybind11/repo b/third_party/pybind11/repo new file mode 160000 index 00000000000000..8de7772cc72dac --- /dev/null +++ b/third_party/pybind11/repo @@ -0,0 +1 @@ +Subproject commit 8de7772cc72daca8e947b79b83fea46214931604