Skip to content

nlopt Update Check

nlopt Update Check #2

name: nlopt Update Check
on:
schedule:
# 04:00 UTC daily
- cron: "0 4 * * *"
workflow_dispatch:
concurrency:
cancel-in-progress: false
group: nlopt-update-check
env:
GH_TOKEN: ${{ secrets.NLOPT_UPDATE_PAT }}
jobs:
check-and-update:
name: Check for new nlopt release and update PR
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.NLOPT_UPDATE_PAT }}
fetch-depth: 0
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Check for a new nlopt release tag
id: check
run: |
set -euo pipefail
REPO_URL="https://github.com/stevengj/nlopt.git"
current_sha=$(git rev-parse HEAD:extern/nlopt)
latest_line=$(git ls-remote --tags "$REPO_URL" \
| awk '{ ref=$2; peeled=(ref ~ /\^\{\}$/); sub(/\^\{\}$/,"",ref); sub(/^refs\/tags\//,"",ref);
if (ref ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/) { if (!(ref in sha) || peeled) sha[ref]=$1 } }
END { for (t in sha) print t, sha[t] }' \
| sort -V | tail -n1)
latest_tag=$(cut -d' ' -f1 <<<"$latest_line")
latest_sha=$(cut -d' ' -f2 <<<"$latest_line")
if [[ -z "$latest_tag" ]]; then
echo "::error::Could not determine the latest nlopt release tag from $REPO_URL"
exit 1
fi
echo "Currently pinned commit: $current_sha"
echo "Latest upstream tag: $latest_tag ($latest_sha)"
if [[ "$latest_sha" == "$current_sha" ]]; then
echo "update_available=false" >> "$GITHUB_OUTPUT"
else
echo "update_available=true" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
fi
- name: Find existing bump PR
id: find-pr
if: steps.check.outputs.update_available == 'true'
run: |
set -euo pipefail
pr_number=$(gh pr list --head chore/bump-nlopt --state open --json number --jq '.[0].number // empty')
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
if [[ -n "$pr_number" ]]; then
echo "Existing open bump PR: #$pr_number"
else
echo "No existing open bump PR."
fi
- name: Sync existing bump PR with master
id: sync
if: steps.check.outputs.update_available == 'true' && steps.find-pr.outputs.pr_number != ''
env:
LATEST_TAG: ${{ steps.check.outputs.latest_tag }}
PR_NUMBER: ${{ steps.find-pr.outputs.pr_number }}
run: |
set -euo pipefail
git fetch origin
git checkout chore/bump-nlopt
before=$(git rev-parse HEAD)
if ! git merge origin/master --no-edit; then
git merge --abort
gh pr comment "$PR_NUMBER" --body "Auto-sync from \`master\` hit a conflict — please resolve manually."
echo "::error::Merge conflict syncing chore/bump-nlopt with master"
exit 1
fi
git submodule update --init extern/nlopt
git -C extern/nlopt fetch --tags --quiet
git -C extern/nlopt checkout --quiet "$LATEST_TAG"
git add extern/nlopt
if ! git diff --cached --quiet; then
git commit -m "Bump nlopt submodule to $LATEST_TAG"
gh pr edit "$PR_NUMBER" --title "Bump nlopt to $LATEST_TAG"
fi
if [[ "$(git rev-parse HEAD)" != "$before" ]]; then
git push origin chore/bump-nlopt
else
echo "chore/bump-nlopt already up to date; nothing to push."
fi
- name: Bump submodule on a fresh branch
id: bump
if: steps.check.outputs.update_available == 'true' && steps.find-pr.outputs.pr_number == ''
env:
LATEST_TAG: ${{ steps.check.outputs.latest_tag }}
run: |
set -euo pipefail
git checkout -b chore/bump-nlopt
git submodule update --init extern/nlopt
git -C extern/nlopt fetch --tags --quiet
git -C extern/nlopt checkout --quiet "$LATEST_TAG"
git add extern/nlopt
version=$(grep -oP 'NLOPT_(MAJOR|MINOR|BUGFIX)_VERSION *"\K[^"]+' extern/nlopt/CMakeLists.txt | paste -sd. -)
echo "version=$version" >> "$GITHUB_OUTPUT"
git commit -m "Bump nlopt submodule to $LATEST_TAG"
git push origin chore/bump-nlopt
- name: Open PR
if: steps.check.outputs.update_available == 'true' && steps.find-pr.outputs.pr_number == ''
env:
LATEST_TAG: ${{ steps.check.outputs.latest_tag }}
VERSION: ${{ steps.bump.outputs.version }}
run: |
set -euo pipefail
gh pr create \
--base master \
--head chore/bump-nlopt \
--title "Bump nlopt to $LATEST_TAG" \
--assignee DanielBok \
--reviewer DanielBok \
--body "$(cat <<EOF
Automated bump of the \`extern/nlopt\` submodule to upstream release [$LATEST_TAG](https://github.com/stevengj/nlopt/releases/tag/$LATEST_TAG).
Computed package version: \`$VERSION\`
- [ ] Check whether \`.github/workflows/build.yml\` / \`manual-deploy.yml\`'s OS/Python matrix needs updating for this release (see release notes)
- [ ] Review CI results on this PR (full wheel build + \`t_python.py\` test matrix)
- [ ] Optional: dry-run publish via \`manual-deploy.yml\` (target: testpypi)
- [ ] Cut a GitHub Release (tag \`v$VERSION\`) to publish to PyPI once merged
This PR is kept in sync automatically: if \`master\` changes while this is open, or a newer nlopt release appears, this branch will be updated in place rather than a new PR being opened.
EOF
)"
notify-on-failure:
name: Open/update issue on failure
needs: check-and-update
if: failure()
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Find or create failure issue
uses: actions/github-script@v7
with:
script: |
const title = "nlopt update check failing";
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = `The nlopt update check workflow failed on ${new Date().toISOString().slice(0, 10)}.\n\nSee the failed run: ${runUrl}`;
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
labels: "nlopt-update-check-failure",
});
if (issues.length > 0) {
const issue = issues[0];
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ["nlopt-update-check-failure"],
});
}