diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db0fb09cbe..defa3b8851 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - v* branches: - master + - debug/* pull_request: branches: - master @@ -18,126 +19,18 @@ env: STABLE_PYTHON_VERSION: "3.12" HATCH_VERBOSE: "1" FORCE_COLOR: "1" - CIBW_BUILD_FRONTEND: build + CIBW_BUILD_FRONTEND: pip jobs: - build-sdist: - name: Build source distribution - - if: github.event_name == 'push' || ! github.event.pull_request.draft - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - # Fetch all tags - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ env.STABLE_PYTHON_VERSION }} - cache: pip - - - name: Install hatch - run: pip install --upgrade hatch - - - name: Create source distribution - run: | - hatch build --target sdist - - - name: Upload sdist - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4 - with: - name: wheels-sdist - path: ./dist/*.tar.* - if-no-files-found: error - compression-level: 0 - - build-x86_64: - name: Build wheels on ${{ matrix.os }} (x86, 64-bit) - - if: github.event_name == 'push' || ! github.event.pull_request.draft - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-20.04 - archs: x86_64 - - os: macos-12 - archs: x86_64 - - os: windows-2019 - archs: AMD64 - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - # Fetch all tags - fetch-depth: 0 - - - name: Cache pip packages - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4 - with: - path: ~/.cache/pip - key: ${{ github.workflow }}-pip-${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ github.workflow }}-pip-${{ runner.os }} - ${{ github.workflow }}-pip - ${{ github.workflow }} - - - name: Filter targets - id: cibw-filter - shell: bash - # Building all wheels on PRs is too slow, so we filter them to target - # the latest stable version of Python. - run: | - if [[ "${{ github.event_name}}" == "pull_request" ]] ; then - echo "build=cp${STABLE_PYTHON_VERSION/./}-*" >> "$GITHUB_OUTPUT" - else - echo "build=*" >> "$GITHUB_OUTPUT" - fi - - - name: Set macOS deployment target - if: startsWith(matrix.os, 'macos-') - run: | - echo "MACOSX_DEPLOYMENT_TARGET=10.12" >> "$GITHUB_ENV" - - - name: Create wheels - uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 - env: - CIBW_ARCHS: ${{ matrix.archs }} - CIBW_BUILD: ${{ steps.cibw-filter.outputs.build }} - - - name: Upload wheels - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4 - with: - name: wheels-${{ matrix.os }}-${{ matrix.archs }} - path: ./wheelhouse/*.whl - if-no-files-found: error - compression-level: 0 - build-arm64: name: Build wheels on ${{ matrix.os }} (arm64) # As this requires emulation, it's not worth running on PRs or master - if: >- - github.event_name == 'push' && - startsWith(github.event.ref, 'refs/tags') runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - - os: ubuntu-20.04 - archs: aarch64 - build: manylinux - - os: ubuntu-20.04 - archs: aarch64 - build: musllinux - - os: macos-14 - archs: arm64 - build: "" - os: windows-2019 archs: ARM64 build: "" @@ -169,6 +62,7 @@ jobs: env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build == '' && '*' || format('*{0}*', matrix.build) }} + DEBUG: "1" - name: Upload wheels uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4 @@ -177,84 +71,3 @@ jobs: path: ./wheelhouse/*.whl if-no-files-found: error compression-level: 0 - - publish: - name: Publish wheels and sdist - - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') - runs-on: ubuntu-20.04 - environment: pypi - - needs: - - build-sdist - - build-x86_64 - - build-arm64 - - permissions: - # Required for trusted publishing - id-token: write - # Required for release creation - contents: write - - steps: - - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - # Fetch all tags - fetch-depth: 0 - - - name: Download wheels and sdist - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 - with: - path: wheels - merge-multiple: true - - - name: Setup Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ env.STABLE_PYTHON_VERSION }} - - - name: Update changelog - id: changelog - run: | - pip install --upgrade commitizen - - cz changelog \ - --incremental \ - --template .github/CHANGELOG.md.j2 \ - --dry-run \ - | tail -n+2 \ - > ${{ runner.temp }}/changelog - echo -e "\n\n## Pull Requests\n\n" >> ${{ runner.temp }}/changelog - - cz changelog \ - --incremental \ - --template .github/CHANGELOG.md.j2 - - - name: Generate release - id: release - uses: softprops/action-gh-release@fb2d03176f42a1f0dd433ca263f314051d3edd44 # v2 - with: - files: wheels/* - body_path: ${{ runner.temp }}/changelog - draft: false - prerelease: false - generate_release_notes: true - - - name: Push build artifacts to PyPI - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 - with: - skip-existing: true - password: ${{ secrets.PYPI_TOKEN }} - packages-dir: wheels - - - name: Create PR for changelog update - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 - with: - token: ${{ secrets.GH_TOKEN }} - commit-message: "chore: update changelog ${{ github.ref_name }}" - title: "chore: update changelog" - body: | - This PR updates the changelog for ${{ github.ref_name }}. - branch: chore/update-changelog - base: master diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 3a93be4111..0000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: docs - -on: - push: - tags: - - v* - branches: - - master - pull_request: - branches: - - master - -env: - STABLE_PYTHON_VERSION: "3.12" - FORCE_COLOR: "1" - HATCH_VERBOSE: "1" - -jobs: - build: - name: Build docs - - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ env.STABLE_PYTHON_VERSION }} - cache: pip - - - name: Install Hatch - run: pip install --upgrade hatch - - - name: Build docs - run: | - hatch run mkdocs build - - - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 - with: - path: site - - publish: - name: Publish docs - - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') - - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 69eb579a00..0000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,224 +0,0 @@ -name: test - -on: - push: - branches: - - master - pull_request: - branches: - - master - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -env: - STABLE_PYTHON_VERSION: "3.12" - PYTEST_ADDOPTS: --color=yes - HATCH_VERBOSE: "1" - FORCE_COLOR: "1" - -jobs: - test-container: - name: >- - Tests py${{ matrix.python-version }} on ${{ matrix.os }} - - runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} - - services: - broker: - image: pactfoundation/pact-broker:latest@sha256:fc44f0a6e731c7de78bf44923b9ab41e5d3351388bfcf2f648e72844041cf74d - ports: - - "9292:9292" - env: - # Basic auth credentials for the Broker - PACT_BROKER_ALLOW_PUBLIC_READ: "true" - PACT_BROKER_BASIC_AUTH_USERNAME: pactbroker - PACT_BROKER_BASIC_AUTH_PASSWORD: pactbroker - # Database - PACT_BROKER_DATABASE_URL: sqlite:////tmp/pact_broker.sqlite - - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - experimental: [false] - include: - - # Run tests against the next Python version, but no need for the full list of OSes. - os: ubuntu-latest - python-version: "3.13.0-alpha.0 - 3.13" - experimental: true - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - submodules: true - - - name: Apply temporary definitions update - shell: bash - run: | - cd tests/v3/compatibility_suite - patch -p1 -d definition < definition-update.diff - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - - name: Install Hatch - run: pip install --upgrade hatch - - - name: Ensure broker is live - run: | - i=0 - until curl -sSf http://localhost:9292/diagnostic/status/heartbeat; do - i=$((i+1)) - if [ $i -gt 120 ]; then - echo "Broker failed to start" - exit 1 - fi - sleep 1 - done - - - name: Run tests - run: hatch run test --broker-url=http://pactbroker:pactbroker@localhost:9292 --container - - - name: Upload coverage - # TODO: Configure code coverage monitoring - if: false && matrix.python-version == env.STABLE_PYTHON_VERSION && matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - test-no-container: - name: >- - Tests py${{ matrix.python-version }} on ${{ matrix.os }} - - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [windows-latest, macos-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - # Python 3.8 and 3.9 aren't supported on macos-latest (ARM) - exclude: - - os: macos-latest - python-version: "3.8" - - os: macos-latest - python-version: "3.9" - include: - - os: macos-13 - python-version: "3.8" - - os: macos-13 - python-version: "3.9" - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - submodules: true - - - name: Apply temporary definitions update - shell: bash - run: | - cd tests/v3/compatibility_suite - patch -p1 -d definition < definition-update.diff - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - - name: Install Hatch - run: pip install --upgrade hatch - - - name: Run tests - run: hatch run test - - test-conlusion: - name: Test matrix complete - - runs-on: ubuntu-latest - needs: - - test-container - - test-no-container - - steps: - - run: echo "Test matrix completed successfully." - - example: - name: Example - - runs-on: ubuntu-latest - if: github.event_name == 'push' || ! github.event.pull_request.draft - - services: - broker: - image: pactfoundation/pact-broker:latest@sha256:fc44f0a6e731c7de78bf44923b9ab41e5d3351388bfcf2f648e72844041cf74d - ports: - - "9292:9292" - env: - # Basic auth credentials for the Broker - PACT_BROKER_ALLOW_PUBLIC_READ: "true" - PACT_BROKER_BASIC_AUTH_USERNAME: pactbroker - PACT_BROKER_BASIC_AUTH_PASSWORD: pactbroker - # Database - PACT_BROKER_DATABASE_URL: sqlite:////tmp/pact_broker.sqlite - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - - name: Set up Python 3 - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ env.STABLE_PYTHON_VERSION }} - cache: pip - - - name: Install Hatch - run: pip install --upgrade hatch - - - name: Ensure broker is live - run: | - i=0 - until curl -sSf http://localhost:9292/diagnostic/status/heartbeat; do - i=$((i+1)) - if [ $i -gt 120 ]; then - echo "Broker failed to start" - exit 1 - fi - sleep 1 - done - - - name: Examples - run: > - hatch run example --broker-url=http://pactbroker:pactbroker@localhost:9292 - - lint: - name: Lint - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - - name: Set up Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 - with: - python-version: ${{ env.STABLE_PYTHON_VERSION }} - cache: pip - - - name: Install Hatch - run: pip install --upgrade hatch - - - name: Lint - run: hatch run lint - - - name: Typecheck - run: hatch run typecheck - - - name: Format - run: hatch run format diff --git a/hatch_build.py b/hatch_build.py index dcb65ec1c2..b98eadec4f 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -13,20 +13,35 @@ from __future__ import annotations import gzip +import logging import os import shutil +import sysconfig import tarfile import tempfile import warnings import zipfile from pathlib import Path -from typing import Any, Dict +from typing import TYPE_CHECKING, Any, Dict import cffi import requests from hatchling.builders.hooks.plugin.interface import BuildHookInterface from packaging.tags import sys_tags +if TYPE_CHECKING: + from hatchling.bridge.app import Application + from hatchling.builders.config import BuilderConfig + from hatchling.metadata.core import ProjectMetadata + +logger = logging.getLogger(__name__) + +logging.basicConfig( + level=logging.DEBUG if os.getenv("DEBUG") else logging.INFO, + format="%(asctime)s.%(msecs)03d [%(levelname)-8s] %(name)s: %(message)s", + datefmt="%H:%M:%S", +) + PACT_ROOT_DIR = Path(__file__).parent.resolve() / "src" / "pact" # Latest version available at: @@ -59,25 +74,44 @@ class PactBuildHook(BuildHookInterface[Any]): PLUGIN_NAME = "custom" - def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 + def __init__( # noqa: PLR0913 + self, + root: str, + config: dict[str, Any], + build_config: BuilderConfig, + metadata: ProjectMetadata[Any], + directory: str, + target_name: str, + app: Application | None = None, + ) -> None: """ Initialize the build hook. For this hook, we additionally define the lib extension based on the current platform. """ - super().__init__(*args, **kwargs) + super().__init__( + root=root, + config=config, + build_config=build_config, + metadata=metadata, + directory=directory, + target_name=target_name, + app=app, + ) self.tmpdir = Path(tempfile.TemporaryDirectory().name) self.tmpdir.mkdir(parents=True, exist_ok=True) def clean(self, versions: list[str]) -> None: # noqa: ARG002 """Clean up any files created by the build hook.""" for subdir in ["bin", "lib", "data"]: + logger.debug("Removing directory %s", PACT_ROOT_DIR / subdir) shutil.rmtree(PACT_ROOT_DIR / subdir, ignore_errors=True) for ffi in (PACT_ROOT_DIR / "v3").glob("_ffi.*"): if ffi.name == "_ffi.pyi": continue + logger.debug("Removing file %s", ffi) ffi.unlink() def initialize( @@ -92,6 +126,7 @@ def initialize( binaries_included = False try: self.pact_bin_install(PACT_BIN_VERSION) + logger.info("Pact CLI binaries installed.") binaries_included = True except UnsupportedPlatformError as err: msg = f"Pact binaries on not available for {err.platform}." @@ -99,6 +134,7 @@ def initialize( try: self.pact_lib_install(PACT_LIB_VERSION) + logger.info("Pact library installed.") binaries_included = True except UnsupportedPlatformError as err: msg = f"Pact library is not available for {err.platform}" @@ -136,59 +172,116 @@ def _pact_bin_url(self, version: str) -> str | None: version: The upstream Pact version. Returns: - The URL to download the Pact binaries from, or None if the current - platform is not supported. + The URL to download the Pact binaries from. + + Raises: + UnsupportedPlatformError: If the platform is not supported. """ platform = next(sys_tags()).platform + logger.debug("Detected platform: %s", platform) + logger.debug("Sysconfig platform: %s", sysconfig.get_platform()) + logger.debug("Self vars:") + for k, v in vars(self).items(): + logger.debug(" %s: %s", k, v) if platform.startswith("macosx"): - os = "osx" - if platform.endswith("arm64"): - machine = "arm64" - elif platform.endswith("x86_64"): - machine = "x86_64" - else: - raise UnsupportedPlatformError(platform) - return PACT_BIN_URL.format( - version=version, - os=os, - machine=machine, - ext="tar.gz", - ) - + return self._pact_bin_url_macos(version, platform) if platform.startswith("win"): - os = "windows" - - if platform.endswith("amd64"): - machine = "x86_64" - elif platform.endswith(("x86", "win32")): - machine = "x86" - else: - raise UnsupportedPlatformError(platform) - return PACT_BIN_URL.format( - version=version, - os=os, - machine=machine, - ext="zip", - ) - - if "manylinux" in platform: - os = "linux" - if platform.endswith("x86_64"): - machine = "x86_64" - elif platform.endswith("aarch64"): - machine = "arm64" - else: - raise UnsupportedPlatformError(platform) - return PACT_BIN_URL.format( - version=version, - os=os, - machine=machine, - ext="tar.gz", - ) - + return self._pact_bin_url_windows(version, platform) + if "linux" in platform: + return self._pact_bin_url_linux(version, platform) raise UnsupportedPlatformError(platform) + def _pact_bin_url_macos(self, version: str, platform: str) -> str | None: + """ + Generate the download URL for the macOS Pact binaries. + + Args: + version: The upstream Pact version. + platform: The macOS platform string. + + Returns: + The URL to download the Pact binaries from. + + Raises: + UnsupportedPlatformError: If the platform is not supported. + """ + os = "osx" + + if platform.endswith("arm64"): + machine = "arm64" + elif platform.endswith("x86_64"): + machine = "x86_64" + else: + raise UnsupportedPlatformError(platform) + + return PACT_BIN_URL.format( + version=version, + os=os, + machine=machine, + ext="tar.gz", + ) + + def _pact_bin_url_windows(self, version: str, platform: str) -> str | None: + """ + Generate the download URL for the Windows Pact binaries. + + Args: + version: The upstream Pact version. + platform: The Windows platform string. + + Returns: + The URL to download the Pact binaries from. + + Raises: + UnsupportedPlatformError: If the platform is not supported. + """ + os = "windows" + + if platform.endswith("amd64"): + machine = "x86_64" + elif platform.endswith(("x86", "win32")): + machine = "x86" + else: + raise UnsupportedPlatformError(platform) + + return PACT_BIN_URL.format( + version=version, + os=os, + machine=machine, + ext="zip", + ) + + def _pact_bin_url_linux(self, version: str, platform: str) -> str | None: + """ + Generate the download URL for the Linux Pact binaries. + + Args: + version: The upstream Pact version. + platform: The Linux platform string. + + Returns: + The URL to download the Pact binaries from. + + Raises: + UnsupportedPlatformError: If the platform is not supported. + """ + os = "linux" + + if platform.endswith("x86_64"): + machine = "x86_64" + elif platform.endswith("aarch64"): + machine = "arm64" + else: + raise UnsupportedPlatformError(platform) + + return PACT_BIN_URL.format( + version=version, + os=os, + machine=machine, + ext="tar.gz", + ) + def _pact_bin_extract(self, artifact: Path) -> None: """ Extract the Pact binaries. @@ -199,16 +292,18 @@ def _pact_bin_extract(self, artifact: Path) -> None: Args: artifact: The path to the downloaded artifact. """ + logger.debug("Extracting %s", artifact) with tempfile.TemporaryDirectory() as tmpdir: - if str(artifact).endswith(".zip"): + if artifact.suffixes[-1] == ".zip": with zipfile.ZipFile(artifact) as f: f.extractall(tmpdir) # noqa: S202 - if str(artifact).endswith(".tar.gz"): + if artifact.suffixes[-2:] == [".tar", ".gz"]: with tarfile.open(artifact) as f: f.extractall(tmpdir) # noqa: S202 for d in ["bin", "lib"]: + logger.debug("Copying extracted %s dir to %s", d, PACT_ROOT_DIR / d) if (PACT_ROOT_DIR / d).is_dir(): shutil.rmtree(PACT_ROOT_DIR / d) shutil.copytree( @@ -232,7 +327,7 @@ def pact_lib_install(self, version: str) -> None: includes = self._pact_lib_header(url) self._pact_lib_cffi(includes) - def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912 + def _pact_lib_url(self, version: str) -> str: """ Generate the download URL for the Pact library. @@ -248,79 +343,120 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912 The URL to download the Pact library from. Raises: - ValueError: + UnsupportedPlatformError: If the current platform is not supported. """ platform = next(sys_tags()).platform + logger.debug("Detected platform: %s", platform) if platform.startswith("macosx"): - os = "macos" - if platform.endswith("arm64"): - machine = "aarch64" - elif platform.endswith("x86_64"): - machine = "x86_64" - else: - raise UnsupportedPlatformError(platform) - return PACT_LIB_URL.format( - prefix="lib", - version=version, - os=os, - machine=machine, - ext="a.gz", - ) - + return self._pact_lib_url_macos(version, platform) if platform.startswith("win"): - os = "windows" - - if platform.endswith("amd64"): - machine = "x86_64" - elif platform.endswith(("arm64", "aarch64")): - machine = "aarch64" - else: - raise UnsupportedPlatformError(platform) - return PACT_LIB_URL.format( - prefix="", - version=version, - os=os, - machine=machine, - ext="lib.gz", - ) - - if "musllinux" in platform: - os = "linux" - if platform.endswith("x86_64"): - machine = "x86_64-musl" - elif platform.endswith("aarch64"): - machine = "aarch64-musl" - else: - raise UnsupportedPlatformError(platform) - return PACT_LIB_URL.format( - prefix="lib", - version=version, - os=os, - machine=machine, - ext="a.gz", - ) - - if "manylinux" in platform: - os = "linux" - if platform.endswith("x86_64"): - machine = "x86_64" - elif platform.endswith("aarch64"): - machine = "aarch64" - else: - raise UnsupportedPlatformError(platform) - - return PACT_LIB_URL.format( - prefix="lib", - version=version, - os=os, - machine=machine, - ext="a.gz", - ) + return self._pact_lib_url_windows(version, platform) + if "linux" in platform: + return self._pact_lib_url_linux(version, platform) raise UnsupportedPlatformError(platform) + def _pact_lib_url_macos(self, version: str, platform: str) -> str: + """ + Generate the download URL for the macOS Pact library. + + Args: + version: The upstream Pact version. + platform: The macOS platform string. + + Returns: + The URL to download the Pact library from. + + Raises: + UnsupportedPlatformError: + If the current platform is not supported. + """ + os = "macos" + + if platform.endswith("arm64"): + machine = "aarch64" + elif platform.endswith("x86_64"): + machine = "x86_64" + else: + raise UnsupportedPlatformError(platform) + + return PACT_LIB_URL.format( + prefix="lib", + version=version, + os=os, + machine=machine, + ext="a.gz", + ) + + def _pact_lib_url_windows(self, version: str, platform: str) -> str: + """ + Generate the download URL for the Windows Pact library. + + Args: + version: The upstream Pact version. + platform: The Windows platform string. + + Returns: + The URL to download the Pact library from. + + Raises: + UnsupportedPlatformError: + If the current platform is not supported. + """ + os = "windows" + + if platform.endswith("amd64"): + machine = "x86_64" + elif platform.endswith(("arm64", "aarch64")): + machine = "aarch64" + else: + raise UnsupportedPlatformError(platform) + + return PACT_LIB_URL.format( + prefix="", + version=version, + os=os, + machine=machine, + ext="lib.gz", + ) + + def _pact_lib_url_linux(self, version: str, platform: str) -> str: + """ + Generate the download URL for the Linux Pact library. + + Args: + version: The upstream Pact version. + platform: The Linux platform string. + + Returns: + The URL to download the Pact library from. + + Raises: + UnsupportedPlatformError: + If the current platform is not supported. + """ + os = "linux" + + if platform.endswith("x86_64"): + machine = "x86_64" + elif platform.endswith("aarch64"): + machine = "aarch64" + else: + raise UnsupportedPlatformError(platform) + + if "musl" in platform: + machine = f"{machine}-musl" + + return PACT_LIB_URL.format( + prefix="lib", + version=version, + os=os, + machine=machine, + ext="a.gz", + ) + def _pact_lib_extract(self, artifact: Path) -> None: """ Extract the Pact library. @@ -334,13 +470,13 @@ def _pact_lib_extract(self, artifact: Path) -> None: # Pypy does not guarantee that the directory exists. self.tmpdir.mkdir(parents=True, exist_ok=True) - if not str(artifact).endswith(".gz"): + if artifact.suffixes[-1] != ".gz": msg = f"Unknown artifact type {artifact}" raise ValueError(msg) - with gzip.open(artifact, "rb") as f_in, ( - self.tmpdir / (artifact.name.split("-")[0] + artifact.suffixes[0]) - ).open("wb") as f_out: + destination = self.tmpdir / (artifact.name.split("-")[0] + artifact.suffixes[0]) + logger.debug("Decompressing %s to %s", artifact, destination) + with gzip.open(artifact, "rb") as f_in, destination.open("wb") as f_out: shutil.copyfileobj(f_in, f_out) def _pact_lib_header(self, url: str) -> list[str]: @@ -363,6 +499,8 @@ def _pact_lib_header(self, url: str) -> list[str]: url = url.rsplit("/", 1)[0] + "/pact.h" artifact = self._download(url) + + logger.debug("Processing %s", artifact) includes: list[str] = [] with artifact.open("r", encoding="utf-8") as f_in, ( self.tmpdir / "pact.h" @@ -393,6 +531,7 @@ def _pact_lib_cffi(self, includes: list[str]) -> None: A list of additional `#include` statements to include in the generated bindings. """ + logger.debug("Compiling binary extension") if os.name == "nt": extra_libs = [ "advapi32", @@ -415,6 +554,7 @@ def _pact_lib_cffi(self, includes: list[str]) -> None: ] else: extra_libs = [] + logger.debug("Extra libraries: %s", extra_libs) ffibuilder = cffi.FFI() with (self.tmpdir / "pact.h").open( @@ -447,6 +587,7 @@ def _download(self, url: str) -> Path: filename = url.split("/")[-1] artifact = PACT_ROOT_DIR / "data" / filename artifact.parent.mkdir(parents=True, exist_ok=True) + logger.debug("Downloading %s to %s", url, artifact) if not artifact.exists(): response = requests.get(url, timeout=30)