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
4 changes: 0 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,8 @@ jobs:
- name: Build extension
run: |
py_inc=$(python -c "import sysconfig; print(sysconfig.get_path('include'))")
py_libdir=$(python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))")
py_lib=$(python -c "import sys; print('python%d.%d' % sys.version_info[:2])")
zig build python-bindings \
-Dpython-include="$py_inc" \
-Dpython-lib="$py_lib" \
-Dpython-lib-path="$py_libdir" \
-Doptimize=ReleaseFast
cp zig-out/bindings/python/astroz/lib_astroz.so bindings/python/astroz/_astroz.so

Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ jobs:
- name: Build Python bindings
run: |
PYTHON_INCLUDE=$(python -c "import sysconfig; print(sysconfig.get_path('include'))")
PYTHON_LIBDIR=$(python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))")
PYTHON_LIB=$(python -c "import sys; print(f'python{sys.version_info.major}.{sys.version_info.minor}')")
zig build python-bindings \
-Dpython-include="$PYTHON_INCLUDE" \
-Dpython-lib="$PYTHON_LIB" \
-Dpython-lib-path="$PYTHON_LIBDIR" \
-Doptimize=ReleaseFast
181 changes: 40 additions & 141 deletions .github/workflows/python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,67 +7,21 @@ on:

jobs:
build_wheels:
name: Build wheels (${{ matrix.os }}, Python ${{ matrix.python }})
name: Build wheels (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64
- os: ubuntu-latest
python: '3.14'
target: linux-x86_64
- os: ubuntu-latest
python: '3.13'
target: linux-x86_64
- os: ubuntu-latest
python: '3.12'
target: linux-x86_64
- os: ubuntu-latest
python: '3.11'
target: linux-x86_64
- os: ubuntu-latest
python: '3.10'
target: linux-x86_64
# Linux aarch64 (Graviton, Apple Silicon containers)
- os: ubuntu-24.04-arm
python: '3.14'
target: linux-aarch64
- os: ubuntu-24.04-arm
python: '3.13'
target: linux-aarch64
- os: ubuntu-24.04-arm
python: '3.12'
target: linux-aarch64
- os: ubuntu-24.04-arm
python: '3.11'
target: linux-aarch64
- os: ubuntu-24.04-arm
python: '3.10'
target: linux-aarch64
# macOS ARM64
- os: macos-latest
python: '3.14'
target: macos-arm64
- os: macos-latest
python: '3.13'
target: macos-arm64
- os: macos-latest
python: '3.12'
target: macos-arm64
- os: macos-latest
python: '3.11'
target: macos-arm64
# cibuildwheel builds and tests every Python version per OS;
# settings live in bindings/python/pyproject.toml
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

# Linux gets Zig inside the build container instead (before-all)
- name: Set up Zig
if: runner.os == 'macOS'
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
Expand All @@ -79,104 +33,49 @@ jobs:
else
VERSION="0.0.0.dev"
fi
sed -i.bak "s/version = \".*\"/version = \"$VERSION\"/" bindings/python/pyproject.toml
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" bindings/python/pyproject.toml
sed -i.bak "s/__version__ = \".*\"/__version__ = \"$VERSION\"/" bindings/python/astroz/__init__.py
echo "VERSION=$VERSION" >> $GITHUB_ENV

- name: Build Python bindings
run: |
PYTHON_INCLUDE=$(python -c "import sysconfig; print(sysconfig.get_path('include'))")
PYTHON_LIBDIR=$(python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))")
PYTHON_LIB=$(python -c "import sys; print(f'python{sys.version_info.major}.{sys.version_info.minor}')")
echo "Python include: $PYTHON_INCLUDE"
echo "Python libdir: $PYTHON_LIBDIR"
echo "Python lib: $PYTHON_LIB"

# On macOS, don't link against Python library - symbols resolve at load time.
# On Linux, pin the target explicitly: without -Dtarget Zig compiles for
# the runner's own CPU and glibc (AVX-512 baseline, glibc 2.39 on
# ubuntu-latest), and the wheel then SIGILLs on non-AVX-512 hosts and
# refuses to load on glibc < 2.36 (Amazon Linux 2023 has 2.34).
# glibc 2.28 = manylinux_2_28; a baseline CPU keeps the wheel portable
# while oma still dispatches to AVX2/AVX-512/SVE at runtime.
if [[ "$RUNNER_OS" == "macOS" ]]; then
zig build python-bindings \
-Dpython-include="$PYTHON_INCLUDE" \
-Doptimize=ReleaseFast
else
zig build python-bindings \
-Dtarget="$(uname -m)-linux-gnu.2.28" \
-Dpython-include="$PYTHON_INCLUDE" \
-Dpython-lib="$PYTHON_LIB" \
-Dpython-lib-path="$PYTHON_LIBDIR" \
-Doptimize=ReleaseFast
fi

