Skip to content

Commit

Permalink
chore: fix wheel builds
Browse files Browse the repository at this point in the history
This commit introduces a few fixes were uncovered by the previous
commit. Specifically:

- Explicitly test the FFI
- Cleanup the hatch build script and created a new
  `UnsupportedPlatformError` exception.
- Update the wheel inclusions.

Signed-off-by: JP-Ellis <[email protected]>
  • Loading branch information
JP-Ellis committed Oct 30, 2023
1 parent 9a5c975 commit e9e2ff6
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 43 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ concurrency:
env:
STABLE_PYTHON_VERSION: "3.11"
CIBW_BUILD_FRONTEND: build
CIBW_TEST_COMMAND: >
python -c
"from pact import EachLike;
assert EachLike(1).generate() == {'json_class': 'Pact::ArrayLike', 'contents': 1, 'min': 1}
"

jobs:
build-x86_64:
Expand Down
81 changes: 52 additions & 29 deletions hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@
PACT_LIB_URL = "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{version}/{prefix}pact_ffi-{os}-{machine}.{ext}"


class UnsupportedPlatformError(RuntimeError):
"""Raised when the current platform is not supported."""

def __init__(self, platform: str) -> None:
"""
Initialize the exception.
Args:
platform: The unsupported platform.
"""
self.platform = platform
super().__init__(f"Unsupported platform {platform}")


class PactBuildHook(BuildHookInterface[Any]):
"""Custom hook to download Pact binaries."""

