Check Package Versions #18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Check Package Versions | |
| on: | |
| schedule: | |
| - cron: '0 6 * * *' # Daily at 6 AM UTC | |
| workflow_dispatch: | |
| inputs: | |
| package: | |
| description: 'Specific package to check (leave empty for all)' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| jobs: | |
| collect-updates: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| updates: ${{ steps.collect.outputs.updates }} | |
| has_updates: ${{ steps.collect.outputs.has_updates }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Install dependencies | |
| run: sudo apt-get update && sudo apt-get install -y jq | |
| - name: Collect all package updates | |
| id: collect | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| INPUT_PACKAGE: ${{ inputs.package }} | |
| run: | | |
| # Source shared package configuration | |
| source ./scripts/packages/config.sh | |
| # Use check-upstream-versions.sh script to get updates | |
| UPDATES_JSON="[]" | |
| # Get list of packages to check | |
| if [[ -n "$INPUT_PACKAGE" ]]; then | |
| PACKAGES_TO_CHECK="$INPUT_PACKAGE" | |
| else | |
| PACKAGES_TO_CHECK="${!PACKAGE_REPOS[@]}" | |
| fi | |
| echo "Checking packages for upstream updates..." | |
| echo "" | |
| for pkg in $PACKAGES_TO_CHECK; do | |
| echo "Checking $pkg..." | |
| # Get spec version (version only, strip release) | |
| SPEC_VER=$(./scripts/packages/get-spec-version.sh "$pkg" 2>/dev/null | sed 's/-[^-]*$//') | |
| if [[ -z "$SPEC_VER" ]]; then | |
| echo " Could not get spec version" | |
| continue | |
| fi | |
| # Get upstream version | |
| UPSTREAM_VER=$(./scripts/packages/get-upstream-version.sh "$pkg" 2>/dev/null) | |
| if [[ -z "$UPSTREAM_VER" ]]; then | |
| echo " Could not get upstream version" | |
| continue | |
| fi | |
| echo " Spec: $SPEC_VER, Upstream: $UPSTREAM_VER" | |
| # Compare versions | |
| if [[ "$SPEC_VER" != "$UPSTREAM_VER" ]]; then | |
| echo " -> Needs update!" | |
| UPDATES_JSON=$(echo "$UPDATES_JSON" | jq -c \ | |
| --arg pkg "$pkg" \ | |
| --arg repo "${PACKAGE_REPOS[$pkg]}" \ | |
| --arg old "$SPEC_VER" \ | |
| --arg new "$UPSTREAM_VER" \ | |
| --arg deps "${PACKAGE_DEPS[$pkg]:-}" \ | |
| '. += [{"package": $pkg, "repo": $repo, "old_version": $old, "new_version": $new, "dependencies": $deps}]') | |
| fi | |
| done | |
| echo "" | |
| echo "Updates found:" | |
| echo "$UPDATES_JSON" | jq . | |
| if [[ "$UPDATES_JSON" == "[]" ]]; then | |
| echo "has_updates=false" >> $GITHUB_OUTPUT | |
| echo "updates=[]" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_updates=true" >> $GITHUB_OUTPUT | |
| echo "updates=$(echo "$UPDATES_JSON" | jq -c .)" >> $GITHUB_OUTPUT | |
| fi | |
| group-updates: | |
| needs: collect-updates | |
| if: needs.collect-updates.outputs.has_updates == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| groups: ${{ steps.group.outputs.groups }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Group updates by dependency chain | |
| id: group | |
| run: | | |
| UPDATES='${{ needs.collect-updates.outputs.updates }}' | |
| # Source shared package configuration | |
| source ./scripts/packages/config.sh | |
| # Use PACKAGE_DEPS from shared config | |
| # Get list of packages that need updates | |
| UPDATED_PKGS=$(echo "$UPDATES" | jq -r '.[].package') | |
| # Function to get all ancestors (dependencies) of a package | |
| get_ancestors() { | |
| local pkg=$1 | |
| local ancestors="" | |
| for dep in ${PACKAGE_DEPS[$pkg]:-}; do | |
| ancestors="$ancestors $dep" | |
| ancestors="$ancestors $(get_ancestors $dep)" | |
| done | |
| echo "$ancestors" | |
| } | |
| # Function to get all descendants (dependents) of a package | |
| get_descendants() { | |
| local pkg=$1 | |
| local descendants="" | |
| for p in "${!PACKAGE_DEPS[@]}"; do | |
| if [[ " ${PACKAGE_DEPS[$p]} " == *" $pkg "* ]]; then | |
| descendants="$descendants $p" | |
| descendants="$descendants $(get_descendants $p)" | |
| fi | |
| done | |
| echo "$descendants" | |
| } | |
| # Build groups - packages that share dependencies go together | |
| declare -A VISITED | |
| GROUPS_JSON="[]" | |
| GROUP_ID=0 | |
| for pkg in $UPDATED_PKGS; do | |
| if [[ -n "${VISITED[$pkg]}" ]]; then | |
| continue | |
| fi | |
| # Start a new group with this package | |
| GROUP_PKGS="$pkg" | |
| VISITED[$pkg]=1 | |
| # Add all updated packages that are ancestors or descendants | |
| ancestors=$(get_ancestors "$pkg") | |
| descendants=$(get_descendants "$pkg") | |
| related="$ancestors $descendants" | |
| for other in $UPDATED_PKGS; do | |
| if [[ -z "${VISITED[$other]}" ]]; then | |
| # Check if other is related to this package | |
| if [[ " $related " == *" $other "* ]]; then | |
| GROUP_PKGS="$GROUP_PKGS $other" | |
| VISITED[$other]=1 | |
| # Also add relatives of this newly added package | |
| other_ancestors=$(get_ancestors "$other") | |
| other_descendants=$(get_descendants "$other") | |
| for another in $UPDATED_PKGS; do | |
| if [[ -z "${VISITED[$another]}" ]]; then | |
| if [[ " $other_ancestors $other_descendants " == *" $another "* ]]; then | |
| GROUP_PKGS="$GROUP_PKGS $another" | |
| VISITED[$another]=1 | |
| fi | |
| fi | |
| done | |
| fi | |
| fi | |
| done | |
| # Deduplicate and sort | |
| GROUP_PKGS=$(echo "$GROUP_PKGS" | tr ' ' '\n' | sort -u | tr '\n' ' ' | xargs) | |
| if [[ -n "$GROUP_PKGS" ]]; then | |
| # Build group JSON with package details | |
| GROUP_UPDATES="[]" | |
| for gpkg in $GROUP_PKGS; do | |
| PKG_DATA=$(echo "$UPDATES" | jq -c --arg pkg "$gpkg" '.[] | select(.package == $pkg)') | |
| if [[ -n "$PKG_DATA" ]]; then | |
| GROUP_UPDATES=$(echo "$GROUP_UPDATES" | jq -c --argjson data "$PKG_DATA" '. += [$data]') | |
| fi | |
| done | |
| GROUP_ID=$((GROUP_ID + 1)) | |
| GROUPS_JSON=$(echo "$GROUPS_JSON" | jq -c \ | |
| --argjson id "$GROUP_ID" \ | |
| --argjson packages "$GROUP_UPDATES" \ | |
| '. += [{"id": $id, "packages": $packages}]') | |
| fi | |
| done | |
| echo "Grouped updates:" | |
| echo "$GROUPS_JSON" | jq . | |
| echo "groups=$(echo "$GROUPS_JSON" | jq -c .)" >> $GITHUB_OUTPUT | |
| create-prs: | |
| needs: group-updates | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 1 | |
| matrix: | |
| group: ${{ fromJson(needs.group-updates.outputs.groups) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Install dependencies | |
| run: sudo apt-get update && sudo apt-get install -y jq | |
| - name: Generate group info | |
| id: info | |
| run: | | |
| PACKAGES='${{ toJson(matrix.group.packages) }}' | |
| # Get package names for branch name | |
| PKG_NAMES=$(echo "$PACKAGES" | jq -r '.[].package' | sort | tr '\n' '-' | sed 's/-$//') | |
| PKG_COUNT=$(echo "$PACKAGES" | jq 'length') | |
| # Create summary for PR title | |
| if [[ "$PKG_COUNT" -eq 1 ]]; then | |
| TITLE=$(echo "$PACKAGES" | jq -r '.[0] | "pkg(\(.package)): update to \(.new_version)"') | |
| else | |
| FIRST_PKG=$(echo "$PACKAGES" | jq -r '.[0].package') | |
| TITLE="pkg: update $FIRST_PKG and $((PKG_COUNT - 1)) related packages" | |
| fi | |
| # Create unique branch name using first package and date | |
| BRANCH="pkg-update/${PKG_NAMES}-$(date +%Y%m%d)" | |
| echo "title=$TITLE" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| echo "pkg_count=$PKG_COUNT" >> $GITHUB_OUTPUT | |
| - name: Check for existing PR | |
| id: existing-pr | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| BRANCH="${{ steps.info.outputs.branch }}" | |
| # Check for PR with this branch | |
| EXISTING=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null) || EXISTING="" | |
| if [[ -n "$EXISTING" ]]; then | |
| echo "PR #$EXISTING already exists for this update group" | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update spec files | |
| if: steps.existing-pr.outputs.exists != 'true' | |
| run: | | |
| PACKAGES='${{ toJson(matrix.group.packages) }}' | |
| DATE=$(date "+%a %b %d %Y") | |
| echo "$PACKAGES" | jq -c '.[]' | while read -r pkg_data; do | |
| PKG=$(echo "$pkg_data" | jq -r '.package') | |
| OLD_VERSION=$(echo "$pkg_data" | jq -r '.old_version') | |
| NEW_VERSION=$(echo "$pkg_data" | jq -r '.new_version') | |
| SPEC_FILE="packages/$PKG/$PKG.spec" | |
| echo "Updating $PKG: $OLD_VERSION -> $NEW_VERSION" | |
| # Update Version field | |
| sed -i "s/^Version:.*$/Version: $NEW_VERSION/" "$SPEC_FILE" | |
| # Update Release back to 1 | |
| sed -i "s/^Release:.*$/Release: 1%{?dist}/" "$SPEC_FILE" | |
| # Add changelog entry | |
| CHANGELOG_ENTRY="* $DATE Hypercube <hypercube@binarypie.dev> - $NEW_VERSION-1\n- Update to $NEW_VERSION" | |
| sed -i "/%changelog/a\\$CHANGELOG_ENTRY" "$SPEC_FILE" | |
| done | |
| - name: Generate PR body | |
| if: steps.existing-pr.outputs.exists != 'true' | |
| id: body | |
| run: | | |
| PACKAGES='${{ toJson(matrix.group.packages) }}' | |
| { | |
| echo "body<<EOF" | |
| echo "## Package Updates" | |
| echo "" | |
| echo "$PACKAGES" | jq -c '.[]' | while read -r pkg_data; do | |
| PKG=$(echo "$pkg_data" | jq -r '.package') | |
| REPO=$(echo "$pkg_data" | jq -r '.repo') | |
| OLD=$(echo "$pkg_data" | jq -r '.old_version') | |
| NEW=$(echo "$pkg_data" | jq -r '.new_version') | |
| echo "### $PKG" | |
| echo "- **Version:** \`$OLD\` -> \`$NEW\`" | |
| echo "- **Release:** https://github.com/$REPO/releases/tag/v$NEW" | |
| echo "" | |
| done | |
| echo "---" | |
| echo "*This PR was automatically created by the package version checker.*" | |
| echo "*Packages are grouped by dependency chain to ensure correct build order.*" | |
| echo "EOF" | |
| } >> $GITHUB_OUTPUT | |
| - name: Create Pull Request | |
| if: steps.existing-pr.outputs.exists != 'true' | |
| id: create-pr | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: "${{ steps.info.outputs.title }}" | |
| title: "${{ steps.info.outputs.title }}" | |
| body: ${{ steps.body.outputs.body }} | |
| branch: "${{ steps.info.outputs.branch }}" | |
| delete-branch: true | |
| labels: | | |
| dependencies | |
| package-update | |
| author: "Claude <claude@anthropic.com>" | |
| committer: "Claude <claude@anthropic.com>" | |
| - name: Analyze and update specs with Claude | |
| if: steps.existing-pr.outputs.exists != 'true' && steps.create-pr.outputs.pull-request-number | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| prompt: | | |
| I need you to analyze and update RPM spec files for package version upgrades. | |
| The following packages were updated (version bumps already applied to spec files): | |
| ${{ toJson(matrix.group.packages) }} | |
| For each package, please: | |
| 1. Fetch the upstream build configuration files to check for dependency changes: | |
| - CMakeLists.txt or meson.build for C/C++ projects (Hyprland ecosystem) | |
| - Cargo.toml for Rust projects (eza, starship, wifitui) | |
| - go.mod for Go projects (lazygit) | |
| - setup.py or pyproject.toml for Python projects (uwsm) | |
| 2. Compare the dependencies in the upstream build files against the current spec file's BuildRequires and Requires sections. | |
| 3. Check if any shared library soname versions have changed (e.g., libfoo.so.3 -> libfoo.so.4) by looking at: | |
| - CMake's set_target_properties for SOVERSION | |
| - The %files section of the spec for current soname expectations | |
| 4. Update the spec file if needed: | |
| - Add/remove/update BuildRequires for new build dependencies | |
| - Add/remove/update Requires for new runtime dependencies | |
| - Update soname versions in %files section if the library major version changed | |
| - Update any hardcoded paths or version references | |
| 5. If you make changes, update the changelog entry to mention what changed beyond just the version bump. | |
| The spec files are located at: packages/<package-name>/<package-name>.spec | |
| Focus on correctness - it's better to flag something for manual review than to make an incorrect change. | |
| If you're uncertain about a change, add a comment in the PR explaining what might need manual verification. | |
| claude_args: "--max-turns 10" |