pact-python-ffi 0.5.10.0__tar.gz → 0.5.12.0__tar.gz
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.
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/.gitignore +1 -0
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/PKG-INFO +1 -1
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/hatch_build.py +134 -118
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/pact_ffi/__init__.py +327 -259
- pact_python_ffi-0.5.12.0/pact_ffi/__version__.py +5 -0
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/pyproject.toml +12 -2
- pact_python_ffi-0.5.10.0/pact_ffi/__version__.py +0 -10
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/LICENSE +0 -0
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/README.md +0 -0
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/pact_ffi/ffi.pyi +0 -0
- {pact_python_ffi-0.5.10.0 → pact_python_ffi-0.5.12.0}/pact_ffi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pact-python-ffi
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.12.0
|
|
4
4
|
Summary: Python bindings for the Pact FFI library
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/pact-foundation/pact-python/issues
|
|
6
6
|
Project-URL: Changelog, https://github.com/pact-foundation/pact-python/blob/main/pact-python-ffi/CHANGELOG.md
|
|
@@ -7,13 +7,15 @@ CFFI bindings for it.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import hashlib
|
|
11
11
|
import os
|
|
12
12
|
import shutil
|
|
13
13
|
import subprocess
|
|
14
14
|
import sys
|
|
15
|
+
import tarfile
|
|
15
16
|
import tempfile
|
|
16
17
|
import urllib.request
|
|
18
|
+
import zipfile
|
|
17
19
|
from pathlib import Path
|
|
18
20
|
from typing import TYPE_CHECKING, Any
|
|
19
21
|
|
|
@@ -25,7 +27,7 @@ if TYPE_CHECKING:
|
|
|
25
27
|
from collections.abc import Mapping, MutableMapping, Sequence
|
|
26
28
|
|
|
27
29
|
PKG_DIR = Path(__file__).parent.resolve() / "src" / "pact_ffi"
|
|
28
|
-
|
|
30
|
+
PACT_RELEASE_URL = "https://github.com/pact-foundation/pact-reference/releases/download/pact_ffi-v{version}"
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class UnsupportedPlatformError(RuntimeError):
|
|
@@ -169,15 +171,30 @@ class PactBuildHook(BuildHookInterface[Any]):
|
|
|
169
171
|
`dst` is the corresponding path within the wheel.
|
|
170
172
|
"""
|
|
171
173
|
# Download the Pact library binary and header file
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
release_url = PACT_RELEASE_URL.format(version=version)
|
|
175
|
+
target = self._target()
|
|
176
|
+
header = self._download(f"{release_url}/pact.h")
|
|
177
|
+
if "windows" in target:
|
|
178
|
+
archive_url = f"{release_url}/pact_ffi-{target}.zip"
|
|
179
|
+
else:
|
|
180
|
+
archive_url = f"{release_url}/pact_ffi-{target}.tar.xz"
|
|
181
|
+
archive = self._download(archive_url)
|
|
182
|
+
self._verify(archive, self._download(f"{archive_url}.sha256"))
|
|
183
|
+
|
|
184
|
+
dll_lib: Path | None = None
|
|
185
|
+
if "windows" in target:
|
|
186
|
+
lib = self._extract_lib(archive, "pact_ffi.dll", "pact_ffi.dll")
|
|
187
|
+
# MSVC resolves `pact_ffi` to `pact_ffi.lib`. The archive's own
|
|
188
|
+
# `pact_ffi.lib` is the static library, so the import library
|
|
189
|
+
# takes its name.
|
|
190
|
+
dll_lib = self._extract_lib(archive, "pact_ffi.dll.lib", "pact_ffi.lib")
|
|
191
|
+
elif "darwin" in target:
|
|
192
|
+
lib = self._extract_lib(archive, "libpact_ffi.dylib", "libpact_ffi.dylib")
|
|
193
|
+
elif "musl" in target:
|
|
194
|
+
# MUSL uses static library
|
|
195
|
+
lib = self._extract_lib(archive, "libpact_ffi.a", "libpact_ffi.a")
|
|
179
196
|
else:
|
|
180
|
-
|
|
197
|
+
lib = self._extract_lib(archive, "libpact_ffi.so", "libpact_ffi.so")
|
|
181
198
|
|
|
182
199
|
# Compile the FFI extension
|
|
183
200
|
extension = self._compile(lib, header)
|
|
@@ -204,16 +221,12 @@ class PactBuildHook(BuildHookInterface[Any]):
|
|
|
204
221
|
|
|
205
222
|
return {str(extension): f"pact_ffi/{extension_dest}"}
|
|
206
223
|
|
|
207
|
-
def
|
|
224
|
+
def _target(self) -> str:
|
|
208
225
|
"""
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
version:
|
|
213
|
-
The upstream Pact version.
|
|
226
|
+
Determine the Rust target triple matching the wheel platform.
|
|
214
227
|
|
|
215
228
|
Returns:
|
|
216
|
-
The
|
|
229
|
+
The Rust target triple of the upstream Pact library archive.
|
|
217
230
|
|
|
218
231
|
Raises:
|
|
219
232
|
UnsupportedPlatformError:
|
|
@@ -221,83 +234,96 @@ class PactBuildHook(BuildHookInterface[Any]):
|
|
|
221
234
|
"""
|
|
222
235
|
wheel_platform = self._sys_tag_platform()
|
|
223
236
|
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
if wheel_platform.endswith(("_arm64", "_aarch64")):
|
|
238
|
+
arch = "aarch64"
|
|
239
|
+
elif wheel_platform.endswith(("_x86_64", "_amd64")):
|
|
240
|
+
arch = "x86_64"
|
|
241
|
+
else:
|
|
242
|
+
raise UnsupportedPlatformError(wheel_platform)
|
|
226
243
|
|
|
227
|
-
# Simplified platform and architecture detection
|
|
228
244
|
if wheel_platform.startswith("macosx"):
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
prefix = "lib"
|
|
242
|
-
suffix = "-musl"
|
|
243
|
-
if wheel_platform.endswith(aarch64):
|
|
244
|
-
platform = "aarch64"
|
|
245
|
-
elif wheel_platform.endswith(x86_64):
|
|
246
|
-
platform = "x86_64"
|
|
247
|
-
else:
|
|
248
|
-
raise UnsupportedPlatformError(wheel_platform)
|
|
249
|
-
|
|
250
|
-
elif wheel_platform.startswith("manylinux"):
|
|
251
|
-
os, ext = "linux", "so.gz"
|
|
252
|
-
prefix = "lib"
|
|
253
|
-
suffix = ""
|
|
254
|
-
if wheel_platform.endswith(aarch64):
|
|
255
|
-
platform = "aarch64"
|
|
256
|
-
elif wheel_platform.endswith(x86_64):
|
|
257
|
-
platform = "x86_64"
|
|
258
|
-
else:
|
|
259
|
-
raise UnsupportedPlatformError(wheel_platform)
|
|
260
|
-
|
|
261
|
-
elif wheel_platform.startswith("win"):
|
|
262
|
-
# TODO: Switch to using `dll.gz`
|
|
263
|
-
# https://github.com/python-cffi/cffi/issues/182
|
|
264
|
-
os, ext = "windows", "dll.gz"
|
|
265
|
-
prefix = ""
|
|
266
|
-
suffix = ""
|
|
267
|
-
if wheel_platform.endswith(aarch64):
|
|
268
|
-
platform = "aarch64"
|
|
269
|
-
elif wheel_platform.endswith(x86_64):
|
|
270
|
-
platform = "x86_64"
|
|
271
|
-
else:
|
|
272
|
-
raise UnsupportedPlatformError(wheel_platform)
|
|
245
|
+
return f"{arch}-apple-darwin"
|
|
246
|
+
if wheel_platform.startswith("musllinux"):
|
|
247
|
+
return f"{arch}-unknown-linux-musl"
|
|
248
|
+
if wheel_platform.startswith("manylinux"):
|
|
249
|
+
return f"{arch}-unknown-linux-gnu"
|
|
250
|
+
if wheel_platform.startswith("win"):
|
|
251
|
+
return f"{arch}-pc-windows-msvc"
|
|
252
|
+
raise UnsupportedPlatformError(wheel_platform)
|
|
253
|
+
|
|
254
|
+
def _verify(self, artefact: Path, checksum: Path) -> None:
|
|
255
|
+
"""
|
|
256
|
+
Verify an artefact against its SHA-256 checksum file.
|
|
273
257
|
|
|
274
|
-
|
|
275
|
-
|
|
258
|
+
A mismatched artefact is removed so that the next build downloads it
|
|
259
|
+
again.
|
|
276
260
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
)
|
|
261
|
+
Args:
|
|
262
|
+
artefact:
|
|
263
|
+
The path to the downloaded artefact.
|
|
264
|
+
|
|
265
|
+
checksum:
|
|
266
|
+
The path to the checksum file, in the `sha256sum` format
|
|
267
|
+
`<hex> *<filename>`.
|
|
285
268
|
|
|
286
|
-
|
|
269
|
+
Raises:
|
|
270
|
+
ValueError:
|
|
271
|
+
If the checksum does not match.
|
|
287
272
|
"""
|
|
288
|
-
|
|
273
|
+
expected = checksum.read_text().split()[0].lower()
|
|
274
|
+
digest = hashlib.sha256()
|
|
275
|
+
with artefact.open("rb") as f:
|
|
276
|
+
for chunk in iter(lambda: f.read(1 << 20), b""):
|
|
277
|
+
digest.update(chunk)
|
|
278
|
+
if digest.hexdigest() != expected:
|
|
279
|
+
artefact.unlink()
|
|
280
|
+
checksum.unlink()
|
|
281
|
+
msg = (
|
|
282
|
+
f"Checksum mismatch for {artefact.name}: "
|
|
283
|
+
f"expected {expected}, got {digest.hexdigest()}"
|
|
284
|
+
)
|
|
285
|
+
self.app.display_error(msg)
|
|
286
|
+
raise ValueError(msg)
|
|
287
|
+
self.app.display_debug(f"Verified checksum of {artefact.name}")
|
|
288
|
+
|
|
289
|
+
def _extract_lib(self, archive: Path, member: str, name: str) -> Path:
|
|
290
|
+
"""
|
|
291
|
+
Extract a file from the Pact library archive.
|
|
292
|
+
|
|
293
|
+
The `.tar.xz` archives nest their files under a `pact_ffi-<target>`
|
|
294
|
+
directory, while the `.zip` archives keep them at the root.
|
|
289
295
|
|
|
290
296
|
Args:
|
|
291
|
-
|
|
292
|
-
The
|
|
297
|
+
archive:
|
|
298
|
+
The path to the downloaded archive.
|
|
299
|
+
|
|
300
|
+
member:
|
|
301
|
+
The file name within the archive.
|
|
302
|
+
|
|
303
|
+
name:
|
|
304
|
+
The file name to extract to within the package directory.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
The path to the extracted file.
|
|
293
308
|
"""
|
|
294
|
-
target = PKG_DIR /
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
309
|
+
target = PKG_DIR / name
|
|
310
|
+
if archive.suffix == ".zip":
|
|
311
|
+
with (
|
|
312
|
+
zipfile.ZipFile(archive) as zf,
|
|
313
|
+
zf.open(member) as f_in,
|
|
314
|
+
target.open("wb") as f_out,
|
|
315
|
+
):
|
|
316
|
+
shutil.copyfileobj(f_in, f_out)
|
|
317
|
+
else:
|
|
318
|
+
prefix = archive.name.removesuffix(".tar.xz")
|
|
319
|
+
with tarfile.open(archive, "r:xz") as tf:
|
|
320
|
+
f_tar = tf.extractfile(f"{prefix}/{member}")
|
|
321
|
+
if f_tar is None:
|
|
322
|
+
msg = f"{member} in {archive.name} is not a regular file"
|
|
323
|
+
raise ValueError(msg)
|
|
324
|
+
with f_tar, target.open("wb") as f_out:
|
|
325
|
+
shutil.copyfileobj(f_tar, f_out)
|
|
326
|
+
self.app.display_debug(f"Extracted {member} from {archive.name} to {target}")
|
|
301
327
|
return target
|
|
302
328
|
|
|
303
329
|
def _compile(self, lib: Path, header: Path) -> Path:
|
|
@@ -386,46 +412,36 @@ class PactBuildHook(BuildHookInterface[Any]):
|
|
|
386
412
|
"""
|
|
387
413
|
Patch the upstream header so that CFFI can compile it.
|
|
388
414
|
|
|
389
|
-
The upstream `pact.h` emits the callback
|
|
390
|
-
`
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
https://github.com/pact-foundation/pact-reference/issues/544.
|
|
415
|
+
The upstream `pact.h` emits the callback parameter of
|
|
416
|
+
`pactffi_register_log_callback` as an opaque struct passed by value,
|
|
417
|
+
which cannot be compiled. The Rust signature is
|
|
418
|
+
`Option<extern "C" fn(...)>`, whose ABI is a nullable function pointer.
|
|
419
|
+
See https://github.com/pact-foundation/pact-reference/issues/544.
|
|
395
420
|
|
|
396
421
|
Args:
|
|
397
422
|
source:
|
|
398
423
|
The contents of the upstream `pact.h`.
|
|
399
424
|
|
|
400
425
|
Returns:
|
|
401
|
-
The header contents with the
|
|
402
|
-
|
|
426
|
+
The header contents with the callback declared as a function
|
|
427
|
+
pointer.
|
|
403
428
|
"""
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
"const char *test_run_id, "
|
|
421
|
-
"const char *level, "
|
|
422
|
-
"const char *target, "
|
|
423
|
-
"const char *message);",
|
|
424
|
-
).replace(
|
|
425
|
-
f"void {register}(struct Option_{callback} callback);",
|
|
426
|
-
f"void {register}({callback} callback);",
|
|
427
|
-
)
|
|
428
|
-
return source
|
|
429
|
+
opaque = "typedef struct Option_LogCallback Option_LogCallback;"
|
|
430
|
+
if opaque not in source:
|
|
431
|
+
return source
|
|
432
|
+
self.app.display_debug("Patching Option_LogCallback in pact.h")
|
|
433
|
+
return source.replace(
|
|
434
|
+
opaque,
|
|
435
|
+
"typedef void (*LogCallback)("
|
|
436
|
+
"const char *log_id, "
|
|
437
|
+
"const char *test_run_id, "
|
|
438
|
+
"const char *level, "
|
|
439
|
+
"const char *target, "
|
|
440
|
+
"const char *message);",
|
|
441
|
+
).replace(
|
|
442
|
+
"void pactffi_register_log_callback(struct Option_LogCallback callback);",
|
|
443
|
+
"void pactffi_register_log_callback(LogCallback callback);",
|
|
444
|
+
)
|
|
429
445
|
|
|
430
446
|
def _download(self, url: str) -> Path:
|
|
431
447
|
"""
|