Expand Down Expand Up @@ -66,8 +80,24 @@ def initialize(
build_data["infer_tag"] = True
build_data["pure_python"] = False

self.pact_bin_install(PACT_BIN_VERSION)
self.pact_lib_install(PACT_LIB_VERSION)
binaries_included = False
try:
self.pact_bin_install(PACT_BIN_VERSION)
binaries_included = True
except UnsupportedPlatformError as err:
msg = f"Pact binaries on not available for {err.platform}."
warnings.warn(msg, RuntimeWarning, stacklevel=2)

try:
self.pact_lib_install(PACT_LIB_VERSION)
binaries_included = True
except UnsupportedPlatformError as err:
msg = f"Pact library is not available for {err.platform}"
warnings.warn(msg, RuntimeWarning, stacklevel=2)

if not binaries_included:
msg = "Wheel does not include any binaries. Aborting."
raise UnsupportedPlatformError(msg)

def pact_bin_install(self, version: str) -> None:
"""
Expand All @@ -84,7 +114,7 @@ def pact_bin_install(self, version: str) -> None:
artifact = self._download(url)
self._pact_bin_extract(artifact)

def _pact_bin_url(self, version: str) -> str | None: # noqa: PLR0911
def _pact_bin_url(self, version: str) -> str | None:
"""
Generate the download URL for the Pact binaries.
Expand All @@ -109,9 +139,7 @@ def _pact_bin_url(self, version: str) -> str | None: # noqa: PLR0911
elif platform.endswith("x86_64"):
machine = "x86_64"
else:
msg = f"Unknown macOS machine {platform}"
warnings.warn(msg, RuntimeWarning, stacklevel=2)
return None
raise UnsupportedPlatformError(platform)
return PACT_BIN_URL.format(
version=version,
os=os,
Expand All @@ -127,36 +155,30 @@ def _pact_bin_url(self, version: str) -> str | None: # noqa: PLR0911
elif platform.endswith(("x86", "win32")):
machine = "x86"
else:
msg = f"Unknown Windows machine {platform}"
warnings.warn(msg, RuntimeWarning, stacklevel=2)
return None
raise UnsupportedPlatformError(platform)
return PACT_BIN_URL.format(
version=version,
os=os,
machine=machine,
ext="zip",
)

if "linux" in platform and "musl" not in platform:
if "manylinux" in platform:
os = "linux"
if platform.endswith("x86_64"):
machine = "x86_64"
elif platform.endswith("aarch64"):
machine = "arm64"
else:
msg = f"Unknown Linux machine {platform}"
warnings.warn(msg, RuntimeWarning, stacklevel=2)
return None
raise UnsupportedPlatformError(platform)
return PACT_BIN_URL.format(
version=version,
os=os,
machine=machine,
ext="tar.gz",
)

msg = f"Unknown platform {platform}"
warnings.warn(msg, RuntimeWarning, stacklevel=2)
return None
raise UnsupportedPlatformError(platform)

def _pact_bin_extract(self, artifact: Path) -> None:
"""
Expand Down Expand Up @@ -224,8 +246,7 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912
elif platform.endswith("x86_64"):
machine = "x86_64"
else:
msg = f"Unknown macOS machine {platform}"
raise ValueError(msg)
raise UnsupportedPlatformError(platform)
return PACT_LIB_URL.format(
prefix="lib",
version=version,
Expand All @@ -240,8 +261,7 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912
if platform.endswith("amd64"):
machine = "x86_64"
else:
msg = f"Unknown Windows machine {platform}"
raise ValueError(msg)
raise UnsupportedPlatformError(platform)
return PACT_LIB_URL.format(
prefix="",
version=version,
Expand All @@ -250,13 +270,12 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912
ext="lib.gz",
)

if "linux" in platform and "musl" in platform:
if "musllinux" in platform:
os = "linux"
if platform.endswith("x86_64"):
machine = "x86_64-musl"
else:
msg = f"Unknown MUSL Linux machine {platform}"
raise ValueError(msg)
raise UnsupportedPlatformError(platform)
return PACT_LIB_URL.format(
prefix="lib",
version=version,
Expand All @@ -265,15 +284,14 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912
ext="a.gz",
)

if "linux" in platform:
if "manylinux" in platform:
os = "linux"
if platform.endswith("x86_64"):
machine = "x86_64"
elif platform.endswith("aarch64"):
machine = "aarch64"
else:
msg = f"Unknown Linux machine {platform}"
raise ValueError(msg)
raise UnsupportedPlatformError(platform)

return PACT_LIB_URL.format(
prefix="lib",
Expand All @@ -283,8 +301,7 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912
ext="a.gz",
)

msg = f"Unknown platform {platform}"
raise ValueError(msg)
raise UnsupportedPlatformError(platform)

def _pact_lib_extract(self, artifact: Path) -> None:
"""
Expand All @@ -296,6 +313,9 @@ def _pact_lib_extract(self, artifact: Path) -> None:
Args:
artifact: The URL to download the Pact binaries from.
"""
# Pypy does not guarantee that the directory exists.
self.tmpdir.mkdir(parents=True, exist_ok=True)

if not str(artifact).endswith(".gz"):
msg = f"Unknown artifact type {artifact}"
raise ValueError(msg)
Expand All @@ -320,6 +340,9 @@ def _pact_lib_header(self, url: str) -> list[str]:
Args:
url: The URL pointing to the Pact library artifact.
"""
# Pypy does not guarantee that the directory exists.
self.tmpdir.mkdir(parents=True, exist_ok=True)

url = url.rsplit("/", 1)[0] + "/pact.h"
artifact = self._download(url)
includes: list[str] = []
Expand Down Expand Up @@ -387,7 +410,7 @@ def _pact_lib_cffi(self, includes: list[str]) -> None:
libraries=["pact_ffi", *extra_libs],
library_dirs=[str(self.tmpdir)],
)
output = ffibuilder.compile(verbose=True, tmpdir=str(self.tmpdir))
output = Path(ffibuilder.compile(verbose=True, tmpdir=str(self.tmpdir)))
shutil.copy(output, ROOT_DIR / "pact" / "v3")

def _download(self, url: str) -> Path:
Expand Down
38 changes: 29 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ requires-python = ">=3.8, <3.12"
# - A specific feature is required in a new minor release
# - A minor version address vulnerability which directly impacts Pact Python
dependencies = [
"cffi ~= 1.0",
"click ~= 8.0",
"fastapi ~= 0.0",
"psutil ~= 5.0",
Expand Down Expand Up @@ -92,12 +93,12 @@ build-backend = "hatchling.build"
path = "pact/__version__.py"

[tool.hatch.build]
include = ["pact/**/*.py", "*.md", "LICENSE"]
include = ["**/py.typed", "**/*.md", "LICENSE", "pact/**/*.py", "pact/**/*.pyi"]

[tool.hatch.build.targets.wheel]
# Ignore the data files in the wheel as their contents are already included
# in the package.
artifacts = ["pact/bin/*", "pact/lib/*"]
artifacts = ["pact/bin/*", "pact/lib/*", "pact/v3/_ffi.*"]

[tool.hatch.build.targets.wheel.hooks.custom]

Expand Down Expand Up @@ -155,9 +156,9 @@ filterwarnings = [
[tool.coverage.report]
exclude_lines = [
"if __name__ == .__main__.:", # Ignore non-runnable code
"if TYPE_CHECKING:", # Ignore typing
"raise NotImplementedError", # Ignore defensive assertions
"@(abc\\.)?abstractmethod", # Ignore abstract methods
"if TYPE_CHECKING:", # Ignore typing
"raise NotImplementedError", # Ignore defensive assertions
"@(abc\\.)?abstractmethod", # Ignore abstract methods
]

################################################################################
Expand All @@ -176,10 +177,7 @@ ignore = [
"ANN102", # `cls` must be typed
]

extend-exclude = [
"tests/*.py",
"pact/*.py",
]
extend-exclude = ["tests/*.py", "pact/*.py"]

[tool.ruff.pyupgrade]
keep-runtime-typing = true
Expand All @@ -201,3 +199,25 @@ extend-exclude = '^/(pact|tests)/(?!v3).+\.py$'

[tool.mypy]
exclude = '^(pact|tests)/(?!v3).+\.py$'

################################################################################
## CI Build Wheel
################################################################################
[tool.cibuildwheel]
test-command = """
python -c \
"from pact import EachLike; \
assert \
EachLike(1).generate() \
== {'json_class': 'Pact::ArrayLike', 'contents': 1, 'min': 1}; \
import pact.v3.ffi; \
assert isinstance(pact.v3.ffi.version(), str);\""""

[tool.cibuildwheel.macos]
# The repair tool unfortunately did not like the bundled Ruby distributable.
# TODO: Check whether delocate-wheel can be configured.
repair-wheel-command = ""

[tool.cibuildwheel.windows]
# Skipping pypy, see giampaolo/psutil#2325
skip = "pp*"

0 comments on commit e9e2ff6

Please sign in to comment.