Skip to content

CI

CI #34

Workflow file for this run

name: CI
on:
workflow_dispatch:
inputs:
target:
description: OS and architecture target
required: false
type: choice
default: all
options:
- all
- linux-x64
- linux-arm64
- macos-arm64
- macos-x64
- windows-x64
- windows-arm64
python_version:
description: Python version
required: false
type: choice
default: all
options:
- all
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
workflow_call:
inputs:
target:
description: OS and architecture target (all, linux-x64, linux-arm64, macos-arm64, macos-x64, windows-x64, windows-arm64)
required: false
type: string
default: all
python_version:
description: Python version (all, 3.10, 3.11, 3.12, 3.13, 3.14)
required: false
type: string
default: all
pull_request:
branches: [main]
jobs:
setup:
runs-on: ubuntu-latest
outputs:
run_linux: ${{ steps.resolve.outputs.run_linux }}
run_macos: ${{ steps.resolve.outputs.run_macos }}
run_windows: ${{ steps.resolve.outputs.run_windows }}
linux_matrix: ${{ steps.resolve.outputs.linux_matrix }}
macos_matrix: ${{ steps.resolve.outputs.macos_matrix }}
windows_matrix: ${{ steps.resolve.outputs.windows_matrix }}
steps:
- name: Resolve selected targets and matrix values
id: resolve
env:
EVENT_NAME: ${{ github.event_name }}
TARGET_INPUT: ${{ inputs.target || 'all' }}
PYTHON_INPUT: ${{ inputs.python_version || 'all' }}
run: |
set -euo pipefail
target="$TARGET_INPUT"
python_version="$PYTHON_INPUT"
if [[ "$EVENT_NAME" == "pull_request" ]]; then
target="all"
python_version="all"
fi
case "$python_version" in
all)
py_versions=("3.10" "3.11" "3.12" "3.13" "3.14")
;;
3.10|3.11|3.12|3.13|3.14)
py_versions=("$python_version")
;;
*)
echo "Unsupported python_version: $python_version"
exit 1
;;
esac
python_json='['
first=true
for py in "${py_versions[@]}"; do
if [[ "$first" == true ]]; then
first=false
else
python_json+=','
fi
python_json+="\"$py\""
done
python_json+=']'
run_linux=false
run_macos=false
run_windows=false
linux_targets=()
macos_versions='[]'
windows_targets=()
case "$target" in
all)
run_linux=true
run_macos=true
run_windows=true
linux_targets+=("ubuntu-latest|x64")
macos_versions='["macos-15","macos-15-intel"]'
windows_targets+=("windows-latest|x64")
;;
linux-x64)
run_linux=true
linux_targets+=("ubuntu-latest|x64")
;;
linux-arm64)
run_linux=true
linux_targets+=("ubuntu-24.04-arm|arm64")
;;
macos-arm64)
run_macos=true
macos_versions='["macos-15"]'
;;
macos-x64)
run_macos=true
macos_versions='["macos-15-intel"]'
;;
windows-x64)
run_windows=true
windows_targets+=("windows-latest|x64")
;;
windows-arm64)
run_windows=true
windows_targets+=("windows-11-arm|arm64")
;;
*)
echo "Unsupported target: $target"
exit 1
;;
esac
build_include_matrix() {
local -n target_ref=$1
local matrix='{"include":['
local first_item=true
for target_item in "${target_ref[@]}"; do
local runs_on="${target_item%%|*}"
local arch="${target_item##*|}"
for py in "${py_versions[@]}"; do
if [[ "$first_item" == true ]]; then
first_item=false
else
matrix+=','
fi
matrix+="{\"runs-on\":\"$runs_on\",\"arch\":\"$arch\",\"python-version\":\"$py\"}"
done
done
matrix+=']}'
echo "$matrix"
}
if [[ "$run_linux" == "true" ]]; then
linux_matrix="$(build_include_matrix linux_targets)"
else
linux_matrix='{"include":[]}'
fi
if [[ "$run_macos" == "true" ]]; then
macos_matrix="{\"macos-version\":$macos_versions,\"python-version\":$python_json}"
else
macos_matrix='{"macos-version":[],"python-version":[]}'
fi
if [[ "$run_windows" == "true" ]]; then
windows_matrix="$(build_include_matrix windows_targets)"
else
windows_matrix='{"include":[]}'
fi
{
echo "run_linux=$run_linux"
echo "run_macos=$run_macos"
echo "run_windows=$run_windows"
echo "linux_matrix=$linux_matrix"
echo "macos_matrix=$macos_matrix"
echo "windows_matrix=$windows_matrix"
} >> "$GITHUB_OUTPUT"
build-linux:
needs: setup
if: ${{ needs.setup.outputs.run_linux == 'true' }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.linux_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Select manylinux Python tag
run: |
case "${{ matrix.python-version }}" in
"3.10") echo "MANYLINUX_PYTAG=cp310-cp310" >> "$GITHUB_ENV" ;;
"3.11") echo "MANYLINUX_PYTAG=cp311-cp311" >> "$GITHUB_ENV" ;;
"3.12") echo "MANYLINUX_PYTAG=cp312-cp312" >> "$GITHUB_ENV" ;;
"3.13") echo "MANYLINUX_PYTAG=cp313-cp313" >> "$GITHUB_ENV" ;;
"3.14") echo "MANYLINUX_PYTAG=cp314-cp314" >> "$GITHUB_ENV" ;;
*)
echo "Unsupported python-version: ${{ matrix.python-version }}"
exit 1
;;
esac
- name: Build and repair wheel in manylinux container
run: |
rm -rf dist wheelhouse
mkdir -p dist wheelhouse
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
MANYLINUX_IMAGE="quay.io/pypa/manylinux_2_34_aarch64:latest"
else
MANYLINUX_IMAGE="quay.io/pypa/manylinux_2_34:latest"
fi
docker run --rm \
-e PYTAG="${MANYLINUX_PYTAG}" \
-v "${{ github.workspace }}:/io" \
-w /io \
"$MANYLINUX_IMAGE" \
/bin/bash -lc '
set -euo pipefail
if ! command -v cmake >/dev/null 2>&1; then
dnf install -y cmake gcc gcc-c++ make ninja-build
fi
"/opt/python/${PYTAG}/bin/python" -m pip install --upgrade pip setuptools wheel build auditwheel
"/opt/python/${PYTAG}/bin/python" -m build --wheel -o dist
auditwheel repair dist/*.whl -w wheelhouse
'
- name: Install wheel
run: |
python3 -m venv env
source env/bin/activate
python -m pip install --upgrade wheelhouse/*.whl
- name: Smoke test import (non-3.14)
if: matrix.python-version != '3.14'
run: |
source env/bin/activate
python -c "import JupiterMag; print(JupiterMag.__version__)"
- name: Upload wheel artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheels-linux-${{ matrix.arch }}-py${{ matrix.python-version }}
path: wheelhouse/*.whl
- name: Build sdist (Linux 3.14)
if: matrix.python-version == '3.14'
run: |
docker run --rm \
-e PYTAG="${MANYLINUX_PYTAG}" \
-v "${{ github.workspace }}:/io" \
-w /io \
quay.io/pypa/manylinux_2_34:latest \
/bin/bash -lc '
set -euo pipefail
"/opt/python/${PYTAG}/bin/python" -m pip install --upgrade pip setuptools wheel build
"/opt/python/${PYTAG}/bin/python" -m build --sdist -o dist
'
- name: Upload sdist artifact (Linux 3.14)
if: matrix.python-version == '3.14'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sdist-linux-${{ matrix.arch }}-py${{ matrix.python-version }}
path: dist/*.tar.gz
- name: Run pytest from tests directory
if: matrix.python-version == '3.14'
run: |
source env/bin/activate
pip install pytest black flake8
cd tests
pytest -q
build-macos:
needs: setup
if: ${{ needs.setup.outputs.run_macos == 'true' }}
runs-on: ${{ matrix.macos-version }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.macos_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Install build tools
run: |
brew update
brew install libtool
python3 -m venv env
source env/bin/activate
python -m pip install --upgrade pip setuptools wheel build
- name: Build wheel
run: |
source env/bin/activate
export CMAKE_OSX_ARCHITECTURES="$(uname -m)"
export CMAKE_OSX_DEPLOYMENT_TARGET="11.0"
export MACOSX_DEPLOYMENT_TARGET="11.0"
rm -rf dist
mkdir -p dist
python -m build --wheel -o dist
ls -1 dist
if ls dist/*universal2*.whl >/dev/null 2>&1; then
echo "Unexpected universal2 wheel produced"
exit 1
fi
- name: Install wheel
run: |
source env/bin/activate
python -m pip install --upgrade dist/*.whl
- name: Smoke test import (non-3.14)
if: matrix.python-version != '3.14'
run: |
source env/bin/activate
python -c "import JupiterMag; print(JupiterMag.__version__)"
- name: Upload wheel artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheels-${{ matrix.macos-version }}-py${{ matrix.python-version }}
path: dist/*.whl
- name: Run tests
if: matrix.python-version == '3.14'
run: |
source env/bin/activate
if [[ "$(uname -m)" == "x86_64" ]]; then
# DateTimeTools may resolve to an arm64 wheel on Intel runners.
# Rebuild from source to guarantee a native x86_64 libdatetime.dylib.
python -m pip install --force-reinstall --no-binary DateTimeTools DateTimeTools
fi
pip install pytest black flake8
cd tests
pytest -q
build-windows:
needs: setup
if: ${{ needs.setup.outputs.run_windows == 'true' }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.windows_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Create venv
shell: pwsh
run: |
python -m venv env
- name: Build wheel
shell: pwsh
run: |
.\env\Scripts\Activate.ps1
$pythonExe = Join-Path $env:VIRTUAL_ENV "Scripts\python.exe"
& $pythonExe -m pip install --upgrade build wheel
$targetArch = if ("${{ matrix.arch }}" -eq "arm64") { "arm64" } else { "x64" }
$hostArch = $targetArch
$vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe"
if (-not (Test-Path $vswhere)) {
throw "vswhere.exe not found at $vswhere"
}
$vsInstallPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vsInstallPath) {
throw "Could not locate a Visual Studio installation with C++ tools"
}
$vsDevCmd = Join-Path $vsInstallPath "Common7\Tools\VsDevCmd.bat"
if (-not (Test-Path $vsDevCmd)) {
throw "VsDevCmd.bat not found at $vsDevCmd"
}
# Import Visual Studio developer environment vars into this PowerShell step.
$envDump = & cmd.exe /s /c "`"$vsDevCmd`" -no_logo -host_arch=$hostArch -arch=$targetArch && set"
foreach ($line in $envDump) {
if ($line -match '^(.*?)=(.*)$') {
Set-Item -Path ("Env:" + $matches[1]) -Value $matches[2]
}
}
$clCmd = Get-Command cl.exe -ErrorAction SilentlyContinue
$cmakeCmd = Get-Command cmake.exe -ErrorAction SilentlyContinue
if (-not $clCmd -or -not $cmakeCmd) {
throw "MSVC or CMake not available in PATH after loading VsDevCmd"
}
$env:CMAKE_GENERATOR = "Visual Studio 17 2022"
$env:CMAKE_GENERATOR_PLATFORM = $targetArch
Remove-Item Env:CMAKE_ARGS -ErrorAction SilentlyContinue
Write-Host "Using cl: $($clCmd.Source)"
Write-Host "Using cmake: $($cmakeCmd.Source)"
& cmd.exe /c "cl"
& $cmakeCmd.Source --version
& $pythonExe -m build --wheel -o dist
- name: Install wheel
shell: pwsh
run: |
.\env\Scripts\Activate.ps1
$wheel = Get-ChildItem -Path dist -Filter *.whl | Select-Object -First 1
if (-not $wheel) { throw "No wheel found in dist/" }
python -m pip install --upgrade $wheel.FullName
- name: Smoke test import (non-3.14)
if: matrix.python-version != '3.14'
shell: pwsh
run: |
.\env\Scripts\Activate.ps1
python -c "import JupiterMag; print(JupiterMag.__version__)"
- name: Upload wheel artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheels-windows-${{ matrix.arch }}-py${{ matrix.python-version }}
path: dist/*.whl
- name: Run tests
if: matrix.python-version == '3.14'
shell: pwsh
run: |
.\env\Scripts\Activate.ps1
Set-Location tests
pip install pytest black flake8
pytest -q