Skip to content
Open
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
68 changes: 68 additions & 0 deletions .github/workflows/s390x-wheel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: s390x Wheel (experimental)

permissions:
contents: read

on:
workflow_dispatch:
inputs:
version:
description: PyPI version to build (leave empty to build from the checked-out ref)
required: false
type: string
openssl_version:
description: OpenSSL version for static linking
required: false
default: "3.4.6"
type: string

env:
IMAGE: cryptography-s390x-builder
WHEELHOUSE: wheelhouse

jobs:
s390x-wheel:
name: s390x manylinux wheel
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- name: Set up QEMU
uses: docker/setup-qemu-action@3b5aeead8caba617bcda9a68a66745a079fc368 # v3.6.0
with:
platforms: all

- name: Build s390x builder image
run: |
docker build --platform linux/s390x \
-f docker/Dockerfile.s390x \
-t "${IMAGE}" \
.

- name: Build cryptography wheel on s390x
env:
VERSION: ${{ github.event.inputs.version }}
OPENSSL_VERSION: ${{ github.event.inputs.openssl_version }}
run: |
mkdir -p "${WHEELHOUSE}"
args=(
--rm
--platform linux/s390x
-v "$PWD:/src:ro"
-v "$PWD/${WHEELHOUSE}:/wheelhouse"
-e SOURCE_DIR=/src
-e WHEELHOUSE=/wheelhouse
-e OPENSSL_VERSION="${OPENSSL_VERSION}"
)
if [ -n "${VERSION}" ]; then
args+=(-e "VERSION=${VERSION}")
fi
docker run "${args[@]}" "${IMAGE}"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cryptography-s390x-wheel
path: wheelhouse/*.whl
63 changes: 63 additions & 0 deletions docker/Dockerfile.s390x
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Minimal s390x wheel builder for cryptography.
#
# Derived from a proven UBI9-based builder; keeps only deps required to compile
# cryptography (Rust/maturin + static OpenSSL + auditwheel), not generic
# packages like Pillow/NumPy.
#
# Build (on any host with QEMU/binfmt for s390x):
# docker build --platform linux/s390x -f docker/Dockerfile.s390x -t cryptography-s390x-builder .
#
# Or use scripts/s390x/build.sh from the repository root.

FROM registry.access.redhat.com/ubi9/ubi

ENV LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
PYTHONUNBUFFERED=1 \
CARGO_HOME=/root/.cargo \
RUSTUP_HOME=/root/.rustup \
PATH="/root/.cargo/bin:/usr/local/bin:${PATH}"

WORKDIR /io

# ---- cryptography build toolchain ----
RUN dnf install -y --allowerasing \
gcc gcc-c++ \
make \
git curl \
tar gzip xz \
autoconf automake libtool \
pkgconf-pkg-config \
python3.12 python3.12-devel python3.12-pip \
openssl-devel \
libffi-devel \
zlib-devel \
perl \
&& dnf clean all

# auditwheel needs patchelf; not packaged on UBI9.
RUN git clone --depth 1 https://github.com/NixOS/patchelf.git /tmp/patchelf \
&& cd /tmp/patchelf \
&& ./bootstrap.sh \
&& ./configure \
&& make -j"$(nproc)" \
&& make install \
&& rm -rf /tmp/patchelf

RUN python3.12 -m pip install --upgrade pip \
&& python3.12 -m pip install \
uv \
maturin \
auditwheel \
wheel \
build

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain stable

RUN mkdir -p /wheelhouse

COPY scripts/s390x/build-wheel.sh /io/build-wheel.sh
RUN chmod +x /io/build-wheel.sh

ENTRYPOINT ["/io/build-wheel.sh"]
168 changes: 168 additions & 0 deletions scripts/s390x/build-wheel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
# Build a manylinux-compatible cryptography wheel on s390x.
#
# Runs inside docker/Dockerfile.s390x. Supports:
# - local checkout mounted at SOURCE_DIR (default /src)
# - PyPI sdist via VERSION when SOURCE_DIR is absent or empty
#
# Environment:
# SOURCE_DIR Path to cryptography source tree (optional)
# VERSION PyPI version to build when not using SOURCE_DIR
# PYTHON Python executable (default: python3.12)
# WHEELHOUSE Output directory (default: /wheelhouse)
# MANYLINUX_PLAT auditwheel platform tag (default: manylinux_2_34_s390x)
# OPENSSL_VERSION OpenSSL release to build statically (default: 3.4.6)
# OPENSSL_DIR Skip OpenSSL build when already provided
# OPENSSL_STATIC Set to 1 (default) for release-style wheels

set -euo pipefail

SOURCE_DIR="${SOURCE_DIR:-/src}"
WHEELHOUSE="${WHEELHOUSE:-/wheelhouse}"
PYTHON="${PYTHON:-python3.12}"
MANYLINUX_PLAT="${MANYLINUX_PLAT:-manylinux_2_34_s390x}"
OPENSSL_VERSION="${OPENSSL_VERSION:-3.4.6}"
OPENSSL_STATIC="${OPENSSL_STATIC:-1}"
BUILD_REQUIREMENTS="${BUILD_REQUIREMENTS:-.github/requirements/build-requirements.txt}"
WORK="${WORK:-/tmp/cryptography-s390x-build}"

mkdir -p "$WHEELHOUSE"
rm -rf "$WORK"
mkdir -p "$WORK"
cd "$WORK"

echo "=== cryptography s390x wheel build ==="
echo " python: $("$PYTHON" --version)"
echo " arch: $(uname -m)"
echo " wheelhouse: $WHEELHOUSE"
echo " manylinux plat: $MANYLINUX_PLAT"
echo " openssl static: $OPENSSL_STATIC"

build_static_openssl() {
if [[ -n "${OPENSSL_DIR:-}" && -d "${OPENSSL_DIR}/include/openssl" ]]; then
echo "Using prebuilt OpenSSL at ${OPENSSL_DIR}"
return
fi

local ossl_path="${WORK}/openssl"
echo "Building static OpenSSL ${OPENSSL_VERSION} -> ${ossl_path}"

export TYPE=openssl
export VERSION="${OPENSSL_VERSION}"
export OSSL_PATH="${ossl_path}"
export CONFIG_FLAGS="${CONFIG_FLAGS:-no-shared no-ssl2 no-ssl3 no-comp no-hw no-engine no-tests}"

if [[ -f "${SOURCE_DIR}/.github/bin/build_openssl.sh" ]]; then
bash "${SOURCE_DIR}/.github/bin/build_openssl.sh"
else
curl -fsSL \
"https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" \
-o "openssl-${OPENSSL_VERSION}.tar.gz"
tar xzf "openssl-${OPENSSL_VERSION}.tar.gz"
pushd "openssl-${OPENSSL_VERSION}"
sed -i "s/^SHLIB_VERSION=.*/SHLIB_VERSION=100/" VERSION.dat
./config ${CONFIG_FLAGS} -fPIC --prefix="${OSSL_PATH}"
make depend
make -j"$(nproc)"
make install_sw install_ssldirs
rm -rf "${OSSL_PATH}/bin"
popd
fi