- name: Copy built library
run: |
ls -la zig-out/bindings/python/astroz/
# Zig builds lib_astroz.so (Linux) or lib_astroz.dylib (macOS)
if [ -f zig-out/bindings/python/astroz/lib_astroz.so ]; then
cp zig-out/bindings/python/astroz/lib_astroz.so bindings/python/astroz/_astroz.so
elif [ -f zig-out/bindings/python/astroz/lib_astroz.dylib ]; then
cp zig-out/bindings/python/astroz/lib_astroz.dylib bindings/python/astroz/_astroz.so
else
echo "ERROR: Could not find built library"
exit 1
fi
- uses: pypa/cibuildwheel@v4.2.1
with:
package-dir: bindings/python

- name: Install build tools
run: pip install build wheel
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: wheelhouse/*.whl

- name: Build wheel
run: |
cd bindings/python
python -m build --wheel
# Make sure the wheels install on real Lambda images. 3.10 runs on Amazon
# Linux 2 (glibc 2.26), 3.14 on Amazon Linux 2023.
test_lambda:
name: Lambda (${{ matrix.os }}, Python ${{ matrix.python }})
needs: [build_wheels]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
python: ['3.10', '3.14']

- name: Repair wheel (Linux)
if: runner.os == 'Linux'
run: |
pip install auditwheel patchelf
auditwheel repair bindings/python/dist/*.whl -w bindings/python/dist/repaired/ || true
if [ -d "bindings/python/dist/repaired" ] && [ "$(ls -A bindings/python/dist/repaired/)" ]; then
rm bindings/python/dist/*.whl
mv bindings/python/dist/repaired/*.whl bindings/python/dist/
fi
steps:
- uses: actions/download-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: dist

- name: Test wheel
- name: Install and import in Lambda base image
run: |
pip install bindings/python/dist/*.whl
python -c "from astroz import Tle, Constellation, propagate, __version__; print(f'astroz {__version__}')"
python -c "
from astroz import Tle, propagate
tle_text = '1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995\n2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123'
tle = Tle(tle_text)
pos = propagate(tle_text, [30.0])
print(f'Position: {pos[0, 0]}')
"
python -c "
from astroz.api import Satrec, SatrecArray, jday, WGS72
import numpy as np
line1 = '1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995'
line2 = '2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123'
sat = Satrec.twoline2rv(line1, line2, WGS72)
jd, fr = jday(2024, 5, 6, 12, 0, 0.0)
e, r, v = sat.sgp4(jd, fr)
print(f'astroz.api: pos=({r[0]:.1f}, {r[1]:.1f}, {r[2]:.1f})')
arr = SatrecArray([sat])
e, r, v = arr.sgp4(np.array([jd]), np.array([fr]))
print(f'SatrecArray: shape={r.shape}')
"

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheel-${{ matrix.target }}-py${{ matrix.python }}
path: bindings/python/dist/*.whl
docker run --rm --entrypoint /bin/bash \
-v "$PWD/dist:/dist:ro" \
public.ecr.aws/lambda/python:${{ matrix.python }} \
-c 'set -e
pip install --quiet --only-binary=:all: --target /tmp/pkg numpy
pip install --quiet --no-index --no-deps --find-links /dist --target /tmp/pkg astroz
PYTHONPATH=/tmp/pkg python -c "import astroz"'

publish:
name: Publish to PyPI
needs: [build_wheels]
needs: [build_wheels, test_lambda]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')

Expand All @@ -186,7 +85,7 @@ jobs:
steps:
- uses: actions/download-artifact@v4
with:
pattern: wheel-*
pattern: wheels-*
path: dist
merge-multiple: true

Expand Down
15 changes: 15 additions & 0 deletions bindings/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ where = ["."]

[tool.setuptools.package-data]
astroz = ["*.so", "*.dylib", "*.pyd", "py.typed", "*.pyi"]

[tool.cibuildwheel]
build = "cp310-* cp311-* cp312-* cp313-* cp314-*"
skip = "*-musllinux_*"
test-command = "python -c \"import astroz\""

[tool.cibuildwheel.linux]
# glibc 2.17 so the wheel also loads on Lambda's Amazon Linux 2 runtimes (glibc 2.26)
before-all = "curl -sSL https://ziglang.org/download/0.16.0/zig-$(uname -m)-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-$(uname -m)-linux-0.16.0/zig /usr/local/bin/zig"
environment = { ASTROZ_ZIG_TARGET = "$(uname -m)-linux-gnu.2.17" }
repair-wheel-command = "auditwheel repair --plat manylinux2014_$(uname -m) -w {dest_dir} {wheel}"

[tool.cibuildwheel.macos]
archs = ["arm64"]
environment = { ASTROZ_ZIG_TARGET = "aarch64-macos.13.0", MACOSX_DEPLOYMENT_TARGET = "13.0" }
19 changes: 6 additions & 13 deletions bindings/python/setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Setup script for astroz Python bindings using Zig."""

import os
import subprocess
import sys
import sysconfig
from pathlib import Path

Expand All @@ -15,11 +15,6 @@ class ZigBuildExt(build_ext):
def build_extension(self, ext):
# Get Python configuration
python_include = sysconfig.get_path("include")
python_lib_dir = sysconfig.get_config_var("LIBDIR")

# Get Python library name (e.g., "python3.12")
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
python_lib = f"python{py_version}"

# Get the output directory
ext_fullpath = Path(self.get_ext_fullpath(ext.name))
Expand All @@ -38,13 +33,11 @@ def build_extension(self, ext):
"-Doptimize=ReleaseFast",
]

# On non-macOS platforms, link against Python library
# On macOS, Python extensions should NOT link against the Python library -
# symbols resolve at runtime when loaded by the interpreter
if sys.platform != "darwin":
cmd.append(f"-Dpython-lib={python_lib}")
if python_lib_dir and Path(python_lib_dir).exists():
cmd.append(f"-Dpython-lib-path={python_lib_dir}")
# CI sets this (e.g. x86_64-linux-gnu.2.17) so wheels don't pick up the
# build machine's glibc and CPU
zig_target = os.environ.get("ASTROZ_ZIG_TARGET")
if zig_target:
cmd.append(f"-Dtarget={zig_target}")

print(f"Building with: {' '.join(cmd)}")
subprocess.check_call(cmd, cwd=project_root)
Expand Down
16 changes: 4 additions & 12 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ pub fn build(b: *std.Build) void {
const cspice_include = b.option([]const u8, "cspice-include", "Directory containing the CSPICE headers");
const cspice_lib = b.option([]const u8, "cspice-lib", "Path to the CSPICE static archive (cspice.a)");
const python_include = b.option([]const u8, "python-include", "Directory containing Python.h");
const python_lib = b.option([]const u8, "python-lib", "Python library to link (default: python3.12)") orelse "python3.12";
const python_lib_path = b.option([]const u8, "python-lib-path", "Directory containing the Python library");

const build_options = b.addOptions();
build_options.addOption(bool, "enable_cspice", enable_cspice);
Expand Down Expand Up @@ -144,9 +142,7 @@ pub fn build(b: *std.Build) void {
//
// Example with a uv-managed interpreter:
// zig build python-bindings -Doptimize=ReleaseFast \
// -Dpython-include=$(uv run python -c "import sysconfig; print(sysconfig.get_path('include'))") \
// -Dpython-lib-path=$(uv run python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") \
// -Dpython-lib=python3.12
// -Dpython-include=$(uv run python -c "import sysconfig; print(sysconfig.get_path('include'))")
const astroz_python = b.addModule("astroz_python", .{
.root_source_file = b.path("src/lib.zig"),
.target = target,
Expand All @@ -170,19 +166,15 @@ pub fn build(b: *std.Build) void {
py_mod.addIncludePath(.{ .cwd_relative = "/usr/include/python3.12" });
py_mod.addIncludePath(.{ .cwd_relative = "/usr/include/python3" });
}
const is_macos = target.result.os.tag == .macos;
if (!is_macos) {
if (python_lib_path) |dir| py_mod.addLibraryPath(.{ .cwd_relative = dir });
py_mod.linkSystemLibrary(python_lib, .{});
}
const py_lib = b.addLibrary(.{
.linkage = .dynamic,
.name = "_astroz",
.root_module = py_mod,
.use_llvm = use_llvm,
});
// On macOS the interpreter provides the Python symbols at load time.
if (is_macos) py_lib.linker_allow_shlib_undefined = true;
// Python symbols come from the interpreter at load time; manylinux Pythons
// have no libpython.so to link against.
py_lib.linker_allow_shlib_undefined = true;
oma.addMultiVersion(oma_dep, py_lib, .{ .source = kernels_src, .pic = true });
const py_step = b.step("python-bindings", "Build the Python extension module");
py_step.dependOn(&b.addInstallArtifact(py_lib, .{
Expand Down
Loading