Skip to content

SKH-70: 完善 API 文档和使用示例 #15

SKH-70: 完善 API 文档和使用示例

SKH-70: 完善 API 文档和使用示例 #15

Workflow file for this run

name: Build macOS wheels
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: macos-14
name: "arm64 — Python ${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.21"
cache-dependency-path: dpi_bridge/go.sum
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Decode DPI headers
env:
DPI_HEADERS_TAR_B64: ${{ secrets.DPI_HEADERS_TAR_B64 }}
run: |
mkdir -p dpi_include
echo "$DPI_HEADERS_TAR_B64" | base64 -d | tar xzf - -C dpi_include/
- name: Build Go bridge library
run: |
cd dpi_bridge && go build -buildmode=c-shared -o libdmdpi.dylib .
install_name_tool -id @rpath/libdmdpi.dylib libdmdpi.dylib
- name: Install build tools
run: pip install build delocate
- name: Build wheel
env:
MACOSX_DEPLOYMENT_TARGET: "14.0"
_PYTHON_HOST_PLATFORM: "macosx-14.0-arm64"
run: DMPYTHON_SKIP_GO_BUILD=1 python -m build --wheel
- name: Delocate wheel
run: |
mkdir -p dist_fixed
DYLD_LIBRARY_PATH=dpi_bridge delocate-wheel -w dist_fixed dist/*.whl -v
- name: Verify wheel
run: |
python -m venv /tmp/test_venv
/tmp/test_venv/bin/pip install dist_fixed/*.whl
/tmp/test_venv/bin/python -c "import dmPython; print('dmPython version:', dmPython.version)"
- name: Check DM secrets availability
id: dm_ready
if: ${{ matrix.python-version == '3.10' }}
env:
DM_TEST_HOST: ${{ secrets.DM_TEST_HOST }}
DM_TEST_PORT: ${{ secrets.DM_TEST_PORT }}
DM_TEST_USER: ${{ secrets.DM_TEST_USER }}
DM_TEST_PASSWORD: ${{ secrets.DM_TEST_PASSWORD }}
run: |
if [ -n "$DM_TEST_HOST" ] && [ -n "$DM_TEST_PORT" ] && [ -n "$DM_TEST_USER" ] && [ -n "$DM_TEST_PASSWORD" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi
- name: Build extension for optional integration tests
if: ${{ matrix.python-version == '3.10' && steps.dm_ready.outputs.available == 'true' }}
run: DMPYTHON_SKIP_GO_BUILD=1 python setup.py build_ext --inplace
- name: Run optional DM integration tests
if: ${{ matrix.python-version == '3.10' && steps.dm_ready.outputs.available == 'true' }}
env:
DYLD_LIBRARY_PATH: ${{ github.workspace }}/dpi_bridge
DM_TEST_HOST: ${{ secrets.DM_TEST_HOST }}
DM_TEST_PORT: ${{ secrets.DM_TEST_PORT }}
DM_TEST_USER: ${{ secrets.DM_TEST_USER }}
DM_TEST_PASSWORD: ${{ secrets.DM_TEST_PASSWORD }}
DM_TEST_BOUNDARY_SIZES: ${{ secrets.DM_TEST_BOUNDARY_SIZES }}
DM_TEST_STRESS_ROWS: ${{ secrets.DM_TEST_STRESS_ROWS }}
DM_TEST_STRESS_WORKERS: ${{ secrets.DM_TEST_STRESS_WORKERS }}
DM_TEST_CHURN_LOOPS: ${{ secrets.DM_TEST_CHURN_LOOPS }}
DM_TEST_GC_LOOPS: ${{ secrets.DM_TEST_GC_LOOPS }}
run: |
python -m pip install pytest pytest-timeout pytest-cov
python -m pytest -q -m requires_dm tests --cov=. --cov-report=term-missing --cov-report=xml:coverage.xml
- name: Upload optional integration coverage
if: ${{ matrix.python-version == '3.10' && steps.dm_ready.outputs.available == 'true' }}
uses: actions/upload-artifact@v4
with:
name: coverage-wheel-baseline-py${{ matrix.python-version }}
path: coverage.xml
- name: Skip optional integration tests (missing DM secrets)
if: ${{ matrix.python-version == '3.10' && steps.dm_ready.outputs.available != 'true' }}
run: |
echo "::notice::SKIP_DM_INTEGRATION: DM_TEST_HOST/PORT/USER/PASSWORD secrets are not fully configured."
- uses: actions/upload-artifact@v4
with:
name: wheel-arm64-py${{ matrix.python-version }}
path: dist_fixed/*.whl
release:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: macos-14
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: dist_fixed
pattern: wheel-*
merge-multiple: true
- name: Validate wheel set and generate release metadata
env:
TAG_NAME: ${{ github.ref_name }}
run: |
python - <<'PY'
import glob
import json
import os
import re
import subprocess
import sys
wheels = sorted(glob.glob("dist_fixed/*.whl"))
if len(wheels) != 5:
print(f"Expected 5 wheels, got {len(wheels)}")
print("\n".join(wheels))
sys.exit(1)
expected_tags = {"cp39-cp39", "cp310-cp310", "cp311-cp311", "cp312-cp312", "cp313-cp313"}
found_tags = set()
for wheel in wheels:
name = os.path.basename(wheel)
if "arm64.whl" not in name:
print(f"Non-arm64 wheel found: {name}")
sys.exit(1)
match = re.search(r"-(cp\d{2,3}-cp\d{2,3})-macosx_.*_arm64\.whl$", name)
if not match:
print(f"Unrecognized wheel naming pattern: {name}")
sys.exit(1)
found_tags.add(match.group(1))
if found_tags != expected_tags:
print(f"Wheel tag mismatch. expected={sorted(expected_tags)} found={sorted(found_tags)}")
sys.exit(1)
with open("dist_fixed/build-metadata.json", "w", encoding="utf-8") as f:
json.dump(
{
"tag": os.environ["TAG_NAME"],
"commit": os.environ.get("GITHUB_SHA", ""),
"workflow": os.environ.get("GITHUB_WORKFLOW", ""),
"run_id": os.environ.get("GITHUB_RUN_ID", ""),
"run_attempt": os.environ.get("GITHUB_RUN_ATTEMPT", ""),
"wheels": [os.path.basename(w) for w in wheels],
},
f,
ensure_ascii=False,
indent=2,
)
subprocess.check_call("shasum -a 256 dist_fixed/*.whl > dist_fixed/checksums.txt", shell=True)
PY
- name: Upsert GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ github.ref_name }}
run: |
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
echo "Release $TAG_NAME already exists, uploading artifacts with --clobber"
gh release upload "$TAG_NAME" dist_fixed/*.whl dist_fixed/checksums.txt dist_fixed/build-metadata.json --clobber
else
echo "Creating release $TAG_NAME"
gh release create "$TAG_NAME" --generate-notes dist_fixed/*.whl dist_fixed/checksums.txt dist_fixed/build-metadata.json
fi
- name: Verify release assets completeness
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ github.ref_name }}
run: |
python - <<'PY'
import json
import os
import re
import subprocess
import sys
tag = os.environ["TAG_NAME"]
raw = subprocess.check_output(
["gh", "release", "view", tag, "--json", "assets"],
text=True,
)
assets = [a["name"] for a in json.loads(raw)["assets"]]
expected_tags = {"cp39-cp39", "cp310-cp310", "cp311-cp311", "cp312-cp312", "cp313-cp313"}
found_tags = set()
for name in assets:
m = re.search(r"-(cp\d{2,3}-cp\d{2,3})-macosx_.*_arm64\.whl$", name)
if m:
found_tags.add(m.group(1))
missing_tags = sorted(expected_tags - found_tags)
required_files = {"checksums.txt", "build-metadata.json"}
missing_files = sorted(f for f in required_files if f not in assets)
if missing_tags or missing_files:
print("Release assets are incomplete.")
print(f"missing wheel tags: {missing_tags}")
print(f"missing files: {missing_files}")
print(f"assets: {assets}")
sys.exit(1)
print("Release assets verified:", assets)
PY