export OPENSSL_DIR="${ossl_path}"
}

resolve_source_tree() {
if [[ -d "${SOURCE_DIR}" && -f "${SOURCE_DIR}/pyproject.toml" ]]; then
echo "Using local source tree: ${SOURCE_DIR}"
cp -a "${SOURCE_DIR}/." "${WORK}/cryptography-src/"
SRC="${WORK}/cryptography-src"
return
fi

if [[ -z "${VERSION:-}" ]]; then
echo "ERROR: set SOURCE_DIR to a checkout or VERSION for a PyPI release." >&2
exit 1
fi

echo "Downloading cryptography==${VERSION} from PyPI"
"$PYTHON" -m pip download --no-binary ":all:" --no-deps "cryptography==${VERSION}"
local archive
archive="$(ls cryptography-*.tar.gz | head -n1)"
tar xzf "${archive}"
SRC="$(find . -maxdepth 1 -type d -name 'cryptography-*' | head -n1)"
}

build_wheel() {
local src="$1"
local constraints=()
local out="${WORK}/dist"
local python_bin
local py_config

mkdir -p "$out"

if [[ -f "${src}/${BUILD_REQUIREMENTS}" ]]; then
constraints=(--require-hashes --build-constraint="${src}/${BUILD_REQUIREMENTS}")
fi

cd "$src"

python_bin="$(readlink -f "$(command -v "$PYTHON")")"
py_config="$(command -v "$(basename "$python_bin")-config" || true)"
if [[ -x "$py_config" ]]; then
export CFLAGS="${CFLAGS:-} $("$py_config" --includes)"
export LDFLAGS="${LDFLAGS:-} $("$py_config" --ldflags)"
fi

export OPENSSL_DIR="${OPENSSL_DIR:-}"
export OPENSSL_STATIC="${OPENSSL_STATIC}"
export PYO3_PYTHON="${python_bin}"

if command -v uv >/dev/null 2>&1; then
uv build --wheel "${constraints[@]}" --python "${python_bin}" -o "$out" .
else
"$python_bin" -m build --wheel -o "$out" .
fi

local wheel
wheel="$(ls "$out"/cryptography-*.whl | head -n1)"
echo "Built wheel: ${wheel}"

auditwheel repair --plat "${MANYLINUX_PLAT}" "${wheel}" -w "$WHEELHOUSE"
}

