mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-05-11 09:36:54 +00:00
feat(ci): add pypi publish workflow and add macos debug script
This commit is contained in:
@@ -353,6 +353,10 @@ jobs:
|
||||
if: matrix.label == 'macos'
|
||||
run: /usr/sbin/softwareupdate --install-rosetta --agree-to-license || true
|
||||
|
||||
- name: Ensure x86_64 Homebrew (/usr/local) for universal slice
|
||||
if: matrix.label == 'macos'
|
||||
run: bash scripts/ci/github-ensure-macos-x86-64-homebrew.sh
|
||||
|
||||
- name: Set up Python x64 for cx_Freeze universal slice
|
||||
id: python_x64
|
||||
if: matrix.label == 'macos'
|
||||
@@ -370,32 +374,24 @@ jobs:
|
||||
if: matrix.label == 'macos'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The Rosetta (x86_64) Homebrew lives at /usr/local/bin/brew on
|
||||
# Apple Silicon GitHub runners. libyaml must be available as
|
||||
# x86_64 so PyYAML's C extension (_yaml.cpython-3xx-darwin.so)
|
||||
# compiles for the darwin-x64 cx_Freeze slice. Without this,
|
||||
# pip falls back to pure Python pyyaml, which is absent from
|
||||
# the x64 tree while the arm64 tree has the binary, causing
|
||||
# unify-backend-plain-files.sh to fail.
|
||||
if [[ -x /usr/local/bin/brew ]]; then
|
||||
arch -x86_64 /usr/local/bin/brew install libyaml || true
|
||||
else
|
||||
echo "x86_64 Homebrew not found at /usr/local/bin/brew; PyYAML C extension may fall back to pure Python for x64 slice."
|
||||
fi
|
||||
# x86_64 libyaml for the darwin-x64 cx_Freeze slice (see
|
||||
# github-ensure-macos-x86-64-homebrew.sh; /usr/local/bin/brew is
|
||||
# guaranteed before this step on Apple Silicon runners).
|
||||
arch -x86_64 /usr/local/bin/brew install libyaml || true
|
||||
|
||||
- name: Install x86_64 codec2 for pycodec2 (universal slice)
|
||||
if: matrix.label == 'macos'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -x /usr/local/bin/brew ]]; then
|
||||
arch -x86_64 /usr/local/bin/brew install codec2
|
||||
else
|
||||
echo "x86_64 Homebrew not found at /usr/local/bin/brew; cannot build pycodec2 x64 slice." >&2
|
||||
exit 1
|
||||
fi
|
||||
arch -x86_64 /usr/local/bin/brew install codec2
|
||||
_codec2="$(arch -x86_64 /usr/local/bin/brew --prefix codec2)"
|
||||
test -f "${_codec2}/include/codec2/codec2.h"
|
||||
test -f "${_codec2}/lib/libcodec2.dylib"
|
||||
shopt -s nullglob
|
||||
_libs=("${_codec2}/lib"/libcodec2*.dylib)
|
||||
if [[ ${#_libs[@]} -eq 0 ]]; then
|
||||
echo "no libcodec2*.dylib under ${_codec2}/lib" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install project deps into x64 Python (mac universal cx_Freeze)
|
||||
if: matrix.label == 'macos'
|
||||
@@ -407,15 +403,10 @@ jobs:
|
||||
CFLAGS: "-arch x86_64"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -x /usr/local/bin/brew ]]; then
|
||||
_codec2="$(arch -x86_64 /usr/local/bin/brew --prefix codec2)"
|
||||
export LDFLAGS="-L${_codec2}/lib -arch x86_64"
|
||||
export CPPFLAGS="-I${_codec2}/include"
|
||||
export PKG_CONFIG_PATH="${_codec2}/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig"
|
||||
else
|
||||
export LDFLAGS="-arch x86_64"
|
||||
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig"
|
||||
fi
|
||||
_codec2="$(arch -x86_64 /usr/local/bin/brew --prefix codec2)"
|
||||
export LDFLAGS="-L${_codec2}/lib -arch x86_64"
|
||||
export CPPFLAGS="-I${_codec2}/include"
|
||||
export PKG_CONFIG_PATH="${_codec2}/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig"
|
||||
arch -x86_64 "$PY_X64" -m pip install -U pip setuptools wheel
|
||||
arch -x86_64 "$PY_X64" -m pip install "cx-freeze>=7.0.0"
|
||||
arch -x86_64 "$PY_X64" -m pip install -e .
|
||||
|
||||
+13
-18
@@ -102,6 +102,10 @@ jobs:
|
||||
if: matrix.label == 'macos'
|
||||
run: /usr/sbin/softwareupdate --install-rosetta --agree-to-license || true
|
||||
|
||||
- name: Ensure x86_64 Homebrew (/usr/local) for universal slice
|
||||
if: matrix.label == 'macos'
|
||||
run: bash scripts/ci/github-ensure-macos-x86-64-homebrew.sh
|
||||
|
||||
- name: Set up Python x64 for cx_Freeze universal slice
|
||||
id: python_x64
|
||||
if: matrix.label == 'macos'
|
||||
@@ -119,11 +123,7 @@ jobs:
|
||||
if: matrix.label == 'macos'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -x /usr/local/bin/brew ]]; then
|
||||
arch -x86_64 /usr/local/bin/brew install codec2
|
||||
else
|
||||
echo "x86_64 Homebrew not found at /usr/local/bin/brew; pycodec2 x64 slice build may fail." >&2
|
||||
fi
|
||||
arch -x86_64 /usr/local/bin/brew install codec2
|
||||
|
||||
- name: Install project deps into x64 Python (mac universal cx_Freeze)
|
||||
if: matrix.label == 'macos'
|
||||
@@ -135,19 +135,14 @@ jobs:
|
||||
CFLAGS: "-arch x86_64"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -x /usr/local/bin/brew ]]; then
|
||||
_codec2="$(arch -x86_64 /usr/local/bin/brew --prefix codec2)"
|
||||
export LDFLAGS="-L${_codec2}/lib -arch x86_64"
|
||||
export CPPFLAGS="-I${_codec2}/include"
|
||||
export PKG_CONFIG_PATH="${_codec2}/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig"
|
||||
else
|
||||
export LDFLAGS="-arch x86_64"
|
||||
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig"
|
||||
fi
|
||||
"$PY_X64" -m pip install -U pip setuptools wheel
|
||||
"$PY_X64" -m pip install "cx-freeze>=7.0.0"
|
||||
"$PY_X64" -m pip install -e .
|
||||
"$PY_X64" scripts/patch_lxst_pyogg_ogg_ctypes.py
|
||||
_codec2="$(arch -x86_64 /usr/local/bin/brew --prefix codec2)"
|
||||
export LDFLAGS="-L${_codec2}/lib -arch x86_64"
|
||||
export CPPFLAGS="-I${_codec2}/include"
|
||||
export PKG_CONFIG_PATH="${_codec2}/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig"
|
||||
arch -x86_64 "$PY_X64" -m pip install -U pip setuptools wheel
|
||||
arch -x86_64 "$PY_X64" -m pip install "cx-freeze>=7.0.0"
|
||||
arch -x86_64 "$PY_X64" -m pip install -e .
|
||||
arch -x86_64 "$PY_X64" scripts/patch_lxst_pyogg_ogg_ctypes.py
|
||||
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# Publish ``reticulum-meshchatx`` sdist and wheel to PyPI (Trusted Publishing).
|
||||
# Builds the Vite frontend and offline bundles via the reusable frontend workflow,
|
||||
# places them under ``meshchatx/public/``, then runs ``python -m build``.
|
||||
#
|
||||
# Tag runs also emit SLSA generic provenance (``generator_generic_slsa3``) for the
|
||||
# exact ``dist/`` digests and optional Cosign ``*.cosign.bundle`` files next to each
|
||||
# distribution (same scripts as ``build-release.yml``; skips if ``COSIGN_PRIVATE_KEY`` unset).
|
||||
# PyPI upload uses a staging directory so bundles are not sent to the index.
|
||||
#
|
||||
# PyPI Trusted Publisher must reference this file as ``pypi.yml`` and use the
|
||||
# ``pypi`` GitHub Environment (with required reviewers if you enabled protection).
|
||||
#
|
||||
# Pinned first-party actions (bump tag and SHA together when upgrading):
|
||||
# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
#
|
||||
# SLSA generator (must stay @vX.Y.Z semver per upstream):
|
||||
# slsa-framework/slsa-github-generator/generator_generic_slsa3.yml@v2.1.0
|
||||
#
|
||||
# Third-party pin (resolve before bumping ``release/v1``):
|
||||
# curl -sS "https://api.github.com/repos/pypa/gh-action-pypi-publish/commits/release/v1" | jq -r '.sha'
|
||||
# pypa/gh-action-pypi-publish@release/v1 -> cef221092ed1bacb1cc03d23a2d87d1d172e277b
|
||||
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pypi-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
PYTHON_VERSION: "3.14"
|
||||
COSIGN_VERSION: "3.0.6"
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Build frontend artifact
|
||||
uses: ./.github/workflows/frontend-build.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
artifact_name: meshchatx-frontend-pypi-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
retention_days: 2
|
||||
|
||||
build:
|
||||
name: Build Python distributions
|
||||
needs: frontend
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
hashes: ${{ steps.slsa-hashes.outputs.hashes }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
env:
|
||||
FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: ${{ env.FRONTEND_ARTIFACT_NAME }}
|
||||
path: meshchatx/public
|
||||
|
||||
- name: Verify frontend bundle in tree
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f meshchatx/public/index.html
|
||||
test -d meshchatx/public/assets
|
||||
test -d meshchatx/public/reticulum-docs-bundled/current
|
||||
|
||||
- name: Install build
|
||||
run: python -m pip install -U pip "build>=1.2.0"
|
||||
|
||||
- name: Build sdist and wheel
|
||||
run: python -m build
|
||||
|
||||
- name: Verify wheel contains frontend assets
|
||||
run: |
|
||||
set -euo pipefail
|
||||
whl=(dist/*.whl)
|
||||
if [[ "${#whl[@]}" -ne 1 ]]; then
|
||||
echo "Expected exactly one wheel in dist/" >&2
|
||||
ls -la dist >&2
|
||||
exit 1
|
||||
fi
|
||||
export WHEEL_PATH="${whl[0]}"
|
||||
python - <<'PY'
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
path = os.environ["WHEEL_PATH"]
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
names = zf.namelist()
|
||||
if "meshchatx/public/index.html" not in names:
|
||||
print("missing: meshchatx/public/index.html", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not any(n.startswith("meshchatx/public/assets/") for n in names):
|
||||
print("missing: meshchatx/public/assets/", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print("wheel contains meshchatx/public frontend paths")
|
||||
PY
|
||||
|
||||
- name: SLSA subject hashes (PyPI dists)
|
||||
id: slsa-hashes
|
||||
if: github.ref_type == 'tag'
|
||||
run: bash scripts/ci/github-slsa-hashes-dist.sh
|
||||
|
||||
- name: Cosign attestations (sdist + wheels)
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
|
||||
GITHUB_WORKFLOW: ${{ github.workflow }}
|
||||
GITHUB_WORKFLOW_FILE: pypi.yml
|
||||
COSIGN_VERSION: ${{ env.COSIGN_VERSION }}
|
||||
run: |
|
||||
set -eu
|
||||
if [ -z "${COSIGN_PRIVATE_KEY:-}" ]; then
|
||||
echo "Skipping cosign attestations (no COSIGN_PRIVATE_KEY)."
|
||||
exit 0
|
||||
fi
|
||||
sh scripts/ci/setup-cosign.sh "${COSIGN_VERSION}"
|
||||
printf '%s\n' "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key
|
||||
chmod 600 /tmp/cosign.key
|
||||
export COSIGN_KEY_PATH=/tmp/cosign.key
|
||||
sh scripts/ci/attest-release-assets.sh ./dist
|
||||
rm -f /tmp/cosign.key
|
||||
|
||||
- name: Store distribution packages
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
if-no-files-found: error
|
||||
|
||||
slsa-provenance-pypi:
|
||||
name: SLSA provenance (PyPI dists)
|
||||
needs: build
|
||||
if: github.ref_type == 'tag'
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
actions: read
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects: ${{ needs.build.outputs.hashes }}
|
||||
upload-assets: false
|
||||
provenance-name: meshchatx-pypi-${{ github.ref_name }}.intoto.jsonl
|
||||
|
||||
publish-to-pypi:
|
||||
name: Publish to PyPI
|
||||
if: >-
|
||||
github.ref_type == 'tag' &&
|
||||
needs.build.result == 'success' &&
|
||||
needs.slsa-provenance-pypi.result == 'success'
|
||||
needs:
|
||||
- build
|
||||
- slsa-provenance-pypi
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/project/meshchatx/
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Download distributions
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
|
||||
- name: Stage PyPI uploads (exclude Cosign bundles)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p pypi-upload
|
||||
shopt -s nullglob
|
||||
files=(dist/*.whl dist/*.tar.gz)
|
||||
if [[ "${#files[@]}" -eq 0 ]]; then
|
||||
echo "No .whl or .tar.gz in dist/" >&2
|
||||
ls -la dist >&2
|
||||
exit 1
|
||||
fi
|
||||
cp -v "${files[@]}" pypi-upload/
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
|
||||
with:
|
||||
packages-dir: pypi-upload/
|
||||
Reference in New Issue
Block a user