Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 242 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,87 @@ name: CI

on:
pull_request:
workflow_dispatch:
inputs:
linux_x86_64:
description: Build for Linux x86_64
type: boolean
default: true
linux_aarch64:
description: Build for Linux ARM64
type: boolean
default: false
macos_arm64:
description: Build for macOS ARM64
type: boolean
default: true
macos_x86_64:
description: Build for macOS x86_64
type: boolean
default: false
windows_x86_64:
description: Build for Windows x86_64
type: boolean
default: true
windows_arm64:
description: Build for Windows ARM64
type: boolean
default: false
upload_artifacts:
description: Upload wheels and one source distribution
type: boolean
default: false
workflow_call:
inputs:
linux_x86_64:
description: Build for Linux x86_64
type: boolean
default: true
linux_aarch64:
description: Build for Linux ARM64
type: boolean
default: false
macos_arm64:
description: Build for macOS ARM64
type: boolean
default: true
macos_x86_64:
description: Build for macOS x86_64
type: boolean
default: false
windows_x86_64:
description: Build for Windows x86_64
type: boolean
default: true
windows_arm64:
description: Build for Windows ARM64
type: boolean
default: false
upload_artifacts:
description: Upload wheels and one source distribution
type: boolean
default: false

permissions:
contents: read

jobs:
build-and-test:
runs-on: ubuntu-latest
linux:
if: github.event_name == 'pull_request' || inputs.linux_x86_64 || inputs.linux_aarch64
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(
github.event_name == 'pull_request' &&
'[{"arch":"x86_64","runner":"ubuntu-latest","python_arch":"x64","primary":true}]' ||
inputs.linux_x86_64 && inputs.linux_aarch64 &&
'[{"arch":"x86_64","runner":"ubuntu-latest","python_arch":"x64","primary":true},{"arch":"aarch64","runner":"ubuntu-24.04-arm","python_arch":"arm64","primary":false}]' ||
inputs.linux_x86_64 &&
'[{"arch":"x86_64","runner":"ubuntu-latest","python_arch":"x64","primary":true}]' ||
'[{"arch":"aarch64","runner":"ubuntu-24.04-arm","python_arch":"arm64","primary":true}]'
) }}
name: linux-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}

steps:
- name: Check out repository
Expand All @@ -20,11 +94,87 @@ jobs:
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
architecture: ${{ matrix.python_arch }}

- name: Build distributions
- name: Build manylinux wheel
run: ./container-build.sh "${{ matrix.arch }}"

- name: Build source distribution
if: inputs.upload_artifacts && matrix.primary
run: |
python -m pip install build
python -m build --sdist .

