Skip to content

Commit 22e3399

Browse files
Copilotjens-maus
andauthored
Automate dependency update PR generation across thinRoot updater scripts (#68)
* Refactor updater scripts for automation Co-authored-by: Jens Maus <j.maus@hzdr.de>
1 parent c04a56f commit 22e3399

10 files changed

Lines changed: 588 additions & 82 deletions
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# thinRoot dependency update automation
2+
# yamllint disable rule:truthy
3+
---
4+
name: Dependency Updates
5+
6+
on:
7+
schedule:
8+
- cron: '0 2 * * *' # run daily at 02:00 UTC
9+
workflow_dispatch:
10+
11+
concurrency:
12+
group: dependency-updates-${{ github.repository }}
13+
cancel-in-progress: true
14+
15+
permissions:
16+
contents: write
17+
pull-requests: write
18+
19+
jobs:
20+
dependency-updates:
21+
name: check (${{ matrix.slug }})
22+
if: ${{ github.repository == 'jens-maus/thinRoot' }}
23+
runs-on: ubuntu-24.04
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
include:
28+
- slug: buildroot
29+
script: scripts/update-buildroot.sh
30+
package: buildroot
31+
version_file: Makefile
32+
version_regex: '^BUILDROOT_VERSION=(.*)$'
33+
- slug: hotkeyd
34+
script: scripts/update-hotkeyd.sh
35+
package: hotkeyd
36+
version_file: buildroot-external/package/hotkeyd/hotkeyd.mk
37+
version_regex: '^HOTKEYD_VERSION = (.*)$'
38+
- slug: kernel-upstream
39+
script: scripts/update-kernel-upstream.sh
40+
package: linux
41+
version_file: buildroot-external/configs/generic-x86_64.config
42+
version_regex: '^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="([^"]+)"$'
43+
- slug: qutselect
44+
script: scripts/update-qutselect.sh
45+
package: qutselect
46+
version_file: buildroot-external/package/qutselect/qutselect.mk
47+
version_regex: '^QUTSELECT_VERSION = (.*)$'
48+
- slug: rpi-eeprom
49+
script: scripts/update-rpi-eeprom.sh
50+
package: rpi-eeprom
51+
version_file: buildroot-external/package/rpi-eeprom/rpi-eeprom.mk
52+
version_regex: '^RPI_EEPROM_VERSION = (.*)$'
53+
- slug: rpi-firmware
54+
script: scripts/update-rpi-firmware.sh
55+
package: rpi-firmware
56+
version_file: buildroot-external/configs/rpi4.config
57+
version_regex: '^BR2_PACKAGE_RPI_FIRMWARE_VERSION="([^"]+)"$'
58+
- slug: rpi-kernel
59+
script: scripts/update-rpi-kernel.sh
60+
package: linux-rpi
61+
version_file: buildroot-external/configs/rpi4.config
62+
version_regex: '^BR2_LINUX_KERNEL_CUSTOM_TARBALL_LOCATION="https://github\.com/raspberrypi/linux/archive/([^"]+)\.tar\.gz"$'
63+
- slug: thinlinc
64+
script: scripts/update-thinlinc.sh
65+
package: thinlinc
66+
version_file: buildroot-external/package/thinlinc/thinlinc.mk
67+
version_regex: '^THINLINC_VERSION = (.*)$'
68+
69+
steps:
70+
- name: Checkout code
71+
uses: actions/checkout@v7
72+
with:
73+
persist-credentials: false
74+
75+
- name: Install workflow dependencies
76+
run: |
77+
sudo apt-get update
78+
sudo apt-get install -y --no-install-recommends jq
79+
80+
- name: Run dependency update script
81+
run: ./${{ matrix.script }}
82+
83+
- name: Check for updated files
84+
id: changes
85+
run: |
86+
if git diff --quiet; then
87+
echo "changed=false" >>"${GITHUB_OUTPUT}"
88+
else
89+
echo "changed=true" >>"${GITHUB_OUTPUT}"
90+
fi
91+
92+
- name: Resolve package and version metadata
93+
id: metadata
94+
if: steps.changes.outputs.changed == 'true'
95+
run: |
96+
previous_version=$(git --no-pager show "HEAD:${{ matrix.version_file }}" \
97+
| sed -nE 's/${{ matrix.version_regex }}/\1/p' \
98+
| head -n1)
99+
version=$(sed -nE 's/${{ matrix.version_regex }}/\1/p' '${{ matrix.version_file }}' | head -n1)
100+
if [[ -z "${version}" ]]; then
101+
version=$(git --no-pager diff -- '${{ matrix.version_file }}' \
102+
| sed -nE 's/^\+[^+]*[[:space:]=]+([0-9a-f]{40})$/\1/p' \
103+
| head -n1)
104+
fi
105+
106+
if [[ "${version}" =~ ^[0-9a-f]{40}$ ]]; then
107+
version="${version:0:7}"
108+
fi
109+
110+
if [[ "${previous_version}" =~ ^[0-9a-f]{40}$ ]]; then
111+
previous_version="${previous_version:0:7}"
112+
fi
113+
114+
if [[ -z "${version}" ]]; then
115+
echo "::error::Failed to determine version for package '${{ matrix.package }}' from '${{ matrix.version_file }}'"
116+
exit 1
117+
fi
118+
119+
if [[ -n "${previous_version}" && "${previous_version}" == "${version}" ]]; then
120+
echo "no_update=true" >>"${GITHUB_OUTPUT}"
121+
echo "Detected file changes but package version remained unchanged (${version}). Skipping PR creation."
122+
exit 0
123+
fi
124+
125+
echo "package=${{ matrix.package }}" >>"${GITHUB_OUTPUT}"
126+
echo "previous_version=${previous_version}" >>"${GITHUB_OUTPUT}"
127+
echo "version=${version}" >>"${GITHUB_OUTPUT}"
128+
echo "no_update=false" >>"${GITHUB_OUTPUT}"
129+
130+
- name: Check package patch apply status
131+
id: patch_check
132+
if: steps.changes.outputs.changed == 'true' && steps.metadata.outputs.no_update != 'true'
133+
env:
134+
PACKAGE: ${{ steps.metadata.outputs.package }}
135+
run: |
136+
echo "patch_warning=false" >>"${GITHUB_OUTPUT}"
137+
shopt -s nullglob
138+
package_patch_files=(buildroot-external/package/"${PACKAGE}"/*.patch)
139+
global_patch_files=(buildroot-external/patches/"${PACKAGE}"/*.patch)
140+
if [[ ${#package_patch_files[@]} -eq 0 && ${#global_patch_files[@]} -eq 0 ]]; then
141+
echo "No package/global patch files found for '${PACKAGE}', skipping patch apply check."
142+
exit 0
143+
fi
144+
145+
make PRODUCT=generic-x86_64 build-generic-x86_64/.config
146+
if ! make -C build-generic-x86_64 "${PACKAGE}-dirclean" "${PACKAGE}-patch"; then
147+
echo "::warning::Patch apply check failed for package '${PACKAGE}'. The version bump likely requires patch updates."
148+
echo "patch_warning=true" >>"${GITHUB_OUTPUT}"
149+
fi
150+
151+
- name: Compose PR body
152+
id: pr_body
153+
if: steps.changes.outputs.changed == 'true' && steps.metadata.outputs.no_update != 'true'
154+
run: |
155+
body_file="${RUNNER_TEMP}/pr_body.md"
156+
cat >"${body_file}" <<'PR_BODY'
157+
Dependency update generated by thinRoot nightly update check workflow.
158+
159+
Updated component:
160+
- Name...........: `${{ matrix.slug }}`
161+
- Package........: `${{ steps.metadata.outputs.package }}`
162+
- Current version: `${{ steps.metadata.outputs.previous_version }}`
163+
- New version....: `${{ steps.metadata.outputs.version }}`
164+
PR_BODY
165+
if [[ "${{ steps.patch_check.outputs.patch_warning }}" == "true" ]]; then
166+
cat >>"${body_file}" <<'PR_WARNING'
167+
168+
> [!WARNING]
169+
> Patch apply check failed for package `${{ steps.metadata.outputs.package }}`.
170+
> The version bump likely requires updates to existing patch files before this change can be merged.
171+
PR_WARNING
172+
fi
173+
echo "body_file=${body_file}" >>"${GITHUB_OUTPUT}"
174+
175+
- name: Check for existing similar open PR
176+
id: duplicate
177+
if: steps.changes.outputs.changed == 'true' && steps.metadata.outputs.no_update != 'true'
178+
env:
179+
GH_TOKEN: ${{ github.token }}
180+
PR_TITLE: "bump ${{ matrix.slug }} to ${{ steps.metadata.outputs.version }}"
181+
run: |
182+
AUTH_HEADER="Authorization: Bearer ${GH_TOKEN}"
183+
count=$(curl -fsSL \
184+
-H "${AUTH_HEADER}" \
185+
-H "Accept: application/vnd.github+json" \
186+
"https://api.github.com/repos/${{ github.repository }}/pulls?state=open&per_page=100" \
187+
| jq --arg title "${PR_TITLE}" \
188+
'[.[] | select(.title == $title)] | length')
189+
190+
if [[ "${count}" -gt 0 ]]; then
191+
echo "skip=true" >>"${GITHUB_OUTPUT}"
192+
echo "Found existing open PR with the same change request title. Skipping PR creation."
193+
else
194+
echo "skip=false" >>"${GITHUB_OUTPUT}"
195+
fi
196+
197+
- name: Create pull request
198+
if: steps.changes.outputs.changed == 'true' && steps.metadata.outputs.no_update != 'true' && steps.duplicate.outputs.skip != 'true'
199+
uses: peter-evans/create-pull-request@v7
200+
with:
201+
token: ${{ secrets.AUTO_MERGE_TOKEN != '' && secrets.AUTO_MERGE_TOKEN || github.token }}
202+
branch: pr/dependency-update-${{ matrix.slug }}-${{ steps.metadata.outputs.version }}
203+
delete-branch: true
204+
commit-message: "bump ${{ matrix.slug }} to ${{ steps.metadata.outputs.version }}"
205+
title: "bump ${{ matrix.slug }} to ${{ steps.metadata.outputs.version }}"
206+
body-path: ${{ steps.pr_body.outputs.body_file }}
207+
labels: dependencies

scripts/update-buildroot.sh

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
#!/bin/bash
2+
# shellcheck source=/dev/null
23
set -e
4+
set -o pipefail
35

4-
ID=${1}
5-
PACKAGE_NAME="buildroot"
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
source "${SCRIPT_DIR}/utils/utils.sh"
8+
9+
ID=${1:-$(resolve_latest_github_stable_tag "buildroot" "buildroot" '^[0-9]+(\.[0-9]+)*$')}
610
PROJECT_URL="https://github.com/buildroot/buildroot"
7-
ARCHIVE_URL="${PROJECT_URL}/archive/${ID}/${PACKAGE_NAME}-${ID}.tar.gz"
11+
ARCHIVE_URL="${PROJECT_URL}/archive/refs/tags/${ID}.tar.gz"
12+
CURRENT_ID=$(sed -nE 's/^BUILDROOT_VERSION=(.*)$/\1/p' "Makefile" | head -n1)
13+
14+
if [[ -z "${1}" ]]; then
15+
exit_if_version_unchanged "${CURRENT_ID}" "${ID}" "buildroot"
16+
fi
817

9-
if [[ -z "${ID}" ]]; then
10-
echo "tag name or commit sha required (see ${URL})"
18+
if ! wget --passive-ftp -nd -t 3 --spider "${ARCHIVE_URL}"; then
19+
echo "Failed to download archive for buildroot" >&2
1120
exit 1
1221
fi
1322

14-
# download archive for hash update
1523
ARCHIVE_HASH=$(wget --passive-ftp -nd -t 3 -O - "${ARCHIVE_URL}" | sha256sum | awk '{ print $1 }')
1624
if [[ -n "${ARCHIVE_HASH}" ]]; then
17-
# update package info
18-
sed -i "s/BUILDROOT_VERSION=.*/BUILDROOT_VERSION=$1/g" "Makefile"
19-
# update package hash
25+
sed -i "s/BUILDROOT_VERSION=.*/BUILDROOT_VERSION=${ID}/g" "Makefile"
2026
sed -i "s/BUILDROOT_SHA256=.*/BUILDROOT_SHA256=${ARCHIVE_HASH}/g" "Makefile"
27+
else
28+
echo "Failed to retrieve archive hash for buildroot" >&2
29+
exit 1
2130
fi

scripts/update-hotkeyd.sh

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
#!/bin/bash
2+
# shellcheck source=/dev/null
23
set -e
4+
set -o pipefail
35

4-
ID=${1}
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
source "${SCRIPT_DIR}/utils/utils.sh"
8+
9+
ID=${1:-$(resolve_latest_github_head_commit "jens-maus" "hotkeyd")}
510
PACKAGE_NAME="hotkeyd"
611
PROJECT_URL="https://github.com/jens-maus/hotkeyd"
712
ARCHIVE_URL="${PROJECT_URL}/archive/${ID}/${PACKAGE_NAME}-${ID}.tar.gz"
13+
CURRENT_ID=$(sed -nE 's/^HOTKEYD_VERSION = (.*)$/\1/p' "buildroot-external/package/${PACKAGE_NAME}/${PACKAGE_NAME}.mk" | head -n1)
14+
15+
if [[ -z "${1}" ]]; then
16+
exit_if_version_unchanged "${CURRENT_ID}" "${ID}" "${PACKAGE_NAME}"
17+
fi
818

9-
if [[ -z "${ID}" ]]; then
10-
echo "Need hotkeyd version/commit"
19+
if ! wget --passive-ftp -nd -t 3 --spider "${ARCHIVE_URL}"; then
20+
echo "Failed to download archive for ${PACKAGE_NAME}" >&2
1121
exit 1
1222
fi
1323

14-
# download archive for hash update
1524
ARCHIVE_HASH=$(wget --passive-ftp -nd -t 3 -O - "${ARCHIVE_URL}" | sha256sum | awk '{ print $1 }')
1625
if [[ -n "${ARCHIVE_HASH}" ]]; then
17-
# update package info
1826
BR_PACKAGE_NAME=${PACKAGE_NAME^^}
1927
BR_PACKAGE_NAME=${BR_PACKAGE_NAME//-/_}
2028
sed -i "s/${BR_PACKAGE_NAME}_VERSION = .*/${BR_PACKAGE_NAME}_VERSION = ${ID}/g" "buildroot-external/package/${PACKAGE_NAME}/${PACKAGE_NAME}.mk"
21-
# update package hash
22-
sed -i "$ d" "buildroot-external/package/${PACKAGE_NAME}/${PACKAGE_NAME}.hash"
29+
sed -i '$ d' "buildroot-external/package/${PACKAGE_NAME}/${PACKAGE_NAME}.hash"
2330
echo "sha256 ${ARCHIVE_HASH} ${PACKAGE_NAME}-${ID}.tar.gz" >>"buildroot-external/package/${PACKAGE_NAME}/${PACKAGE_NAME}.hash"
31+
else
32+
echo "Failed to retrieve archive hash for ${PACKAGE_NAME}" >&2
33+
exit 1
2434
fi

scripts/update-kernel-upstream.sh

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,63 @@
11
#!/bin/bash
2+
# shellcheck source=/dev/null
23
set -e
4+
set -o pipefail
5+
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
source "${SCRIPT_DIR}/utils/utils.sh"
38

4-
ID=${1}
59
PACKAGE_NAME="linux"
6-
PROJECT_URL="https://cdn.kernel.org/pub/linux/kernel/v6.x"
7-
#ARCHIVE_URL="${PROJECT_URL}/${PACKAGE_NAME}-${ID}.tar.xz"
10+
PROJECT_ROOT_URL="https://cdn.kernel.org/pub/linux/kernel"
11+
PROJECT_SERIES=$(wget --passive-ftp -nd -t 3 -O - "${PROJECT_ROOT_URL}/" | grep -oE 'v[0-9]+\.x/' | tr -d '/' | sort -uV | tail -n1 || true)
12+
if [[ -z "${PROJECT_SERIES}" ]]; then
13+
echo "Failed to resolve latest ${PACKAGE_NAME} kernel series from ${PROJECT_ROOT_URL}" >&2
14+
exit 1
15+
fi
16+
PROJECT_URL="${PROJECT_ROOT_URL}/${PROJECT_SERIES}"
817
CHECKSUM_URL="${PROJECT_URL}/sha256sums.asc"
918

19+
if ! wget --passive-ftp -nd -t 3 --spider "${CHECKSUM_URL}"; then
20+
echo "Failed to download checksum list for ${PACKAGE_NAME}" >&2
21+
exit 1
22+
fi
23+
24+
CHECKSUM_CONTENT=$(wget --passive-ftp -nd -t 3 -O - "${CHECKSUM_URL}")
25+
ID=${1:-$(echo "${CHECKSUM_CONTENT}" | grep -oE "${PACKAGE_NAME}-[0-9]+\.[0-9]+\.[0-9]+\.tar\.xz" | sed -E "s/^${PACKAGE_NAME}-//; s/\.tar\.xz$//" | sort -V | tail -n1)}
1026
if [[ -z "${ID}" ]]; then
11-
echo "Need a kernel version!"
27+
echo "Failed to resolve latest ${PACKAGE_NAME} version from ${CHECKSUM_URL}" >&2
1228
exit 1
1329
fi
1430

15-
# extract sha256 checksum
16-
ARCHIVE_HASH=$(wget --passive-ftp -nd -t 3 -O - "${CHECKSUM_URL}" | grep "${PACKAGE_NAME}-${ID}.tar.xz" | awk '{ print $1 }')
31+
ARCHIVE_HASH=$(echo "${CHECKSUM_CONTENT}" | grep "${PACKAGE_NAME}-${ID}.tar.xz" | awk '{ print $1 }')
1732
if [[ -z "${ARCHIVE_HASH}" ]]; then
18-
echo "no hash found for ${PACKAGE_NAME}-${ID}.tar.xz"
33+
echo "no hash found for ${PACKAGE_NAME}-${ID}.tar.xz" >&2
1934
exit 1
2035
fi
2136

22-
# update kconfig file
37+
CURRENT_VERSION_LIST=$(grep -oE 'BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="[^"]+"' buildroot-external/configs/{generic-x86_64,tinkerboard}.config | sed -E 's/.*"([^"]+)"/\1/' | sort -u)
38+
if [[ $(echo "${CURRENT_VERSION_LIST}" | wc -l) -ne 1 ]]; then
39+
echo "${PACKAGE_NAME}: inconsistent kernel versions found across target configs, refusing to auto-update" >&2
40+
exit 1
41+
fi
42+
CURRENT_VERSION="${CURRENT_VERSION_LIST}"
43+
EXPECTED_HASH_LINE="sha256 ${ARCHIVE_HASH} ${PACKAGE_NAME}-${ID}.tar.xz"
44+
HASH_LINE_COUNT=$(grep -Ec "^sha256[[:space:]]+[[:alnum:]]+[[:space:]]+${PACKAGE_NAME}-.*\\.tar\\.xz$" "buildroot-external/patches/${PACKAGE_NAME}/${PACKAGE_NAME}.hash")
45+
HEADERS_HASH_LINE_COUNT=$(grep -Ec "^sha256[[:space:]]+[[:alnum:]]+[[:space:]]+${PACKAGE_NAME}-.*\\.tar\\.xz$" "buildroot-external/patches/${PACKAGE_NAME}-headers/${PACKAGE_NAME}-headers.hash")
46+
47+
if [[ -z "${1}" ]]; then
48+
if [[ "${CURRENT_VERSION}" == "${ID}" ]] \
49+
&& grep -Fxq "${EXPECTED_HASH_LINE}" "buildroot-external/patches/${PACKAGE_NAME}/${PACKAGE_NAME}.hash" \
50+
&& grep -Fxq "${EXPECTED_HASH_LINE}" "buildroot-external/patches/${PACKAGE_NAME}-headers/${PACKAGE_NAME}-headers.hash" \
51+
&& [[ "${HASH_LINE_COUNT}" -eq 1 ]] \
52+
&& [[ "${HEADERS_HASH_LINE_COUNT}" -eq 1 ]]; then
53+
echo "${PACKAGE_NAME}: version ${ID} is already current, no config or hash updates required"
54+
exit 0
55+
fi
56+
fi
57+
2358
sed -i "s/BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=\".*\"/BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=\"${ID}\"/g" buildroot-external/configs/{generic-x86_64,tinkerboard}.config
2459

25-
# update hash files
26-
sed -i "/${PACKAGE_NAME}-.*\.tar\.xz/d" "buildroot-external/patches/${PACKAGE_NAME}/${PACKAGE_NAME}.hash"
60+
sed -i "/${PACKAGE_NAME}-${CURRENT_VERSION}\.tar\.xz/d" "buildroot-external/patches/${PACKAGE_NAME}/${PACKAGE_NAME}.hash"
2761
echo "sha256 ${ARCHIVE_HASH} ${PACKAGE_NAME}-${ID}.tar.xz" >>"buildroot-external/patches/${PACKAGE_NAME}/${PACKAGE_NAME}.hash"
28-
sed -i "/${PACKAGE_NAME}-.*\.tar\.xz/d" "buildroot-external/patches/${PACKAGE_NAME}-headers/${PACKAGE_NAME}-headers.hash"
62+
sed -i "/${PACKAGE_NAME}-${CURRENT_VERSION}\.tar\.xz/d" "buildroot-external/patches/${PACKAGE_NAME}-headers/${PACKAGE_NAME}-headers.hash"
2963
echo "sha256 ${ARCHIVE_HASH} ${PACKAGE_NAME}-${ID}.tar.xz" >>"buildroot-external/patches/${PACKAGE_NAME}-headers/${PACKAGE_NAME}-headers.hash"

0 commit comments

Comments
 (0)