Skip to content

Sync MLX

Sync MLX #9

Workflow file for this run

name: Sync MLX
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
jobs:
update-mlx:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Determine MLX release state
id: mlx
run: |
git submodule update --init --recursive extern/mlx
git -C extern/mlx fetch --tags origin
LATEST_TAG=$(git -C extern/mlx for-each-ref --sort=-version:refname --format='%(refname:strip=2)' refs/tags | grep '^v' | head -n 1)
if [ -z "$LATEST_TAG" ]; then
echo "::error::Failed to determine latest MLX tag"
exit 1
fi
CURRENT_TAG=$(git -C extern/mlx describe --tags --abbrev=0 2>/dev/null || echo "")
CURRENT_COMMIT=$(git -C extern/mlx rev-parse --short HEAD 2>/dev/null || echo "")
LATEST_COMMIT=$(git -C extern/mlx rev-list -n 1 "${LATEST_TAG}")
echo "latest_tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
echo "latest_commit=${LATEST_COMMIT}" >> "$GITHUB_OUTPUT"
echo "current_tag=${CURRENT_TAG}" >> "$GITHUB_OUTPUT"
echo "current_commit=${CURRENT_COMMIT}" >> "$GITHUB_OUTPUT"
echo "latest_version=${LATEST_TAG#v}" >> "$GITHUB_OUTPUT"
if [ "$CURRENT_TAG" = "$LATEST_TAG" ]; then
echo "MLX submodule already at ${CURRENT_TAG}"
echo "update_needed=false" >> "$GITHUB_OUTPUT"
else
echo "MLX update required: ${CURRENT_TAG:-unknown} -> ${LATEST_TAG}"
echo "update_needed=true" >> "$GITHUB_OUTPUT"
fi
- name: Prepare update branch
if: steps.mlx.outputs.update_needed == 'true'
env:
BRANCH: mlx-sync/${{ steps.mlx.outputs.latest_version }}
run: git checkout -B "$BRANCH"
- name: Update MLX submodule
if: steps.mlx.outputs.update_needed == 'true'
run: |
git -C extern/mlx checkout ${{ steps.mlx.outputs.latest_tag }}
git add extern/mlx
- name: Setup Python
if: steps.mlx.outputs.update_needed == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Update package versions
if: steps.mlx.outputs.update_needed == 'true'
env:
VERSION: ${{ steps.mlx.outputs.latest_version }}
run: |
python3 <<'PY'
import os
import re
from pathlib import Path
version = os.environ["VERSION"]
props_path = Path("Directory.Build.props")
text = props_path.read_text()
text = re.sub(r"<Version>[^<]+</Version>", f"<Version>{version}</Version>", text)
text = re.sub(r"<PackageVersion>[^<]+</PackageVersion>", f"<PackageVersion>{version}</PackageVersion>", text)
props_path.write_text(text)
PY
git add Directory.Build.props
- name: Commit changes
if: steps.mlx.outputs.update_needed == 'true'
id: commit
env:
TAG: ${{ steps.mlx.outputs.latest_tag }}
run: |
if git diff --cached --quiet; then
echo "No staged changes to commit"
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "mlx: sync to ${TAG}"
echo "changed=true" >> "$GITHUB_OUTPUT"
- name: Setup .NET
if: steps.commit.outputs.changed == 'true'
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Build solution
if: steps.commit.outputs.changed == 'true'
run: |
dotnet restore
dotnet build --configuration Release --no-restore
- name: Push changes
if: steps.commit.outputs.changed == 'true'
env:
BRANCH: mlx-sync/${{ steps.mlx.outputs.latest_version }}
run: |
git push --force --set-upstream origin "$BRANCH"
- name: Create or update pull request
if: steps.commit.outputs.changed == 'true'
uses: actions/github-script@v7
env:
BRANCH: mlx-sync/${{ steps.mlx.outputs.latest_version }}
PR_TITLE: "MLX: sync to ${{ steps.mlx.outputs.latest_tag }}"
PR_BODY: |
- Update `extern/mlx` submodule to `${{ steps.mlx.outputs.latest_tag }}`
- Align package version fields to `${{ steps.mlx.outputs.latest_version }}`
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const branch = process.env.BRANCH;
const title = process.env.PR_TITLE;
const body = process.env.PR_BODY;
const owner = context.repo.owner;
const repo = context.repo.repo;
const base = context.payload.repository.default_branch;
const { data: existing } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
head: `${owner}:${branch}`,
});
if (existing.length > 0) {
const pr = existing[0];
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
title,
body,
});
core.info(`Updated PR ${pr.html_url}`);
} else {
const { data: pr } = await github.rest.pulls.create({
owner,
repo,
head: branch,
base,
title,
body,
});
core.info(`Created PR ${pr.html_url}`);
}
- name: Create tracking issue
if: steps.commit.outputs.changed == 'true'
uses: actions/github-script@v7
env:
ISSUE_TITLE: "Review bindings for MLX ${{ steps.mlx.outputs.latest_tag }}"
LATEST_TAG: ${{ steps.mlx.outputs.latest_tag }}
LATEST_COMMIT: ${{ steps.mlx.outputs.latest_commit }}
CURRENT_TAG: ${{ steps.mlx.outputs.current_tag }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueTitle = process.env.ISSUE_TITLE.replace(/\s+/g, ' ').trim();
const latestTag = process.env.LATEST_TAG;
const latestCommit = process.env.LATEST_COMMIT;
const previousTag = process.env.CURRENT_TAG && process.env.CURRENT_TAG.length > 0 ? process.env.CURRENT_TAG : 'unknown';
const compareBase = previousTag === 'unknown' ? latestTag : previousTag;
const compareUrl = `https://github.com/ml-explore/mlx/compare/${compareBase}...${latestTag}`;
const issueBody = [
`The upstream MLX submodule updated to \`${latestTag}\`.`,
'',
'Please review the upstream diff and ensure the C API (`native/include/mlxsharp/api.h`) and managed bindings stay in sync.',
'',
`- Upstream tag: \`${latestTag}\``,
`- Upstream commit: \`${latestCommit}\``,
`- Previous tag: \`${previousTag}\``,
`- Compare: ${compareUrl}`,
'',
'Track interface updates, adjust `native/src/mlxsharp.cpp`, refresh docs/tests as needed.'
].join('\n');
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: issues } = await github.rest.issues.listForRepo({
owner,
repo,
state: 'open',
per_page: 100,
});
const existing = issues.find(issue => issue.title === issueTitle);
if (existing) {
core.info(`Issue already exists: ${existing.html_url}`);
return;
}
const { data: created } = await github.rest.issues.create({
owner,
repo,
title: issueTitle,
body: issueBody,
assignees: ['copoit'],
labels: ['dependency-update'],
});
core.info(`Created issue ${created.html_url}`);