Build R Packages #10921
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: Build R Packages | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - 'build/**' | |
| paths: | |
| - 'logs/dispatched-packages.txt' | |
| - 'logs/successful-packages.txt' | |
| concurrency: | |
| group: build-r-packages-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| token: ${{ secrets.PAT }} | |
| persist-credentials: true | |
| - name: Update to latest | |
| run: | | |
| git pull origin ${GITHUB_REF#refs/heads/} | |
| git reset --hard origin/${GITHUB_REF#refs/heads/} | |
| - name: Save kubeconfig | |
| shell: bash | |
| run: mkdir -p ~/.kube && echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config | |
| - name: Install Kubectl | |
| run: | | |
| curl -LO 'https://dl.k8s.io/release/v1.34.1/bin/linux/amd64/kubectl' && \ | |
| chmod +x ./kubectl && \ | |
| mv ./kubectl /usr/local/bin/kubectl && \ | |
| kubectl version | |
| - name: Check for completed cycle | |
| id: cycle_check | |
| run: | | |
| # Check if PACKAGES file exists (indicates completed cycle) | |
| if [[ -f "PACKAGES" ]]; then | |
| echo "Cycle already completed (PACKAGES file exists). Skipping." | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Manage build lifecycle | |
| if: steps.cycle_check.outputs.skip != 'true' | |
| env: | |
| RCLONE_CONF: ${{secrets.RCLONE_CONF}} | |
| id: build | |
| run: | | |
| set -euxo pipefail | |
| # Use branch name as the build identifier | |
| BRANCH_NAME=${GITHUB_REF#refs/heads/} | |
| BUILD_ID=$(echo "${BRANCH_NAME}" | sed 's|build/||' | tr '/' '-') | |
| echo "Build ID: ${BUILD_ID}" | |
| echo "Branch: ${BRANCH_NAME}" | |
| # Check if this is first run (no biocdeps.json yet) | |
| if [[ ! -s "biocdeps.json" ]]; then | |
| echo "First run - setting up Kubernetes resources..." | |
| CONTAINER=$(cat CONTAINER_BASE_IMAGE.bioc) | |
| # Setup Kubernetes resources - this handles running deps_json.R via init container | |
| bash ./.github/scripts/setup_k8s.sh "nfs-client" "500Gi" "${BUILD_ID}" | |
| else | |
| echo "Continuing existing build..." | |
| CONTAINER=$(cat CONTAINER_BASE_IMAGE.bioc) | |
| # Handle completed jobs | |
| bash ./.github/scripts/handle_k8s_jobs.sh \ | |
| "${BUILD_ID}" \ | |
| "logs/successful-packages.txt" \ | |
| "logs/failed-packages.txt" | |
| fi | |
| # Ensure log directory exists before any operations | |
| mkdir -p logs | |
| touch logs/dispatched-packages.txt | |
| # Find and dispatch packages | |
| python ./.github/scripts/find_ready_pkgs.py \ | |
| "biocdeps.json" \ | |
| "ready_packages.txt" \ | |
| "logs/dispatched-packages.txt" \ | |
| "logs/successful-packages.txt" \ | |
| "remaining-packages.json" | |
| # Calculate truncated build ID for PVC name | |
| BUILD_ID_CLEAN=$(echo "${BUILD_ID}" | tr '[:upper:]' '[:lower:]' | tr '.' '-' | tr -cd '[:alnum:]-') | |
| VERSION_SUFFIX="${BUILD_ID_CLEAN: -3}" | |
| DATE_PART=$(echo "$BUILD_ID_CLEAN" | cut -d'-' -f1-3 | tr -d '-') | |
| CONTAINER_HINT=$(echo "$BUILD_ID_CLEAN" | sed 's/^[0-9-]*-[0-9]*-//' | sed 's/-[^-]*$//' | cut -c1-4) | |
| BUILD_ID_SHORT="${DATE_PART}-${CONTAINER_HINT}-${VERSION_SUFFIX}" | |
| PVC_NAME="pvc-${BUILD_ID_SHORT}" | |
| # Dispatch jobs with xargs and delay | |
| cat "ready_packages.txt" | xargs -i bash -c "bash ./.github/scripts/dispatch_k8s_job.sh {} ${CONTAINER} ${PVC_NAME} ${BUILD_ID} && sleep 1" | |
| # Track if we had any new activity | |
| git add . | |
| ACTIVITY_CHECK=$(git diff --staged --name-status | grep -q "logs/.*packages.txt" && echo "true" || echo "false") | |
| echo "had_activity=${ACTIVITY_CHECK}" >> $GITHUB_OUTPUT | |
| # Get counter values | |
| NULL_PUSH_COUNT=$(cat "null_push_counter" 2>/dev/null || echo "0") | |
| RESET_ATTEMPTS=$(cat "reset_attempts_counter" 2>/dev/null || echo "0") | |
| # Note: Reset failed logic removed - handle failures manually or via separate workflow if needed | |
| # Backup changed files to temp directory | |
| mkdir -p /tmp/restore | |
| # Stage all changes to identify modified files | |
| git add . | |
| # Copy changed files to restore directory | |
| git diff --staged --name-only | xargs -r -I{} bash -c 'mkdir -p "/tmp/restore/$(dirname "{}")" && cp "{}" "/tmp/restore/{}"' | |
| echo "Backup complete - copied changed files" | |
| - name: Commit and push results | |
| if: steps.cycle_check.outputs.skip != 'true' | |
| id: repo_changes | |
| run: | | |
| BRANCH_NAME=${GITHUB_REF#refs/heads/} | |
| git pull origin ${BRANCH_NAME} && git reset --hard origin/${BRANCH_NAME} || git reset --hard origin/${BRANCH_NAME} | |
| cp -r /tmp/restore/* ./ || true | |
| git config --global user.name 'GitHub Actions' | |
| git config --global user.email 'actions@github.com' | |
| git add . || true | |
| # Check if there are actual changes to commit | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| # If no changes, increment the null push counter | |
| NULL_PUSH_COUNT=$(cat "null_push_counter" 2>/dev/null || echo "0") | |
| NEW_COUNT=$((NULL_PUSH_COUNT + 1)) | |
| echo "${NEW_COUNT}" > "null_push_counter" | |
| # Commit just the counter change | |
| git add "null_push_counter" | |
| git commit -m "Increment null push counter to ${NEW_COUNT}" | |
| git push || (git pull --rebase && git push) | |
| echo "pushed=false" >> $GITHUB_OUTPUT | |
| else | |
| # Check for new successful packages | |
| NEW_SUCCESS=$(git diff --staged -- "logs/successful-packages.txt" | grep -c "^\+" | grep -v "^\+\+\+" || echo "0") | |
| # Always reset null push counter on meaningful changes | |
| echo "0" > "null_push_counter" | |
| git add "null_push_counter" | |
| # Only reset attempts counter if we have new successful packages | |
| if [ "${NEW_SUCCESS}" -gt 0 ]; then | |
| echo "New successful packages detected (${NEW_SUCCESS}), resetting attempts counter" | |
| echo "0" > "reset_attempts_counter" | |
| git add "reset_attempts_counter" | |
| fi | |
| # Commit all changes | |
| git commit -m "Build state update: ${{ github.run_id }}" | |
| git push || (git pull --rebase && git push) | |
| echo "pushed=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check if cycle is ready to finish | |
| if: steps.cycle_check.outputs.skip != 'true' | |
| id: check_complete | |
| run: | | |
| if bash ./.github/scripts/check_cycle_complete.sh > /tmp/cycle-check-output.txt 2>&1; then | |
| echo "notify=yes" >> $GITHUB_OUTPUT | |
| # Get container name for notification | |
| CONTAINER=$(cat CONTAINER_BASE_IMAGE.bioc) | |
| # Get stats for notification | |
| SUCCESSFUL=$(wc -l < logs/successful-packages.txt 2>/dev/null || echo 0) | |
| FAILED=$(wc -l < logs/failed-packages.txt 2>/dev/null || echo 0) | |
| # Count only blocked packages (remaining packages that are not failed) | |
| REMAINING=$(python3 -c "import json; remaining = json.load(open('remaining-packages.json')); failed = set(line.strip() for line in open('logs/failed-packages.txt') if line.strip()); blocked = [pkg for pkg in remaining if pkg not in failed]; print(len(blocked))" 2>/dev/null || echo 0) | |
| # Create simple single-line notification message (avoid quotes to prevent JSON escaping issues) | |
| MSG="Build cycle ready for ${CONTAINER}: ${SUCCESSFUL} successful, ${FAILED} failed, ${REMAINING} blocked. Run finish_cycle: https://github.com/${{ github.repository }}/actions/workflows/finish_cycle.yaml" | |
| # Replace any double quotes with single quotes to avoid JSON issues | |
| MSG="${MSG//\"/\'}" | |
| echo "SLACK_MESSAGE=${MSG}" >> "$GITHUB_ENV" | |
| else | |
| echo "notify=no" >> $GITHUB_OUTPUT | |
| cat /tmp/cycle-check-output.txt || true | |
| fi | |
| - name: Notify slack channel | |
| uses: slackapi/slack-github-action@v2.1.1 | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| payload: | | |
| { | |
| "channel": "${{ secrets.SLACK_CHANNEL_ID }}", | |
| "text": "${{ env.SLACK_MESSAGE }}" | |
| } | |
| env: | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | |
| SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }} | |
| if: env.SLACK_BOT_TOKEN != null && env.SLACK_CHANNEL_ID != null && steps.check_complete.outputs.notify == 'yes' | |
| continue-on-error: true |