Skip to content

added all algo in primary --gen --lntt 1,2,3 #67

added all algo in primary --gen --lntt 1,2,3

added all algo in primary --gen --lntt 1,2,3 #67

name: Publish Python
# Triggered on every push to main.
# Workflow overview:
# 1) parse : read commit message flags and calculate version (including
# auto-dev if necessary).
# 2) build-{linux,windows,mac} : three independent jobs that each compile the
# C library for its platform, run the full pytest suite, and build a
# platform-specific wheel. Separating them prevents one slow container
# from doing all work and allows concurrent builds.
# 3) publish-test : when --pipTest is requested and all builds succeed, the
# three wheels are uploaded to TestPyPI.
# 3.5) verify-testpkg : lightweight smoke checks install a wheel from
# TestPyPI on each OS and exercise a couple of landing features.
# 4) publish-live : when --pipLive with explicit version is given, push the
# same wheels to PyPI and create a GitHub release.
#
# Flags in the commit message control what happens (all case-insensitive):
#
# --v X.Y.Z or --version X.Y.Z -> set the package version before building
# --PipTest (or --piptest etc.) -> publish to TestPyPI after tests pass
# --pipLive (or --piplive etc.) -> publish to PyPI + create GitHub release
# --noRelease (or --norelease etc.) -> build and test only -- no publishing,
# no tags, no releases (even if --v set)
#
# If --PipTest is given without --v, the version is auto-set to
# <current>.devN where N is the git commit count (unique per push).
#
# Changelog: place note/changelog/<X_Y_Z>.md before pushing --pipLive.
# e.g. note/changelog/0_1_0.md for version 0.1.0
on:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
id-token: write
# -----------------------------------------------------------------------------
jobs:
# -- 1. Parse commit message flags --------------------------------------------
parse:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.p.outputs.version }}
has_version: ${{ steps.p.outputs.has_version }}
pip_test: ${{ steps.p.outputs.pip_test }}
pip_live: ${{ steps.p.outputs.pip_live }}
no_release: ${{ steps.p.outputs.no_release }}
should_run: ${{ steps.p.outputs.should_run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # needed for git rev-list --count
- name: Parse commit message
id: p
shell: bash
run: |
MSG="${{ github.event.head_commit.message }}"
VERSION=$(python3 -c "
import re, sys
m = re.search(r'--(?:v|version)\s+([\d]+(?:\.[\d]+)*)', sys.argv[1])
print(m.group(1) if m else '')
" "$MSG")
# All flags are matched case-insensitively so --pipTest / --PipTest
# / --PIPTEST all work the same way.
PIP_TEST=$(echo "$MSG" | grep -qi -- '--piptest' && echo true || echo false)
PIP_LIVE=$(echo "$MSG" | grep -qi -- '--piplive' && echo true || echo false)
NO_RELEASE=$(echo "$MSG" | grep -qi -- '--norelease' && echo true || echo false)
# When --PipTest is used without an explicit version, auto-build a
# unique dev version: take the base version from pyproject.toml and
# append .devN where N is the current git commit count.
# This ensures every push produces a distinct wheel on TestPyPI and
# avoids silent skip-existing collisions.
if [ "$PIP_TEST" = true ] && [ -z "$VERSION" ]; then
BASE_VER=$(python3 -c "
import re
with open('libs/python/pyproject.toml') as f:
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', f.read(), re.MULTILINE)
print(m.group(1).split('-')[0] if m else '0.0.1')
")
COMMIT_N=$(git rev-list --count HEAD)
VERSION="${BASE_VER}.dev${COMMIT_N}"
echo "[INFO] No --v flag — using auto dev version: $VERSION"
fi
HAS_VERSION=$([ -n "$VERSION" ] && echo true || echo false)
# Enforce rules for live publishing: version must be set and a
# corresponding changelog file should exist; abort early otherwise.
if [ "$PIP_LIVE" = true ]; then
if [ -z "$VERSION" ]; then
echo "[ERROR] --pipLive specified but no --v/--version provided" >&2
exit 1
fi
# look for note/changelog/0_0_1.md style file
VER_UNDER=${VERSION//./_}
CHG="note/changelog/${VER_UNDER}.md"
if [ ! -f "$CHG" ]; then
echo "[ERROR] changelog file not found for version $VERSION: $CHG" >&2
exit 1
fi
fi
if [ "$HAS_VERSION" = true ] || [ "$PIP_TEST" = true ] || [ "$PIP_LIVE" = true ]; then
SHOULD_RUN=true
else
SHOULD_RUN=false
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "has_version=$HAS_VERSION" >> $GITHUB_OUTPUT
echo "pip_test=$PIP_TEST" >> $GITHUB_OUTPUT
echo "pip_live=$PIP_LIVE" >> $GITHUB_OUTPUT
echo "no_release=$NO_RELEASE" >> $GITHUB_OUTPUT
echo "should_run=$SHOULD_RUN" >> $GITHUB_OUTPUT
echo "=== Parsed flags ==="
echo " version : $VERSION"
echo " has_version : $HAS_VERSION"
echo " pip_test : $PIP_TEST"
echo " pip_live : $PIP_LIVE"
echo " no_release : $NO_RELEASE"
echo " should_run : $SHOULD_RUN"
# -- 2a. Build + test on Linux ------------------------------------------------
build-linux:
needs: parse
if: needs.parse.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install GCC
run: sudo apt-get install -y gcc
- name: Install Python build tools
run: python -m pip install --upgrade pip setuptools wheel pytest
- name: Set version in pyproject.toml
if: needs.parse.outputs.has_version == 'true'
shell: bash
run: python libs/python/tools/version.py --set "${{ needs.parse.outputs.version }}"
- name: Build C library
shell: bash
run: |
python runner.py \
--build system:main \
--platform linux \
--lib-ext .so \
--no-color
- name: Bundle native binary into package
shell: bash
run: |
mkdir -p libs/python/src/nextssl/lib
BIN=$(find bin/linux/primary -name "main.so" 2>/dev/null | head -1)
if [ -z "$BIN" ]; then echo "[ERROR] compiled binary not found"; exit 1; fi
cp "$BIN" libs/python/src/nextssl/lib/nextssl.so
- name: Run Python tests
shell: bash
run: python -m pytest test/ -v --tb=short
- name: Build linux wheel
shell: bash
run: |
cd libs/python
python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --dist-dir dist
ls dist/
- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheel-linux
path: libs/python/dist/*.whl
if-no-files-found: error
# -- 2b. Build + test on Windows ----------------------------------------------
build-windows:
needs: parse
if: needs.parse.outputs.should_run == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Python build tools
run: python -m pip install --upgrade pip setuptools wheel pytest
- name: Set version in pyproject.toml
if: needs.parse.outputs.has_version == 'true'
shell: bash
run: python libs/python/tools/version.py --set "${{ needs.parse.outputs.version }}"
- name: Build C library
shell: bash
run: |
python runner.py \
--build system:main \
--platform windows \
--lib-ext .dll \
--no-color
- name: Bundle native binary into package
shell: bash
run: |
mkdir -p libs/python/src/nextssl/lib
BIN=$(find bin/windows/primary -name "main.dll" 2>/dev/null | head -1)
if [ -z "$BIN" ]; then echo "[ERROR] no DLL"; exit 1; fi
cp "$BIN" libs/python/src/nextssl/lib/nextssl.dll
- name: Run Python tests
shell: bash
run: python -m pytest test/ -v --tb=short
- name: Build windows wheel
shell: bash
run: |
cd libs/python
python setup.py bdist_wheel --plat-name win_amd64 --dist-dir dist
ls dist/
- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheel-windows
path: libs/python/dist/*.whl
if-no-files-found: error
# -- 2c. Build + test on macOS ------------------------------------------------
build-mac:
needs: parse
if: needs.parse.outputs.should_run == 'true'
runs-on: macos-latest # arm64 (Apple Silicon) — macos-13 Intel was deprecated Dec 2025
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Python build tools
run: python -m pip install --upgrade pip setuptools wheel pytest
- name: Set version in pyproject.toml
if: needs.parse.outputs.has_version == 'true'
shell: bash
run: python libs/python/tools/version.py --set "${{ needs.parse.outputs.version }}"
- name: Build C library
shell: bash
run: |
python runner.py \
--build system:main \
--platform mac \
--lib-ext .dylib \
--no-color
- name: Bundle native binary into package
shell: bash
run: |
mkdir -p libs/python/src/nextssl/lib
BIN=$(find bin/mac/primary -name "main.dylib" 2>/dev/null | head -1)
if [ -z "$BIN" ]; then echo "[ERROR] no dylib"; exit 1; fi
cp "$BIN" libs/python/src/nextssl/lib/nextssl.dylib
- name: Run Python tests
shell: bash
run: python -m pytest test/ -v --tb=short
- name: Build mac wheel
shell: bash
run: |
cd libs/python
python setup.py bdist_wheel --plat-name macosx_14_0_arm64 --dist-dir dist
ls dist/
- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheel-mac
path: libs/python/dist/*.whl
if-no-files-found: error
# -- 3. Publish to TestPyPI --------------------------------------------------
publish-test:
needs: [parse, build-linux, build-windows, build-mac]
if: |
needs.build-linux.result == 'success' &&
needs.build-windows.result == 'success' &&
needs.build-mac.result == 'success' &&
needs.parse.outputs.pip_test == 'true' &&
needs.parse.outputs.no_release != 'true'
runs-on: ubuntu-latest
steps:
- name: Download all platform wheels
uses: actions/download-artifact@v4
with:
pattern: wheel-*
path: dist
merge-multiple: true
- name: List wheels to publish
run: ls -lh dist/
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
repository-url: https://test.pypi.org/legacy/
skip-existing: true
verbose: true
# -- 3.5. Verify published package on each platform ------------------------
verify-testpkg:
# Needs both publish-test (to wait for upload) and parse (for version output).
needs: [parse, publish-test]
if: needs.parse.outputs.pip_test == 'true' && needs.parse.outputs.no_release != 'true'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: windows-latest
- os: macos-latest # arm64 — macos-13 Intel deprecated Dec 2025
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4 # needed to access test/smoke.py
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install pytest
run: python -m pip install --upgrade pip pytest
- name: Install from TestPyPI
shell: bash
run: |
VER="${{ needs.parse.outputs.version }}"
PKG="${VER:+nextssl==$VER}"
PKG="${PKG:-nextssl}"
INDEX="--index-url https://test.pypi.org/simple"
# TestPyPI may take a moment to index a freshly uploaded release.
for attempt in 1 2 3 4 5; do
pip install $INDEX --no-deps --pre "$PKG" && break
echo "[WARN] attempt $attempt failed; retrying in 15s…"
sleep 15
done
- name: Print installed version
shell: bash
run: python -c "import nextssl; print('installed:', nextssl.__version__)"
# Run smoke.py with --noconftest so conftest.py does NOT inject the local
# source tree into sys.path. This guarantees we exercise the pip-installed
# wheel, not repo source code.
- name: Run smoke tests against installed wheel
shell: bash
run: pytest test/smoke.py --noconftest -v --tb=short
# -- 4. Publish to PyPI + create GitHub release ------------------------------
publish-live:
needs: [parse, build-linux, build-windows, build-mac]
if: |
needs.build-linux.result == 'success' &&
needs.build-windows.result == 'success' &&
needs.build-mac.result == 'success' &&
needs.parse.outputs.pip_live == 'true' &&
needs.parse.outputs.no_release != 'true' &&
needs.parse.outputs.has_version == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sanity check version & changelog
shell: bash
run: |
VER="${{ needs.parse.outputs.version }}"
if [ -z "$VER" ]; then
echo "[ERROR] publish-live invoked without version" >&2
exit 1
fi
FILE="note/changelog/${VER//./_}.md"
if [ ! -f "$FILE" ]; then
echo "[ERROR] changelog file missing: $FILE" >&2
exit 1
fi
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Download all platform wheels
uses: actions/download-artifact@v4
with:
pattern: wheel-*
path: dist
merge-multiple: true
- name: List wheels to publish
run: ls -lh dist/
- name: Find changelog file
id: notes
shell: bash
run: |
VER="${{ needs.parse.outputs.version }}"
SLUG=$(echo "$VER" | tr '.' '_')
NOTE_PATH="note/changelog/${SLUG}.md"
if [ -f "$NOTE_PATH" ]; then
echo "path=$NOTE_PATH" >> $GITHUB_OUTPUT
echo "[OK] Using changelog: $NOTE_PATH"
else
printf "Release %s\n" "$VER" > /tmp/release_notes.md
echo "path=/tmp/release_notes.md" >> $GITHUB_OUTPUT
echo "[WARN] No changelog at $NOTE_PATH -- using placeholder"
fi
- name: Create git tag
shell: bash
run: |
VER="${{ needs.parse.outputs.version }}"
TAG="py-v${VER}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "[SKIP] Tag $TAG already exists"
else
git tag "$TAG"
git push origin "$TAG" \
&& echo "[OK] Tag $TAG pushed" \
|| echo "[WARN] Failed to push tag -- continuing anyway"
fi
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
VER="${{ needs.parse.outputs.version }}"
TAG="py-v${VER}"
gh release create "$TAG" dist/*.whl \
--title "NextSSL Python $VER" \
--notes-file "${{ steps.notes.outputs.path }}" \
&& echo "[OK] GitHub release created: $TAG" \
|| echo "[WARN] Release creation failed (may already exist)"
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
skip-existing: true
verbose: true