- name: Install wheel into test environment
run: |
python -m venv "$RUNNER_TEMP/spicedmodel-test-venv"
"$RUNNER_TEMP/spicedmodel-test-venv/bin/python" -m pip install wheelhouse/*.whl pytest

- name: Run installed-package tests
working-directory: ${{ runner.temp }}
env:
MPLCONFIGDIR: ${{ runner.temp }}/matplotlib
run: |
"$RUNNER_TEMP/spicedmodel-test-venv/bin/python" -m pytest "$GITHUB_WORKSPACE/tests"

- name: Upload wheel
if: inputs.upload_artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheel-linux-${{ matrix.arch }}
path: wheelhouse/*.whl
if-no-files-found: error

- name: Upload source distribution
if: inputs.upload_artifacts && matrix.primary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: source-distribution
path: dist/*.tar.gz
if-no-files-found: error

macos:
if: github.event_name == 'pull_request' || inputs.macos_arm64 || inputs.macos_x86_64
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(
github.event_name == 'pull_request' &&
'[{"arch":"arm64","runner":"macos-latest","python_arch":"arm64","primary":true}]' ||
inputs.macos_arm64 && inputs.macos_x86_64 &&
'[{"arch":"arm64","runner":"macos-latest","python_arch":"arm64","primary":true},{"arch":"x86_64","runner":"macos-15-intel","python_arch":"x64","primary":false}]' ||
inputs.macos_arm64 &&
'[{"arch":"arm64","runner":"macos-latest","python_arch":"arm64","primary":true}]' ||
'[{"arch":"x86_64","runner":"macos-15-intel","python_arch":"x64","primary":true}]'
) }}
name: macos-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
architecture: ${{ matrix.python_arch }}

- name: Install OpenMP
run: |
brew install libomp
echo "OpenMP_ROOT=$(brew --prefix libomp)" >> "$GITHUB_ENV"

- name: Build wheel
run: |
python -m pip install build
python -m build .
python -m build --wheel .

- name: Build source distribution
if: inputs.upload_artifacts && matrix.primary && !inputs.linux_x86_64 && !inputs.linux_aarch64
run: python -m build --sdist .

- name: Install wheel into test environment
run: |
Expand All @@ -37,3 +187,91 @@ jobs:
MPLCONFIGDIR: ${{ runner.temp }}/matplotlib
run: |
"$RUNNER_TEMP/spicedmodel-test-venv/bin/python" -m pytest "$GITHUB_WORKSPACE/tests"

- name: Upload wheel
if: inputs.upload_artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheel-macos-${{ matrix.arch }}
path: dist/*.whl
if-no-files-found: error

- name: Upload source distribution
if: inputs.upload_artifacts && matrix.primary && !inputs.linux_x86_64 && !inputs.linux_aarch64
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: source-distribution
path: dist/*.tar.gz
if-no-files-found: error

windows:
if: github.event_name == 'pull_request' || inputs.windows_x86_64 || inputs.windows_arm64
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(
github.event_name == 'pull_request' &&
'[{"arch":"x86_64","runner":"windows-latest","python_arch":"x64","primary":true}]' ||
inputs.windows_x86_64 && inputs.windows_arm64 &&
'[{"arch":"x86_64","runner":"windows-latest","python_arch":"x64","primary":true},{"arch":"arm64","runner":"windows-11-arm","python_arch":"arm64","primary":false}]' ||
inputs.windows_x86_64 &&
'[{"arch":"x86_64","runner":"windows-latest","python_arch":"x64","primary":true}]' ||
'[{"arch":"arm64","runner":"windows-11-arm","python_arch":"arm64","primary":true}]'
) }}
name: windows-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
architecture: ${{ matrix.python_arch }}

- name: Build wheel with MSVC
run: |
python -m pip install build
python -m build --wheel .

- name: Build source distribution
if: inputs.upload_artifacts && matrix.primary && !inputs.linux_x86_64 && !inputs.linux_aarch64 && !inputs.macos_arm64 && !inputs.macos_x86_64
run: python -m build --sdist .

- name: Install wheel into test environment
shell: pwsh
run: |
$venv = Join-Path $env:RUNNER_TEMP "spicedmodel-test-venv"
python -m venv $venv
$python = Join-Path $venv "Scripts/python.exe"
$wheel = Get-ChildItem "dist/*.whl" | Select-Object -First 1
& $python -m pip install $wheel.FullName pytest

- name: Run installed-package tests
shell: pwsh
working-directory: ${{ runner.temp }}
env:
MPLCONFIGDIR: ${{ runner.temp }}/matplotlib
run: |
$python = Join-Path $env:RUNNER_TEMP "spicedmodel-test-venv/Scripts/python.exe"
& $python -m pytest "$env:GITHUB_WORKSPACE/tests"

- name: Upload wheel
if: inputs.upload_artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheel-windows-${{ matrix.arch }}
path: dist/*.whl
if-no-files-found: error

- name: Upload source distribution
if: inputs.upload_artifacts && matrix.primary && !inputs.linux_x86_64 && !inputs.linux_aarch64 && !inputs.macos_arm64 && !inputs.macos_x86_64
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: source-distribution
path: dist/*.tar.gz
if-no-files-found: error
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.20)

project(spicedmodel LANGUAGES CXX)

set(BUILD_TESTING OFF CACHE BOOL "Build bundled SPICED tests" FORCE)
add_subdirectory(spicedmodel/__data/spiced)

# scikit-build-core installs this component directly into the wheel. The
# submodule's development files and CMake package are intentionally omitted.
install(
TARGETS spiced ann
LIBRARY DESTINATION spicedmodel/__data/spiced/lib COMPONENT Python
RUNTIME DESTINATION spicedmodel/__data/spiced/lib COMPONENT Python
)
3 changes: 0 additions & 3 deletions MANIFEST.in

This file was deleted.

83 changes: 83 additions & 0 deletions container-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash

set -euo pipefail

usage() {
cat <<'EOF'
Usage: ./container-build.sh [x86_64|aarch64]

Build and repair one Python-independent manylinux_2_28 wheel. The architecture
defaults to the Docker host architecture. Cross-architecture builds require
Docker's corresponding binfmt/QEMU support.

Outputs:
dist/*.whl Unrepaired Linux wheel
wheelhouse/*.whl Repaired manylinux wheel
EOF
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi

case "${1:-$(uname -m)}" in
x86_64|amd64)
ARCH="x86_64"
PLATFORM="linux/amd64"
;;
aarch64|arm64)
ARCH="aarch64"
PLATFORM="linux/arm64"
;;
*)
echo "Unsupported architecture: ${1:-$(uname -m)}" >&2
usage >&2
exit 1
;;
esac

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
if command -v git >/dev/null 2>&1; then
REPO_ROOT="$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel 2>/dev/null || echo "${SCRIPT_DIR}")"
else
REPO_ROOT="${SCRIPT_DIR}"
fi

if [[ ! -f "${REPO_ROOT}/pyproject.toml" || ! -f "${REPO_ROOT}/CMakeLists.txt" ]]; then
echo "Could not identify the spicedmodel repository root: ${REPO_ROOT}" >&2
exit 1
fi

IMAGE="quay.io/pypa/manylinux_2_28_${ARCH}:latest"

echo "Building ${ARCH} wheel with ${IMAGE}"
echo "Repository root: ${REPO_ROOT}"

rm -rf "${REPO_ROOT}/dist" "${REPO_ROOT}/wheelhouse"
mkdir -p "${REPO_ROOT}/dist" "${REPO_ROOT}/wheelhouse"

docker run --rm \
--platform "${PLATFORM}" \
-e AUDITWHEEL_PLAT="manylinux_2_28_${ARCH}" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
-v "${REPO_ROOT}:/io" \
-w /io \
"${IMAGE}" \
/bin/bash -lc '
set -euo pipefail

# The interpreter is only a build frontend. wheel.py-api="py3" ensures
# that neither its Python version nor its ABI appears in the wheel tag.
PYTHON=/opt/python/cp314-cp314/bin/python
"${PYTHON}" -m pip install --upgrade build
"${PYTHON}" -m build --wheel --outdir /tmp/dist /io
cp /tmp/dist/*.whl /io/dist/
auditwheel repair --plat "${AUDITWHEEL_PLAT}" /tmp/dist/*.whl \
--wheel-dir /io/wheelhouse
chown -R "${HOST_UID}:${HOST_GID}" /io/dist /io/wheelhouse || true
'

echo "Done. Repaired wheel:"
ls -1 "${REPO_ROOT}/wheelhouse"/*.whl
Loading
Loading