Skip to content

Build Wheel (macOS) #53

Build Wheel (macOS)

Build Wheel (macOS) #53

# Copyright (c) 2026 Huawei Technologies Co., Ltd.
# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
# CANN Open Software License Agreement Version 2.0 (the "License").
# Please refer to the License for details. You may not use this file except in compliance with the License.
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
# See LICENSE in the root of the software repository for the full text of the License.
name: Build Wheel (macOS)
on:
push:
tags-ignore:
- '**'
# Nightly build & publish (UTC time, offset from Linux workflow).
schedule:
- cron: "40 17 * * *"
workflow_dispatch:
release:
types: [prereleased, released]
permissions:
contents: write
env:
LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git
LLVM_COMMIT: b7c83f68bf84125e696a70bec4b665c70a3b584d
jobs:
build_wheel:
name: Build wheel (Python ${{ matrix.python }}, ${{ matrix.arch }})
runs-on: ${{ matrix.arch == 'x86_64' && 'macos-15-intel' || 'macos-26' }}
strategy:
fail-fast: false
matrix:
python: ["3.10", "3.11", "3.12"]
arch: ["x86_64", "aarch64"]
# Keep macOS matrix jobs at 5 to stay within GitHub Actions macOS job limits.
exclude:
- python: "3.10"
arch: "x86_64"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Select runner metadata
id: runner-meta
run: |
if [ "${{ matrix.arch }}" = "x86_64" ]; then
echo "label=macos-15-intel" >> "$GITHUB_OUTPUT"
else
echo "label=macos-26" >> "$GITHUB_OUTPUT"
fi
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Set Python path
run: |
echo "PY_PATH=$(python -c 'import sys; print(sys.prefix)')" >> $GITHUB_ENV
- name: Compute PTOAS CLI version
run: |
if [ "${GITHUB_EVENT_NAME}" = "release" ]; then
EXPECTED_RELEASE_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode release)"
PTOAS_VERSION="${GITHUB_REF_NAME#v}"
if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then
echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2
exit 1
fi
else
PTOAS_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode dev)"
fi
echo "PTOAS_VERSION=${PTOAS_VERSION}" >> "$GITHUB_ENV"
echo "Using PTOAS CLI version: ${PTOAS_VERSION}"
- name: Install system dependencies
run: |
brew install ninja ccache
- name: Install Python dependencies
run: |
pip install --no-cache-dir numpy 'nanobind>=2.9,<3' typing_extensions setuptools wheel delocate
- name: Set build directories
run: |
echo "WORKSPACE_DIR=${RUNNER_TEMP}/llvm-workspace" >> $GITHUB_ENV
echo "LLVM_SOURCE_DIR=${RUNNER_TEMP}/llvm-workspace/llvm-project" >> $GITHUB_ENV
echo "LLVM_BUILD_DIR=${RUNNER_TEMP}/llvm-workspace/llvm-project/build-shared" >> $GITHUB_ENV
echo "PTO_SOURCE_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV
echo "PTO_INSTALL_DIR=$GITHUB_WORKSPACE/install" >> $GITHUB_ENV
- name: Restore LLVM build cache (exact key)
id: cache-llvm
uses: actions/cache/restore@v4
with:
path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared
key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2
- name: Prepare LLVM source
run: |
mkdir -p $LLVM_SOURCE_DIR
cd $LLVM_SOURCE_DIR
if [ ! -d .git ]; then
git init
git remote add origin ${{ env.LLVM_REPOSITORY }}
fi
git remote set-url origin ${{ env.LLVM_REPOSITORY }}
git fetch --depth 1 origin ${{ env.LLVM_COMMIT }}
git checkout --detach ${{ env.LLVM_COMMIT }}
test "$(git rev-parse HEAD)" = "${{ env.LLVM_COMMIT }}"
- name: Warn when default-branch cache is missing for PR/release runs
if: steps.cache-llvm.outputs.cache-hit != 'true' && (github.event_name == 'pull_request' || github.event_name == 'release')
run: |
echo "LLVM cache miss while running on PR/release."
echo "PR/release runs are cache-consumers only and should reuse cache generated on the default branch."
echo "Please run this workflow on the default branch first to populate cache key:"
echo "llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2"
- name: Build LLVM/MLIR
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
cd $LLVM_SOURCE_DIR
cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \
-DLLVM_ENABLE_PROJECTS="mlir;clang" \
-DMLIR_ENABLE_LINXISA_PTO=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF \
-DBUILD_SHARED_LIBS=ON \
-DMLIR_ENABLE_BINDINGS_PYTHON=ON \
-DPython3_EXECUTABLE=$(which python) \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD="host"
ninja -C $LLVM_BUILD_DIR
# Upload cache immediately after the build step, to avoid `build-shared` directory being polluted by later steps.
- name: Save LLVM cache
# Allow cache writes except for PR/release runs to avoid duplicated tag/merge caches.
if: steps.cache-llvm.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'release'
uses: actions/cache/save@v4
with:
path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared
key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2
- name: Build PTOAS
run: |
cd $PTO_SOURCE_DIR
cmake -G Ninja \
-S . \
-B build \
-DLLVM_DIR=$LLVM_BUILD_DIR/lib/cmake/llvm \
-DMLIR_DIR=$LLVM_BUILD_DIR/lib/cmake/mlir \
-DPython3_ROOT_DIR=${PY_PATH} \
-DPython3_EXECUTABLE=$(which python) \
-DPython_EXECUTABLE=$(which python) \
-DPython3_FIND_STRATEGY=LOCATION \
-Dnanobind_DIR=$(python -m nanobind --cmake_dir) \
-DMLIR_PYTHON_PACKAGE_DIR=${LLVM_BUILD_DIR}/tools/mlir/python_packages/mlir_core \
-DPTOAS_RELEASE_VERSION_OVERRIDE=${PTOAS_VERSION} \
-DPTOAS_ENABLE_WERROR=OFF \
-DCMAKE_INSTALL_PREFIX=${PTO_INSTALL_DIR} \
-DCMAKE_BUILD_TYPE=Release
ninja -C build
ninja -C build install
- name: Create Python wheel
run: |
export PY_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core
export PTOAS_PYTHON_PACKAGE_VERSION="${PTOAS_VERSION}"
if [ "${{ matrix.arch }}" = "x86_64" ]; then
TARGET_ARCH="x86_64"
else
TARGET_ARCH="arm64"
fi
# setup-python may provide a universal2 interpreter on macOS.
# Force a single-arch wheel tag to match the matrix target and delocate checks.
export WHEEL_PLAT_NAME=$(python "$PTO_SOURCE_DIR/docker/get_macos_wheel_plat_name.py" "$TARGET_ARCH")
bash $PTO_SOURCE_DIR/docker/create_wheel.sh
shopt -s nullglob
built_wheels=("$PY_PACKAGE_DIR"/dist/ptoas*.whl)
if [ "${#built_wheels[@]}" -gt 0 ]; then
printf 'Built wheel file: %s\n' "$(basename "${built_wheels[0]}")"
fi
- name: Repair wheel with delocate
run: |
set -euo pipefail
export PY_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core
cd $PY_PACKAGE_DIR
if [ "${{ matrix.arch }}" = "x86_64" ]; then
REQUIRED_ARCH="x86_64"
else
REQUIRED_ARCH="arm64"
fi
shopt -s nullglob
wheels=(dist/ptoas*.whl)
if [ "${#wheels[@]}" -ne 1 ]; then
echo "Expected exactly one wheel in dist/, found ${#wheels[@]}:"
printf ' %s\n' "${wheels[@]}"
exit 1
fi
echo "Runner label: ${{ steps.runner-meta.outputs.label }}"
echo "Wheel size:"
du -sh "${wheels[0]}"
echo "dist contents:"
ls -lh dist
echo "delocate dependency summary:"
delocate-listdeps "${wheels[0]}" || true
delocate-wheel \
--require-archs "${REQUIRED_ARCH}" \
--wheel-dir wheelhouse \
"${wheels[0]}" &
delocate_pid=$!
(
sleep 4500
if kill -0 "$delocate_pid" 2>/dev/null; then
echo "ERROR: delocate-wheel exceeded 75 minutes, terminating pid ${delocate_pid}"
kill -TERM "$delocate_pid" 2>/dev/null || true
sleep 15
kill -KILL "$delocate_pid" 2>/dev/null || true
fi
) &
watchdog_pid=$!
while kill -0 "$delocate_pid" 2>/dev/null; do
echo "[$(date '+%Y-%m-%d %H:%M:%S')] delocate still running (pid=${delocate_pid})"
ps -o pid=,ppid=,%cpu=,%mem=,etime=,command= -p "$delocate_pid" || true
du -sh dist wheelhouse 2>/dev/null || true
sleep 60
done
set +e
wait "$delocate_pid"
rc=$?
set -e
kill "$watchdog_pid" 2>/dev/null || true
wait "$watchdog_pid" 2>/dev/null || true
if [ "$rc" -ne 0 ]; then
echo "delocate-wheel failed with rc=${rc}"
echo "Active Python processes:"
ps -ax -o pid=,ppid=,%cpu=,%mem=,etime=,command= | grep -E '[d]elocate|[p]ython' || true
echo "Filesystem usage:"
df -h .
echo "dist contents after failure:"
ls -lh dist || true
echo "wheelhouse contents after failure:"
ls -lh wheelhouse || true
exit "$rc"
fi
echo "wheelhouse contents:"
ls -lh wheelhouse
- name: Test wheel installation
run: |
export PY_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core
pip install $PY_PACKAGE_DIR/wheelhouse/ptoas*.whl
export DYLD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:${DYLD_LIBRARY_PATH}
bash $PTO_SOURCE_DIR/docker/test_wheel_imports.sh
- name: Test ptoas CLI
run: |
export DYLD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:${DYLD_LIBRARY_PATH}
bash $PTO_SOURCE_DIR/docker/test_ptoas_cli.sh
- name: Copy wheel to workspace
run: |
export PY_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core
mkdir -p $GITHUB_WORKSPACE/wheelhouse
cp $PY_PACKAGE_DIR/wheelhouse/ptoas*.whl $GITHUB_WORKSPACE/wheelhouse/
- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: ptoas-wheel-macos-py${{ matrix.python }}-${{ matrix.arch }}
path: wheelhouse/*.whl
- name: Collect ptoas binary and dependencies
if: matrix.python == '3.11'
run: |
bash "$PTO_SOURCE_DIR/docker/collect_ptoas_dist_mac.sh" "$GITHUB_WORKSPACE/ptoas-dist"
- name: Archive ptoas binary artifact
if: matrix.python == '3.11'
run: |
chmod +x "$GITHUB_WORKSPACE/ptoas-dist/ptoas" "$GITHUB_WORKSPACE/ptoas-dist/bin/ptoas"
tar -czf "$GITHUB_WORKSPACE/ptoas-bin-macos-${{ matrix.arch }}.tar.gz" \
-C "$GITHUB_WORKSPACE/ptoas-dist" .
- name: Smoke test archived ptoas binary artifact
if: matrix.python == '3.11'
run: |
TEST_DIR="$RUNNER_TEMP/ptoas-dist-smoke-${{ matrix.arch }}"
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR/extracted"
tar -xzf "$GITHUB_WORKSPACE/ptoas-bin-macos-${{ matrix.arch }}.tar.gz" -C "$TEST_DIR/extracted"
test -x "$TEST_DIR/extracted/ptoas"
test -x "$TEST_DIR/extracted/bin/ptoas"
env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \
"$TEST_DIR/extracted/ptoas" --version
env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \
"$TEST_DIR/extracted/ptoas" \
"$PTO_SOURCE_DIR/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto" \
>/dev/null
- name: Smoke test wheel imports after collecting artifacts
if: matrix.python == '3.11'
run: |
# Test the copied wheel artifact from an isolated env with no build-tree
# paths, so post-collect/release regressions (e.g. ARM cs_invalid_page)
# are caught before upload.
brew install uv
uv python install 3.11
TEST_DIR="$RUNNER_TEMP/wheel-smoke-test"
VENV_DIR="$TEST_DIR/.venv"
mkdir -p "$TEST_DIR"
cd "$TEST_DIR"
uv venv --python 3.11 "$VENV_DIR"
source "$VENV_DIR/bin/activate"
unset PYTHONPATH
unset DYLD_LIBRARY_PATH
unset LD_LIBRARY_PATH
shopt -s nullglob
wheels=("$GITHUB_WORKSPACE"/wheelhouse/ptoas*.whl)
if [ "${#wheels[@]}" -ne 1 ]; then
echo "Expected exactly one wheel in \$GITHUB_WORKSPACE/wheelhouse, found ${#wheels[@]}:"
printf ' %s\n' "${wheels[@]}"
exit 1
fi
uv pip install "${wheels[0]}"
if [ "${{ matrix.arch }}" = "aarch64" ]; then
DYLD_PRINT_LIBRARIES=1 python -c "import mlir.ir; print('mlir.ir imported successfully')"
DYLD_PRINT_LIBRARIES=1 python -c "from mlir.dialects import pto; print('pto dialect imported successfully')"
else
python -c "import mlir.ir; print('mlir.ir imported successfully')"
python -c "from mlir.dialects import pto; print('pto dialect imported successfully')"
fi
- name: Upload ptoas binary artifact
if: matrix.python == '3.11'
uses: actions/upload-artifact@v4
with:
name: ptoas-bin-macos-${{ matrix.arch }}
path: ptoas-bin-macos-${{ matrix.arch }}.tar.gz
upload_release_assets:
name: Upload release assets
if: github.event_name == 'release' || github.event_name == 'schedule'
needs: build_wheel
runs-on: ubuntu-latest
steps:
- name: Compute release metadata
run: |
if [ "${GITHUB_EVENT_NAME}" = "release" ]; then
echo "RELEASE_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
echo "RELEASE_NAME=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
echo "RELEASE_PRERELEASE=false" >> "$GITHUB_ENV"
echo "RELEASE_MAKE_LATEST=true" >> "$GITHUB_ENV"
{
echo "RELEASE_BODY<<EOF"
echo "Published release assets for ${GITHUB_REF_NAME}."
echo "EOF"
} >> "$GITHUB_ENV"
else
echo "RELEASE_TAG=nightly" >> "$GITHUB_ENV"
echo "RELEASE_NAME=Nightly Build" >> "$GITHUB_ENV"
echo "RELEASE_PRERELEASE=true" >> "$GITHUB_ENV"
echo "RELEASE_MAKE_LATEST=false" >> "$GITHUB_ENV"
{
echo "RELEASE_BODY<<EOF"
echo "Automated nightly build."
echo ""
echo "- Commit: ${GITHUB_SHA}"
echo "- Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
echo "- Date (UTC): $(date -u +'%Y-%m-%d %H:%M:%S')"
echo "EOF"
} >> "$GITHUB_ENV"
fi
- name: Download all wheel artifacts
uses: actions/download-artifact@v4
with:
pattern: ptoas-wheel-macos-*
path: release-artifacts
merge-multiple: true
- name: Download x86_64 binary artifact
uses: actions/download-artifact@v4
with:
name: ptoas-bin-macos-x86_64
path: release-artifacts/ptoas-bin-macos-x86_64
- name: Download aarch64 binary artifact
uses: actions/download-artifact@v4
with:
name: ptoas-bin-macos-aarch64
path: release-artifacts/ptoas-bin-macos-aarch64
- name: Collect packaged ptoas binaries
run: |
set -euo pipefail
mv "release-artifacts/ptoas-bin-macos-x86_64/ptoas-bin-macos-x86_64.tar.gz" \
"release-artifacts/ptoas-bin-macos-x86_64.tar.gz"
mv "release-artifacts/ptoas-bin-macos-aarch64/ptoas-bin-macos-aarch64.tar.gz" \
"release-artifacts/ptoas-bin-macos-aarch64.tar.gz"
- name: Upload assets to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_NAME }}
target_commitish: ${{ github.sha }}
prerelease: ${{ env.RELEASE_PRERELEASE }}
make_latest: ${{ env.RELEASE_MAKE_LATEST }}
body: ${{ env.RELEASE_BODY }}
overwrite_files: true
files: |
release-artifacts/*.whl
release-artifacts/*.tar.gz