Skip to content

Build R Packages

Build R Packages #8516

Workflow file for this run

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@v4
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