smoketest_wheel() {
local wheel
local src="${WORK}/cryptography-src"
wheel="$(ls "${WHEELHOUSE}"/cryptography-*.whl | tail -n1)"

"$PYTHON" -m venv "${WORK}/venv"
# shellcheck disable=SC1091
source "${WORK}/venv/bin/activate"
pip install -U pip

if [[ -f "${src}/${BUILD_REQUIREMENTS}" ]]; then
pip install --require-hashes -r "${src}/${BUILD_REQUIREMENTS}"
else
pip install "cffi>=2.0.0"
fi

pip install --no-index --no-deps "${wheel}"

python - <<'EOF'
from cryptography.hazmat.backends.openssl.backend import backend

print("Smoketest OK")
print("Loaded:", backend.openssl_version_text())
EOF
}

resolve_source_tree
build_static_openssl
build_wheel "${SRC}"
smoketest_wheel

echo
echo "=== Wheel ready ==="
ls -lh "${WHEELHOUSE}"/cryptography-*.whl
69 changes: 69 additions & 0 deletions scripts/s390x/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Build cryptography s390x wheels using docker + QEMU.
#
# Examples:
# # From this checkout (recommended while developing the contribution):
# ./scripts/s390x/build.sh
#
# # From a tagged PyPI release:
# VERSION=44.0.0 ./scripts/s390x/build.sh
#
# # Custom output directory:
# WHEELHOUSE=$PWD/wheelhouse ./scripts/s390x/build.sh
#
# Environment:
# IMAGE Docker image tag (default: cryptography-s390x-builder)
# SETUP_QEMU Run setup-qemu.sh first when 1 (default on non-s390x hosts)
# VERSION PyPI version when not building from checkout
# OPENSSL_VERSION Passed through to build-wheel.sh
# PYTHON Python executable inside the container

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"

IMAGE="${IMAGE:-cryptography-s390x-builder}"
WHEELHOUSE="${WHEELHOUSE:-${ROOT}/wheelhouse}"
PYTHON="${PYTHON:-python3.12}"
HOST_ARCH="$(uname -m)"

mkdir -p "$WHEELHOUSE"

if [[ "${SETUP_QEMU:-}" == "1" || ( "${HOST_ARCH}" != "s390x" && "${SETUP_QEMU:-}" != "0" ) ]]; then
echo "==> Host arch is ${HOST_ARCH}; ensuring QEMU binfmt for s390x"
bash "${ROOT}/scripts/s390x/setup-qemu.sh"
fi

echo "==> Building ${IMAGE} for linux/s390x"
docker build --platform linux/s390x \
-f docker/Dockerfile.s390x \
-t "${IMAGE}" \
.

RUN_ARGS=(
--rm
--platform linux/s390x
-v "${ROOT}:/src:ro"
-v "${WHEELHOUSE}:/wheelhouse"
-e "SOURCE_DIR=/src"
-e "WHEELHOUSE=/wheelhouse"
-e "PYTHON=${PYTHON}"
)

if [[ -n "${VERSION:-}" ]]; then
RUN_ARGS+=(-e "VERSION=${VERSION}")
fi
if [[ -n "${OPENSSL_VERSION:-}" ]]; then
RUN_ARGS+=(-e "OPENSSL_VERSION=${OPENSSL_VERSION}")
fi
if [[ -n "${MANYLINUX_PLAT:-}" ]]; then
RUN_ARGS+=(-e "MANYLINUX_PLAT=${MANYLINUX_PLAT}")
fi

echo "==> Running s390x wheel build"
docker run "${RUN_ARGS[@]}" "${IMAGE}"

echo
echo "Done. Wheels:"
ls -lh "${WHEELHOUSE}"/cryptography-*.whl
20 changes: 20 additions & 0 deletions scripts/s390x/setup-qemu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Register QEMU user-mode handlers so docker can run linux/s390x images
# on x86_64, aarch64, or macOS hosts.
#
# Usage: scripts/s390x/setup-qemu.sh

set -euo pipefail

if ! command -v docker >/dev/null 2>&1; then
echo "ERROR: docker is required." >&2
exit 1
fi

echo "==> Installing binfmt handlers via tonistiigi/binfmt"
docker run --privileged --rm tonistiigi/binfmt --install all

echo "==> Verifying s390x emulation"
docker run --rm --platform linux/s390x quay.io/pypa/manylinux_2_28_s390x uname -m

echo "QEMU s390x is ready."
Loading