Skip to content

Commit

Permalink
Build the Rootcanal pypi package
Browse files Browse the repository at this point in the history
- generate a multi platform python wheel package
  containing:
  + the rootcanal binary
  + the python binder library to the rootcanal FFI interface

- attach the wheel package to release assets
  • Loading branch information
hchataing committed Sep 20, 2023
1 parent 2b57c6b commit 18eda78
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 19 deletions.
52 changes: 39 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ jobs:
submodules: recursive

- name: Install dependencies
run: cargo install pdl-compiler
run: |
cargo install pdl-compiler
- name: Build
run: |
Expand All @@ -36,16 +37,13 @@ jobs:
- name: Package Artifacts
run: |
mkdir -p bin
mkdir -p lib
mkdir -p include
cp bazel-bin/rootcanal bin
cp bazel-bin/librootcanal_ffi.so lib
cp model/controller/ffi.h include/rootcanal_ffi.h
zip -r rootcanal-${{ matrix.release }} \
bin/rootcanal \
lib/librootcanal_ffi.so \
include/rootcanal_ffi.h
mkdir -p rootcanal-${{ matrix.release }}/bin
mkdir -p rootcanal-${{ matrix.release }}/lib
mkdir -p rootcanal-${{ matrix.release }}/include
cp bazel-bin/rootcanal rootcanal-${{ matrix.release }}/bin
cp bazel-bin/librootcanal_ffi.so rootcanal-${{ matrix.release }}/lib
cp model/controller/ffi.h rootcanal-${{ matrix.release }}/include/rootcanal_ffi.h
zip -r rootcanal-${{ matrix.release }} rootcanal-${{ matrix.release }}
- name: Upload Artifacts
uses: actions/upload-artifact@v3
Expand All @@ -60,6 +58,21 @@ jobs:
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive

