python-can-hub 0.2.2__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- canhub/__init__.py +16 -0
- canhub/__main__.py +18 -0
- canhub/_native.py +128 -0
- canhub/bus.py +166 -0
- canhub/fingerprint.py +23 -0
- canhub/libcanhub.dll +0 -0
- python_can_hub-0.2.2.dist-info/METADATA +62 -0
- python_can_hub-0.2.2.dist-info/RECORD +11 -0
- python_can_hub-0.2.2.dist-info/WHEEL +5 -0
- python_can_hub-0.2.2.dist-info/entry_points.txt +2 -0
- python_can_hub-0.2.2.dist-info/top_level.txt +1 -0
canhub/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""python-can-hub: native python-can backend for can-hub.
|
|
2
|
+
|
|
3
|
+
The native library loads lazily: importing the package (e.g. for the pure
|
|
4
|
+
python fingerprint helper) must work without libcanhub present.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .fingerprint import identity_fingerprint
|
|
8
|
+
|
|
9
|
+
__all__ = ["CanHubBus", "identity_fingerprint"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def __getattr__(name):
|
|
13
|
+
if name == "CanHubBus":
|
|
14
|
+
from .bus import CanHubBus
|
|
15
|
+
return CanHubBus
|
|
16
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
canhub/__main__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""usage: python3 -m canhub fingerprint <certificate.pem>"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from .fingerprint import identity_fingerprint
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> int:
|
|
9
|
+
if len(sys.argv) != 3 or sys.argv[1] != "fingerprint":
|
|
10
|
+
print(__doc__, file=sys.stderr)
|
|
11
|
+
return 1
|
|
12
|
+
|
|
13
|
+
print(identity_fingerprint(sys.argv[2]))
|
|
14
|
+
return 0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
sys.exit(main())
|
canhub/_native.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""ctypes bindings over the libcanhub C ABI (include/canhub.h, version 1).
|
|
2
|
+
|
|
3
|
+
The shared library is resolved in order: the CANHUB_LIBRARY environment
|
|
4
|
+
variable, the copy bundled inside this package, the system library path.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ctypes
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from ctypes import POINTER, Structure, c_char, c_char_p, c_int32, c_size_t, c_uint8, c_uint32, c_uint64, c_void_p
|
|
11
|
+
|
|
12
|
+
FRAME_PAYLOAD_MAX = 64
|
|
13
|
+
AGENT_NAME_MAX = 128
|
|
14
|
+
INTERFACE_NAME_MAX = 16
|
|
15
|
+
FILTERS_MAX = 16
|
|
16
|
+
|
|
17
|
+
CAN_ID_MASK = 0x1FFFFFFF
|
|
18
|
+
CAN_ID_FLAG_ERR = 1 << 29
|
|
19
|
+
CAN_ID_FLAG_RTR = 1 << 30
|
|
20
|
+
CAN_ID_FLAG_EFF = 1 << 31
|
|
21
|
+
|
|
22
|
+
FRAME_FLAG_FD = 1 << 0
|
|
23
|
+
FRAME_FLAG_BRS = 1 << 1
|
|
24
|
+
|
|
25
|
+
OPEN_FLAG_NO_ECHO = 1 << 0
|
|
26
|
+
OPEN_FLAG_WRITE = 1 << 1
|
|
27
|
+
|
|
28
|
+
OK = 0
|
|
29
|
+
RECEIVED = 1
|
|
30
|
+
ERR_TIMEOUT = -1
|
|
31
|
+
ERR_DISCONNECTED = -2
|
|
32
|
+
ERR_NOT_FOUND = -3
|
|
33
|
+
ERR_OPEN_REJECTED = -4
|
|
34
|
+
ERR_WRITE_DENIED = -5
|
|
35
|
+
ERR_READ_DENIED = -6
|
|
36
|
+
ERR_ARGUMENT = -7
|
|
37
|
+
ERR_STATE = -8
|
|
38
|
+
ERR_TRANSPORT = -9
|
|
39
|
+
ERR_HUB = -10
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CanHubFrame(Structure):
|
|
43
|
+
_fields_ = [
|
|
44
|
+
("timestamp_us", c_uint64),
|
|
45
|
+
("can_id", c_uint32),
|
|
46
|
+
("flags", c_uint8),
|
|
47
|
+
("length", c_uint8),
|
|
48
|
+
("reserved", c_uint8 * 2),
|
|
49
|
+
("payload", c_uint8 * FRAME_PAYLOAD_MAX),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CanHubInterfaceInfo(Structure):
|
|
54
|
+
_fields_ = [
|
|
55
|
+
("interface_id", c_uint32),
|
|
56
|
+
("agent", c_char * AGENT_NAME_MAX),
|
|
57
|
+
("interface_name", c_char * INTERFACE_NAME_MAX),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CanHubFilter(Structure):
|
|
62
|
+
_fields_ = [
|
|
63
|
+
("can_id", c_uint32),
|
|
64
|
+
("can_mask", c_uint32),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CanHubConnectConfig(Structure):
|
|
69
|
+
_fields_ = [
|
|
70
|
+
("struct_size", c_uint32),
|
|
71
|
+
("url", c_char_p),
|
|
72
|
+
("state_directory", c_char_p),
|
|
73
|
+
("certificate_path", c_char_p),
|
|
74
|
+
("key_path", c_char_p),
|
|
75
|
+
("hub_fingerprint", c_char_p),
|
|
76
|
+
("connect_timeout_ms", c_int32),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_library():
|
|
81
|
+
bundled_name = "libcanhub.dll" if sys.platform == "win32" else "libcanhub.so"
|
|
82
|
+
|
|
83
|
+
override = os.environ.get("CANHUB_LIBRARY")
|
|
84
|
+
if override:
|
|
85
|
+
return ctypes.CDLL(override)
|
|
86
|
+
|
|
87
|
+
bundled = os.path.join(os.path.dirname(__file__), bundled_name)
|
|
88
|
+
if os.path.exists(bundled):
|
|
89
|
+
return ctypes.CDLL(bundled)
|
|
90
|
+
|
|
91
|
+
if sys.platform == "win32":
|
|
92
|
+
return ctypes.CDLL("libcanhub.dll")
|
|
93
|
+
return ctypes.CDLL("libcanhub.so.0")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
lib = _load_library()
|
|
97
|
+
|
|
98
|
+
lib.canhub_api_version.restype = c_uint32
|
|
99
|
+
lib.canhub_api_version.argtypes = []
|
|
100
|
+
|
|
101
|
+
lib.canhub_connect.restype = c_void_p
|
|
102
|
+
lib.canhub_connect.argtypes = [POINTER(CanHubConnectConfig)]
|
|
103
|
+
|
|
104
|
+
lib.canhub_close.restype = None
|
|
105
|
+
lib.canhub_close.argtypes = [c_void_p]
|
|
106
|
+
|
|
107
|
+
lib.canhub_last_error.restype = c_char_p
|
|
108
|
+
lib.canhub_last_error.argtypes = [c_void_p]
|
|
109
|
+
|
|
110
|
+
lib.canhub_list.restype = c_int32
|
|
111
|
+
lib.canhub_list.argtypes = [c_void_p, POINTER(CanHubInterfaceInfo), c_size_t, c_int32]
|
|
112
|
+
|
|
113
|
+
lib.canhub_open.restype = c_int32
|
|
114
|
+
lib.canhub_open.argtypes = [c_void_p, c_char_p, c_uint32, c_int32]
|
|
115
|
+
|
|
116
|
+
lib.canhub_set_filters.restype = c_int32
|
|
117
|
+
lib.canhub_set_filters.argtypes = [c_void_p, POINTER(CanHubFilter), c_uint8]
|
|
118
|
+
|
|
119
|
+
lib.canhub_recv.restype = c_int32
|
|
120
|
+
lib.canhub_recv.argtypes = [c_void_p, POINTER(CanHubFrame), c_int32]
|
|
121
|
+
|
|
122
|
+
lib.canhub_send.restype = c_int32
|
|
123
|
+
lib.canhub_send.argtypes = [c_void_p, POINTER(CanHubFrame)]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def last_error(session):
|
|
127
|
+
detail = lib.canhub_last_error(session)
|
|
128
|
+
return detail.decode("utf-8", errors="replace") if detail else "unknown error"
|
canhub/bus.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""python-can backend over libcanhub: can.Bus(interface="canhub", ...)."""
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
from typing import Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from can import BusABC, CanInitializationError, CanOperationError, Message
|
|
7
|
+
|
|
8
|
+
from . import _native as native
|
|
9
|
+
|
|
10
|
+
DEFAULT_TIMEOUT_MS = 5000
|
|
11
|
+
FINGERPRINT_HEX_LENGTH = 64
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _is_fingerprint(text: str) -> bool:
|
|
15
|
+
if len(text) != FINGERPRINT_HEX_LENGTH:
|
|
16
|
+
return False
|
|
17
|
+
return all(character in "0123456789abcdef" for character in text.lower())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CanHubBus(BusABC):
|
|
21
|
+
"""One hub interface as a python-can bus.
|
|
22
|
+
|
|
23
|
+
:param channel: namespaced interface name ``agent/iface`` or numeric id.
|
|
24
|
+
:param url: hub url (``quic://host:port``, ``tls://``, ``tcp://``,
|
|
25
|
+
``unix:///path``); ``None`` connects to the local hub unix socket.
|
|
26
|
+
:param identity_cert: path to the client certificate (PEM). Together
|
|
27
|
+
with ``identity_key`` it injects an explicit identity (the
|
|
28
|
+
fingerprint the hub ACLs refer to) instead of the state dir one.
|
|
29
|
+
:param identity_key: path to the client private key (PEM).
|
|
30
|
+
:param hub_fingerprint: expected hub fingerprint (64 hex). When given,
|
|
31
|
+
the connection is rejected unless the hub presents exactly this
|
|
32
|
+
certificate — no TOFU, no pin store on disk.
|
|
33
|
+
:param state_dir: directory holding the client TLS identity and the
|
|
34
|
+
TOFU pin store (tls/quic only); ``None`` uses the can-hub default.
|
|
35
|
+
:param receive_own_messages: also receive the frames this bus sends.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
channel: str,
|
|
41
|
+
url: Optional[str] = None,
|
|
42
|
+
identity_cert: Optional[str] = None,
|
|
43
|
+
identity_key: Optional[str] = None,
|
|
44
|
+
hub_fingerprint: Optional[str] = None,
|
|
45
|
+
state_dir: Optional[str] = None,
|
|
46
|
+
receive_own_messages: bool = False,
|
|
47
|
+
**kwargs,
|
|
48
|
+
):
|
|
49
|
+
self._session = None
|
|
50
|
+
self._writable = False
|
|
51
|
+
|
|
52
|
+
if hub_fingerprint is not None:
|
|
53
|
+
hub_fingerprint = str(hub_fingerprint)
|
|
54
|
+
if not _is_fingerprint(hub_fingerprint):
|
|
55
|
+
raise CanInitializationError(
|
|
56
|
+
"hub_fingerprint must be 64 hex characters (it may have been "
|
|
57
|
+
"mangled by python-can config value casting; pass it as a string)"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
config = native.CanHubConnectConfig()
|
|
61
|
+
config.struct_size = ctypes.sizeof(config)
|
|
62
|
+
config.url = url.encode() if url else None
|
|
63
|
+
config.state_directory = state_dir.encode() if state_dir else None
|
|
64
|
+
config.certificate_path = identity_cert.encode() if identity_cert else None
|
|
65
|
+
config.key_path = identity_key.encode() if identity_key else None
|
|
66
|
+
config.hub_fingerprint = hub_fingerprint.encode() if hub_fingerprint else None
|
|
67
|
+
config.connect_timeout_ms = DEFAULT_TIMEOUT_MS
|
|
68
|
+
|
|
69
|
+
self._session = native.lib.canhub_connect(ctypes.byref(config))
|
|
70
|
+
if not self._session:
|
|
71
|
+
raise CanInitializationError(f"could not connect to {url or 'the local can-hub socket'}")
|
|
72
|
+
|
|
73
|
+
self._open(str(channel), receive_own_messages)
|
|
74
|
+
self.channel_info = f"canhub {channel} via {url or 'unix socket'}"
|
|
75
|
+
super().__init__(channel=channel, **kwargs)
|
|
76
|
+
|
|
77
|
+
def _recv_internal(self, timeout: Optional[float]) -> Tuple[Optional[Message], bool]:
|
|
78
|
+
frame = native.CanHubFrame()
|
|
79
|
+
timeout_ms = -1 if timeout is None else max(0, int(timeout * 1000))
|
|
80
|
+
|
|
81
|
+
result = native.lib.canhub_recv(self._session, ctypes.byref(frame), timeout_ms)
|
|
82
|
+
if result == native.RECEIVED:
|
|
83
|
+
return self._to_message(frame), self._filters_applied
|
|
84
|
+
if result == native.ERR_TIMEOUT:
|
|
85
|
+
return None, self._filters_applied
|
|
86
|
+
|
|
87
|
+
raise CanOperationError(native.last_error(self._session))
|
|
88
|
+
|
|
89
|
+
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
|
|
90
|
+
frame = native.CanHubFrame()
|
|
91
|
+
|
|
92
|
+
if not self._writable:
|
|
93
|
+
raise CanOperationError("bus is read-only (write denied by the hub ACL)")
|
|
94
|
+
if msg.timestamp:
|
|
95
|
+
frame.timestamp_us = int(msg.timestamp * 1_000_000)
|
|
96
|
+
frame.can_id = msg.arbitration_id & native.CAN_ID_MASK
|
|
97
|
+
if msg.is_extended_id:
|
|
98
|
+
frame.can_id |= native.CAN_ID_FLAG_EFF
|
|
99
|
+
if msg.is_remote_frame:
|
|
100
|
+
frame.can_id |= native.CAN_ID_FLAG_RTR
|
|
101
|
+
if msg.is_error_frame:
|
|
102
|
+
frame.can_id |= native.CAN_ID_FLAG_ERR
|
|
103
|
+
if msg.is_fd:
|
|
104
|
+
frame.flags |= native.FRAME_FLAG_FD
|
|
105
|
+
if msg.bitrate_switch:
|
|
106
|
+
frame.flags |= native.FRAME_FLAG_BRS
|
|
107
|
+
data = bytes(msg.data or b"")
|
|
108
|
+
frame.length = len(data)
|
|
109
|
+
frame.payload[: len(data)] = data
|
|
110
|
+
|
|
111
|
+
result = native.lib.canhub_send(self._session, ctypes.byref(frame))
|
|
112
|
+
if result != native.OK:
|
|
113
|
+
raise CanOperationError(native.last_error(self._session))
|
|
114
|
+
|
|
115
|
+
def shutdown(self) -> None:
|
|
116
|
+
super().shutdown()
|
|
117
|
+
if self._session:
|
|
118
|
+
native.lib.canhub_close(self._session)
|
|
119
|
+
self._session = None
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def _filters_applied(self) -> bool:
|
|
123
|
+
return getattr(self, "_hardware_filtered", False)
|
|
124
|
+
|
|
125
|
+
def _apply_filters(self, filters) -> None:
|
|
126
|
+
if not filters or len(filters) > native.FILTERS_MAX:
|
|
127
|
+
self._hardware_filtered = False
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
native_filters = (native.CanHubFilter * len(filters))()
|
|
131
|
+
for slot, can_filter in zip(native_filters, filters):
|
|
132
|
+
slot.can_id = can_filter["can_id"]
|
|
133
|
+
slot.can_mask = can_filter["can_mask"]
|
|
134
|
+
result = native.lib.canhub_set_filters(self._session, native_filters, len(filters))
|
|
135
|
+
self._hardware_filtered = result == native.OK
|
|
136
|
+
|
|
137
|
+
def _open(self, channel: str, receive_own_messages: bool) -> None:
|
|
138
|
+
flags = native.OPEN_FLAG_WRITE
|
|
139
|
+
if not receive_own_messages:
|
|
140
|
+
flags |= native.OPEN_FLAG_NO_ECHO
|
|
141
|
+
|
|
142
|
+
result = native.lib.canhub_open(self._session, channel.encode(), flags, DEFAULT_TIMEOUT_MS)
|
|
143
|
+
if result == native.ERR_WRITE_DENIED:
|
|
144
|
+
result = native.lib.canhub_open(self._session, channel.encode(), flags & ~native.OPEN_FLAG_WRITE, DEFAULT_TIMEOUT_MS)
|
|
145
|
+
else:
|
|
146
|
+
self._writable = result == native.OK
|
|
147
|
+
|
|
148
|
+
if result != native.OK:
|
|
149
|
+
detail = native.last_error(self._session)
|
|
150
|
+
native.lib.canhub_close(self._session)
|
|
151
|
+
self._session = None
|
|
152
|
+
raise CanInitializationError(f"could not open {channel}: {detail}")
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _to_message(frame: native.CanHubFrame) -> Message:
|
|
156
|
+
return Message(
|
|
157
|
+
timestamp=frame.timestamp_us / 1_000_000,
|
|
158
|
+
arbitration_id=frame.can_id & native.CAN_ID_MASK,
|
|
159
|
+
is_extended_id=bool(frame.can_id & native.CAN_ID_FLAG_EFF),
|
|
160
|
+
is_remote_frame=bool(frame.can_id & native.CAN_ID_FLAG_RTR),
|
|
161
|
+
is_error_frame=bool(frame.can_id & native.CAN_ID_FLAG_ERR),
|
|
162
|
+
is_fd=bool(frame.flags & native.FRAME_FLAG_FD),
|
|
163
|
+
bitrate_switch=bool(frame.flags & native.FRAME_FLAG_BRS),
|
|
164
|
+
dlc=frame.length,
|
|
165
|
+
data=bytes(frame.payload[: frame.length]),
|
|
166
|
+
)
|
canhub/fingerprint.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""can-hub fingerprints without native code: sha256 over the certificate DER."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
_PEM_BODY = re.compile(
|
|
8
|
+
r"-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----",
|
|
9
|
+
re.DOTALL,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def identity_fingerprint(certificate_path: str) -> str:
|
|
14
|
+
"""Fingerprint of a PEM certificate: what the hub pins and the ACLs key on."""
|
|
15
|
+
with open(certificate_path, "r", encoding="ascii") as certificate_file:
|
|
16
|
+
pem = certificate_file.read()
|
|
17
|
+
|
|
18
|
+
match = _PEM_BODY.search(pem)
|
|
19
|
+
if match is None:
|
|
20
|
+
raise ValueError(f"{certificate_path} does not contain a PEM certificate")
|
|
21
|
+
|
|
22
|
+
der = base64.b64decode(match.group(1))
|
|
23
|
+
return hashlib.sha256(der).hexdigest()
|
canhub/libcanhub.dll
ADDED
|
Binary file
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-can-hub
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: python-can backend for can-hub: remote CAN interfaces over unix/tcp/tls/quic
|
|
5
|
+
License: AGPL-3.0-only
|
|
6
|
+
Project-URL: Homepage, https://github.com/can-hub-io/can-hub
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: python-can >=4.0
|
|
10
|
+
|
|
11
|
+
# python-can-hub
|
|
12
|
+
|
|
13
|
+
Native [python-can](https://python-can.readthedocs.io) backend for
|
|
14
|
+
[can-hub](https://github.com/can-hub-io/can-hub): consume remote CAN interfaces
|
|
15
|
+
exported by can-hub agents, directly over the binary protocol — unix socket,
|
|
16
|
+
plain TCP, TLS or QUIC, with mTLS identity and TOFU pinning on the encrypted
|
|
17
|
+
transports. No bridge process in between.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import can
|
|
21
|
+
|
|
22
|
+
bus = can.Bus(
|
|
23
|
+
interface="canhub",
|
|
24
|
+
channel="truck42/can0",
|
|
25
|
+
url="quic://hub.example.com:7227",
|
|
26
|
+
)
|
|
27
|
+
bus.send(can.Message(arbitration_id=0x123, data=b"\xDE\xAD\xBE\xEF"))
|
|
28
|
+
for message in bus:
|
|
29
|
+
print(message)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- `channel`: namespaced interface `agent/iface` (or the numeric id from
|
|
33
|
+
`can-hub-client list`).
|
|
34
|
+
- `url`: omit to connect to the local hub unix socket.
|
|
35
|
+
- `state_dir`: client TLS identity + pin store location (tls/quic).
|
|
36
|
+
- `receive_own_messages`: standard python-can echo semantics.
|
|
37
|
+
|
|
38
|
+
Write access follows the hub client ACLs: if the ACL grants read-only, the
|
|
39
|
+
bus opens read-only and `send()` raises.
|
|
40
|
+
|
|
41
|
+
The wheel bundles `libcanhub.so` with the TLS/QUIC stack linked in
|
|
42
|
+
statically; the only runtime dependency is glibc.
|
|
43
|
+
|
|
44
|
+
## Building from source
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
./scripts/build-python-wheel.sh # host arch, glibc-tagged (local dev)
|
|
48
|
+
pip install python/dist/*.whl
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Distributable manylinux wheels are built per architecture in a manylinux
|
|
52
|
+
container and repaired by auditwheel (needs docker, plus QEMU binfmt for the
|
|
53
|
+
cross arches):
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
./scripts/build-python-wheel.sh x86_64 # python/dist/x86_64/*.whl
|
|
57
|
+
./scripts/build-python-wheel.sh aarch64
|
|
58
|
+
./scripts/build-python-wheel.sh armv7l
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The release workflow builds all three and publishes them to PyPI on a `v*`
|
|
62
|
+
tag.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
canhub/__init__.py,sha256=yC7SFSz4vUDFgve7gehAaOSqZ1Iv1Kbv1A4c5nj_XcM,485
|
|
2
|
+
canhub/__main__.py,sha256=WM0VMTSjzySENh7zkbTT4WpLnqyS_gsZBgJ4f05qNHQ,366
|
|
3
|
+
canhub/_native.py,sha256=4MHqnxR86NfMN_4jrp5rg60MR1hfhD7p-zXozSoAEAg,3295
|
|
4
|
+
canhub/bus.py,sha256=dLwIGXdGplCyG_vh0Rb9DmM5kUjGCMyV_OlMU10q4x4,7060
|
|
5
|
+
canhub/fingerprint.py,sha256=N38dP3_odFYn6821XViWG6aQKUPhxJHDY8xE8uW8zNo,711
|
|
6
|
+
canhub/libcanhub.dll,sha256=BFcvsPyRdRgD2wiGAAlHZEDRXTqHQlHwjlTkDaxu19k,6396928
|
|
7
|
+
python_can_hub-0.2.2.dist-info/METADATA,sha256=MaxFh9oRfrcQ60f7_UfbYdbz6Ng3RarPS0Om8fDaWVs,1998
|
|
8
|
+
python_can_hub-0.2.2.dist-info/WHEEL,sha256=KTdQDMVZqs2eeRhOpF4kInPW2OgLgRWl7KhVZQueA2o,99
|
|
9
|
+
python_can_hub-0.2.2.dist-info/entry_points.txt,sha256=ZfPo9cCR4xH9BjYRCi_nOcw_48SN6vqboV0B2y8HcjU,46
|
|
10
|
+
python_can_hub-0.2.2.dist-info/top_level.txt,sha256=nPyUT2Mw-NsKrJhpH-4EUcBBqz7GOSwfxnGkFEILnKk,7
|
|
11
|
+
python_can_hub-0.2.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
canhub
|