ebeddfb308
In order to make testing and deployment simpler and more reproducible, we want to provide a Nix derivation for our macOS .app build. We already build the Rust and dashboard with Nix, but so far the Python has been blocked because we haven't had an MLX build. This change adds a Metal compiler derivation that uses `requireFile` to be provided a NAR of the unfree macOS Metal compiler. It is documented how to get this file, but effectively you have to trigger the download, mount the DMG, and NAR the result. Once this is added to the store by hash we can build MLX using it. The MLX build itself is quite self explanatory. Test plan: - CI. We follow the instructions to grab the Metal compiler. Once this is in Cachix we should really never do this again, and I can pin the path too to ensure it doesn't leave. - MLX tests run as part of the MLX derivation's build. They pass. - `NIXPKGS_ALLOW_UNFREE=1 nix build .#mlx.passthru.tests.mlxTest --impure --option sandbox false` --------- Co-authored-by: Ryuichi Leo Takashige <leo@exolabs.net>
194 lines
6.6 KiB
YAML
194 lines
6.6 KiB
YAML
name: ci-pipeline
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
branches:
|
|
- staging
|
|
- main
|
|
|
|
jobs:
|
|
typecheck:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
lfs: false
|
|
|
|
- uses: cachix/install-nix-action@v31
|
|
with:
|
|
nix_path: nixpkgs=channel:nixos-unstable
|
|
|
|
- uses: cachix/cachix-action@v14
|
|
name: Configure Cachix
|
|
with:
|
|
name: exo
|
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
|
|
- name: Configure git user
|
|
run: |
|
|
git config --local user.email "github-actions@users.noreply.github.com"
|
|
git config --local user.name "github-actions bot"
|
|
shell: bash
|
|
|
|
- name: Pull LFS files
|
|
run: |
|
|
echo "Pulling Git LFS files..."
|
|
git lfs pull
|
|
shell: bash
|
|
|
|
- name: Setup Nix Environment
|
|
run: |
|
|
echo "Checking for nix installation..."
|
|
|
|
# Check if nix binary exists directly
|
|
if [ -f /nix/var/nix/profiles/default/bin/nix ]; then
|
|
echo "Found nix binary at /nix/var/nix/profiles/default/bin/nix"
|
|
export PATH="/nix/var/nix/profiles/default/bin:$PATH"
|
|
echo "PATH=$PATH" >> $GITHUB_ENV
|
|
nix --version
|
|
elif [ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]; then
|
|
echo "Found nix profile script, sourcing..."
|
|
source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
nix --version
|
|
elif command -v nix >/dev/null 2>&1; then
|
|
echo "Nix already in PATH"
|
|
nix --version
|
|
else
|
|
echo "Nix not found. Debugging info:"
|
|
echo "Contents of /nix/var/nix/profiles/default/:"
|
|
ls -la /nix/var/nix/profiles/default/ 2>/dev/null || echo "Directory not found"
|
|
echo "Contents of /nix/var/nix/profiles/default/bin/:"
|
|
ls -la /nix/var/nix/profiles/default/bin/ 2>/dev/null || echo "Directory not found"
|
|
exit 1
|
|
fi
|
|
shell: bash
|
|
|
|
- name: Configure basedpyright include for local MLX
|
|
run: |
|
|
RUNNER_LABELS='${{ toJSON(runner.labels) }}'
|
|
if echo "$RUNNER_LABELS" | grep -q "local_mlx"; then
|
|
if [ -d "/Users/Shared/mlx" ]; then
|
|
echo "Updating [tool.basedpyright].include to use /Users/Shared/mlx"
|
|
awk '
|
|
BEGIN { in=0 }
|
|
/^\[tool\.basedpyright\]/ { in=1; print; next }
|
|
in && /^\[/ { in=0 } # next section
|
|
in && /^[ \t]*include[ \t]*=/ {
|
|
print "include = [\"/Users/Shared/mlx\"]"
|
|
next
|
|
}
|
|
{ print }
|
|
' pyproject.toml > pyproject.toml.tmp && mv pyproject.toml.tmp pyproject.toml
|
|
|
|
echo "New [tool.basedpyright] section:"
|
|
sed -n '/^\[tool\.basedpyright\]/,/^\[/p' pyproject.toml | sed '$d' || true
|
|
else
|
|
echo "local_mlx tag present but /Users/Shared/mlx not found; leaving pyproject unchanged."
|
|
fi
|
|
else
|
|
echo "Runner does not have 'local_mlx' tag; leaving pyproject unchanged."
|
|
fi
|
|
shell: bash
|
|
|
|
- uses: ./.github/actions/typecheck
|
|
|
|
nix:
|
|
name: Build and check (${{ matrix.system }})
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- runner: macos-26
|
|
system: aarch64-darwin
|
|
- runner: ubuntu-latest
|
|
system: x86_64-linux
|
|
- runner: ubuntu-24.04-arm
|
|
system: aarch64-linux
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
lfs: false
|
|
|
|
- uses: cachix/install-nix-action@v31
|
|
with:
|
|
nix_path: nixpkgs=channel:nixos-unstable
|
|
|
|
- uses: cachix/cachix-action@v14
|
|
name: Configure Cachix
|
|
with:
|
|
name: exo
|
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
|
|
- name: Build Metal packages (macOS only)
|
|
if: runner.os == 'macOS'
|
|
run: |
|
|
# Try to build metal-toolchain first (may succeed via cachix cache hit)
|
|
if nix build .#metal-toolchain 2>/dev/null; then
|
|
echo "metal-toolchain built successfully (likely cache hit)"
|
|
else
|
|
echo "metal-toolchain build failed, extracting from Xcode..."
|
|
|
|
NAR_HASH="sha256-ayR5mXN4sZAddwKEG2OszGRF93k9ZFc7H0yi2xbylQw="
|
|
NAR_NAME="metal-toolchain-17C48.nar"
|
|
|
|
# Use RUNNER_TEMP to avoid /tmp symlink issues on macOS
|
|
WORK_DIR="${RUNNER_TEMP}/metal-work"
|
|
mkdir -p "$WORK_DIR"
|
|
|
|
# Download the Metal toolchain component
|
|
xcodebuild -downloadComponent MetalToolchain
|
|
|
|
# Find and mount the DMG
|
|
DMG_PATH=$(find /System/Library/AssetsV2/com_apple_MobileAsset_MetalToolchain -name '*.dmg' 2>/dev/null | head -1)
|
|
if [ -z "$DMG_PATH" ]; then
|
|
echo "Error: Could not find Metal toolchain DMG"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found DMG at: $DMG_PATH"
|
|
hdiutil attach "$DMG_PATH" -mountpoint "${WORK_DIR}/metal-dmg"
|
|
|
|
# Copy the toolchain
|
|
cp -R "${WORK_DIR}/metal-dmg/Metal.xctoolchain" "${WORK_DIR}/metal-export"
|
|
hdiutil detach "${WORK_DIR}/metal-dmg"
|
|
|
|
# Create NAR and add to store
|
|
nix nar pack "${WORK_DIR}/metal-export" > "${WORK_DIR}/${NAR_NAME}"
|
|
STORE_PATH=$(nix store add --mode flat "${WORK_DIR}/${NAR_NAME}")
|
|
echo "Added NAR to store: $STORE_PATH"
|
|
|
|
# Verify the hash matches
|
|
ACTUAL_HASH=$(nix hash file "${WORK_DIR}/${NAR_NAME}")
|
|
if [ "$ACTUAL_HASH" != "$NAR_HASH" ]; then
|
|
echo "Warning: NAR hash mismatch!"
|
|
echo "Expected: $NAR_HASH"
|
|
echo "Actual: $ACTUAL_HASH"
|
|
echo "The metal-toolchain.nix may need updating"
|
|
fi
|
|
|
|
# Clean up
|
|
rm -rf "$WORK_DIR"
|
|
|
|
# Retry the build now that NAR is in store
|
|
nix build .#metal-toolchain
|
|
fi
|
|
|
|
# Build mlx (depends on metal-toolchain)
|
|
nix build .#mlx
|
|
|
|
- name: Build all Nix outputs
|
|
run: |
|
|
nix flake show --json | jq -r '
|
|
[
|
|
(.packages."${{ matrix.system }}" // {} | keys[] | ".#packages.${{ matrix.system }}.\(.)"),
|
|
(.devShells."${{ matrix.system }}" // {} | keys[] | ".#devShells.${{ matrix.system }}.\(.)")
|
|
] | .[]
|
|
' | xargs nix build
|
|
|
|
- name: Run nix flake check
|
|
run: nix flake check
|