- name: Install dependencies
run: |
cargo install pdl-compiler
python3 -m pip install hatch
- name: Set VERSION
run: |
TAG=${{ github.ref_name }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- name: Create Release
id: create_release
uses: actions/create-release@v1
Expand All @@ -81,14 +94,27 @@ jobs:
with:
name: rootcanal-macos-x86_64

- name: Build Python Wheel
run: bash py/make_wheel.sh ${{ env.VERSION }}

- name: Upload Python Wheel
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: py/dist/rootcanal-${{ env.VERSION }}-py3-none-any.whl
asset_name: rootcanal-${{ env.VERSION }}-py3-none-any.whl
asset_content_type: application/x-wheel+zip

- name: Upload rootcanal-linux-x86_64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: rootcanal-linux-x86_64.zip
asset_name: rootcanal-${{ github.ref_name }}-linux-x86_64.zip
asset_name: rootcanal-${{ env.VERSION }}-linux-x86_64.zip
asset_content_type: application/zip

- name: Upload rootcanal-macos-x86_64
Expand All @@ -98,5 +124,5 @@ jobs:
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: rootcanal-macos-x86_64.zip
asset_name: rootcanal-${{ github.ref_name }}-macos-x86_64.zip
asset_name: rootcanal-${{ env.VERSION }}-macos-x86_64.zip
asset_content_type: application/zip
42 changes: 42 additions & 0 deletions py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# RootCanal

Binding to the RootCanal controller implementation. Enables virtual testing
of Bluetooth applications directly in Python.

## Usage

```python
from rootcanal.controller import Controller
from rootcanal.bluetooth import Address
import hci_packets as hci
import llcp_packets as llcp
import lmp_packets as lmp
import ll_packets as ll

controller = Controller::new(Address("ca:fe:ca:fe:00:00"))

# Send HCI Reset to the controller and wait for the response.
controller.send_cmd(hci.Reset())
_ = await controller.expect_evt(hci.ResetCommplete)

# Enable page scan.
controller.send_cmd(hci.WriteScanEnable(scan_enable=hci.ScanEnable.PAGE_SCAN_ONLY))
_ = await controller.expect_evt(hci.WriteScanEnableComplete)

# Send classic connection request.
controller.send_ll(
ll.Page(
class_of_device=0,
allow_role_switch=0,
source_address=Address("11:11:11:11:11:11")))
_ = await controller.expect_evt(hci.ConnectionRequest)

# Accept the connection request.
_ = await controller.send_cmd(
hci.AcceptConnectionRequest(
bd_addr=Address("11:11:11:11:11:11"),
role=hci.AcceptConnectionRequestRole.REMAIN_PERIPHERAL))
_ = await controller.expect_evt(hci.AcceptConnectionRequestStatus)
_ = await controller.expect_ll(hci.PageResponse)
_ = await controller.expect_evt(hci.ConnectionComplete)
```
39 changes: 39 additions & 0 deletions py/make_wheel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

set -e

# Build the python wheel package for rootcanal including
# the binary release of the rootcanal executable and shared library.

# 0. Extract build artifacts.

unzip rootcanal-linux-x86_64.zip
mkdir -p py/src/rootcanal/bin/linux-x86_64
cp rootcanal-linux-x86_64/bin/rootcanal py/src/rootcanal/bin/linux-x86_64/rootcanal
cp rootcanal-linux-x86_64/lib/librootcanal_ffi.so py/src/rootcanal/bin/linux-x86_64/librootcanal_ffi.so

unzip rootcanal-macos-x86_64.zip
mkdir -p py/src/rootcanal/bin/macos-x86_64
cp rootcanal-macos-x86_64/bin/rootcanal py/src/rootcanal/bin/macos-x86_64/rootcanal
cp rootcanal-macos-x86_64/lib/librootcanal_ffi.so py/src/rootcanal/bin/macos-x86_64/librootcanal_ffi.so

# 1. Generate the python backends for packet parsing.
mkdir -p py/src/rootcanal/packets
pdlc packets/hci_packets.pdl |./third_party/pdl/pdl-compiler/scripts/generate_python_backend.py \
--custom-type-location "..bluetooth" \
--output "py/src/rootcanal/packets/hci.py"
pdlc packets/link_layer_packets.pdl |./third_party/pdl/pdl-compiler/scripts/generate_python_backend.py \
--custom-type-location "..bluetooth" \
--output "py/src/rootcanal/packets/ll.py"
pdlc rust/lmp_packets.pdl |./third_party/pdl/pdl-compiler/scripts/generate_python_backend.py \
--output "py/src/rootcanal/packets/lmp.py"
pdlc rust/llcp_packets.pdl |./third_party/pdl/pdl-compiler/scripts/generate_python_backend.py \
--output "py/src/rootcanal/packets/llcp.py"

# 2. Configure the version.
cd py
python3 -m hatch version ${1}

# 3. Build wheel.
python3 -m hatch build -t wheel
cd -
50 changes: 50 additions & 0 deletions py/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[project]
name = "rootcanal"
description = "Virtual Bluetooth controller"
requires-python = ">=3.10"
license.text = "Apache-2.0"
readme = "README.md"
maintainers = [
{ name="Henri Chataing", email="[email protected]" },
{ name="David Duarte", email="[email protected]" },
]
dynamic = [
"version"
]
keywords = ["Bluetooth", "Controller", "Emulation"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
]

[project.urls]
"Homepage" = "https://github.com/google/rootcanal"
"Bug Tracker" = "https://github.com/google/rootcanal/issues"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/rootcanal/__about__.py"

[tool.hatch.build]
sources = ["src"]

[tool.hatch.build.targets.wheel]
include = [
"src/rootcanal/__init__.py",
"src/rootcanal/__main__.py",
"src/rootcanal/controller.py",
"src/rootcanal/bluetooth.py",
"src/rootcanal/packets/__init__.py",
"src/rootcanal/packets/hci.py",
"src/rootcanal/packets/ll.py",
"src/rootcanal/packets/lmp.py",
"src/rootcanal/packets/llcp.py",
"src/rootcanal/bin/linux-x86_64/rootcanal",
"src/rootcanal/bin/linux-x86_64/librootcanal_ffi.so",
"src/rootcanal/bin/macos-x86_64/rootcanal",
"src/rootcanal/bin/macos-x86_64/librootcanal_ffi.so",
]
15 changes: 15 additions & 0 deletions py/src/rootcanal/__about__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2023 The Android Open Source Project
#
# 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.

__version__ = "0.0.0"
Empty file added py/src/rootcanal/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions py/src/rootcanal/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2023 The Android Open Source Project
#
# 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 os
import sys
import subprocess
import distutils

sys.exit(subprocess.call([
os.path.join(os.path.dirname(__file__), "bin", distutils.util.get_platform(), "rootcanal"),
*sys.argv[1:]
]))
File renamed without changes.
16 changes: 10 additions & 6 deletions py/controller.py → py/src/rootcanal/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@

import asyncio
import collections
import distutils
import enum
import hci_packets as hci
import link_layer_packets as ll
import llcp_packets as llcp
import py.bluetooth
import os
from .packets import hci
from .packets import ll
from .packets import llcp
from .packets.hci import ErrorCode
from . import bluetooth
import sys
import typing
import unittest
from typing import Optional, Tuple, Union
from hci_packets import ErrorCode

from ctypes import *

rootcanal = cdll.LoadLibrary("lib_rootcanal_ffi.so")
librootcanal_ffi_path = os.path.join(
os.path.dirname(__file__), "bin", distutils.util.get_platform(), "librootcanal_ffi.so")
rootcanal = cdll.LoadLibrary(librootcanal_ffi_path)
rootcanal.ffi_controller_new.restype = c_void_p

SEND_HCI_FUNC = CFUNCTYPE(None, c_int, POINTER(c_ubyte), c_size_t)
Expand Down
Empty file.

0 comments on commit 18eda78

Please sign in to comment.