diff --git a/.github/workflows/build-pull-request-jenkins.yml b/.github/workflows/build-pull-request-jenkins.yml new file mode 100644 index 0000000000..68d95d9812 --- /dev/null +++ b/.github/workflows/build-pull-request-jenkins.yml @@ -0,0 +1,157 @@ +name: Build Pull Request Jenkins + +on: + pull_request: + paths-ignore: + - '.github/**' + - 'docs/**' + - '!.github/workflows/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read + +jobs: + jenkins-ci-docker: + runs-on: ubuntu-latest + env: + JENKINS_URL: ${{ vars.JENKINS_URL || 'https://starjenkins.sdcc.bnl.gov' }} + JENKINS_JOB: ${{ vars.JENKINS_JOB || 'star-sw-ci-pipeline' }} + JENKINS_USER: ${{ secrets.JENKINS_USER }} + JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + GIT_COMMIT: ${{ github.event.pull_request.head.sha }} + REPO_URL: ${{ github.event.pull_request.head.repo.clone_url }} + steps: + - name: Validate Jenkins configuration + run: | + set -euo pipefail + for var in JENKINS_URL JENKINS_JOB JENKINS_USER JENKINS_TOKEN PR_NUMBER BRANCH_NAME GIT_COMMIT REPO_URL; do + if [ -z "${!var:-}" ]; then + echo "::error::Missing required value for ${var}" + exit 1 + fi + done + + - name: Fetch Jenkins crumb + id: crumb + run: | + set -euo pipefail + curl_auth="${JENKINS_USER}:${JENKINS_TOKEN}" + crumb="$(curl --silent --show-error --fail --retry 3 --retry-all-errors --user "$curl_auth" \ + "${JENKINS_URL}/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)")" + echo "::add-mask::$crumb" + echo "value=$crumb" >> "$GITHUB_OUTPUT" + + - name: Cancel older Jenkins builds for this PR + env: + JENKINS_CRUMB: ${{ steps.crumb.outputs.value }} + run: | + set -euo pipefail + curl_auth="${JENKINS_USER}:${JENKINS_TOKEN}" + + build_api="${JENKINS_URL}/job/${JENKINS_JOB}/api/json?tree=builds[number,url,building,actions[parameters[name,value]]]" + queue_api="${JENKINS_URL}/queue/api/json?tree=items[id,task[name],actions[parameters[name,value]]]" + + running_builds="$( + curl --globoff --silent --show-error --fail --retry 3 --retry-all-errors --user "$curl_auth" "$build_api" | + jq -r --arg pr "$PR_NUMBER" --arg branch "$BRANCH_NAME" --arg repo "$REPO_URL" ' + def param($name): ([.actions[]?.parameters[]? | select(.name == $name) | (.value | tostring)][0] // ""); + .builds[]? + | select(.building == true) + | select( + (param("PR_NUMBER") != "" and param("PR_NUMBER") == $pr) + or + (param("PR_NUMBER") == "" and param("BRANCH_NAME") == $branch and param("REPO_URL") == $repo) + ) + | .url + ' + )" + + queued_items="$( + curl --globoff --silent --show-error --fail --retry 3 --retry-all-errors --user "$curl_auth" "$queue_api" | + jq -r --arg job "$JENKINS_JOB" --arg pr "$PR_NUMBER" --arg branch "$BRANCH_NAME" --arg repo "$REPO_URL" ' + def param($name): ([.actions[]?.parameters[]? | select(.name == $name) | (.value | tostring)][0] // ""); + .items[]? + | select(.task.name == $job) + | select( + (param("PR_NUMBER") != "" and param("PR_NUMBER") == $pr) + or + (param("PR_NUMBER") == "" and param("BRANCH_NAME") == $branch and param("REPO_URL") == $repo) + ) + | .id + ' + )" + + if [ -n "$queued_items" ]; then + while IFS= read -r item_id; do + [ -n "$item_id" ] || continue + echo "Canceling queued Jenkins item ${item_id}" + curl --silent --show-error --fail \ + --request POST \ + --retry 3 \ + --retry-all-errors \ + --user "$curl_auth" \ + --header "$JENKINS_CRUMB" \ + --output /dev/null \ + "${JENKINS_URL}/queue/cancelItem?id=${item_id}" + done <<< "$queued_items" + fi + + if [ -n "$running_builds" ]; then + while IFS= read -r build_url; do + [ -n "$build_url" ] || continue + echo "Stopping running Jenkins build ${build_url}" + curl --silent --show-error --fail \ + --request POST \ + --retry 3 \ + --retry-all-errors \ + --user "$curl_auth" \ + --header "$JENKINS_CRUMB" \ + --output /dev/null \ + "${build_url}stop" + done <<< "$running_builds" + fi + + - name: Trigger Jenkins docker job + env: + JENKINS_CRUMB: ${{ steps.crumb.outputs.value }} + run: | + set -euo pipefail + curl_auth="${JENKINS_USER}:${JENKINS_TOKEN}" + response_headers="$(mktemp)" + + curl --silent --show-error --fail \ + --request POST \ + --retry 3 \ + --retry-all-errors \ + --user "$curl_auth" \ + --header "$JENKINS_CRUMB" \ + --data-urlencode "PR_NUMBER=${PR_NUMBER}" \ + --data-urlencode "BRANCH_NAME=${BRANCH_NAME}" \ + --data-urlencode "GIT_COMMIT=${GIT_COMMIT}" \ + --data-urlencode "REPO_URL=${REPO_URL}" \ + --dump-header "$response_headers" \ + --output /dev/null \ + "${JENKINS_URL}/job/${JENKINS_JOB}/buildWithParameters" + + queue_url="$(awk 'BEGIN {IGNORECASE=1} /^Location:/ {print $2}' "$response_headers" | tr -d '\r')" + + echo "Triggered Jenkins job ${JENKINS_JOB} for ${BRANCH_NAME}@${GIT_COMMIT}" + if [ -n "$queue_url" ]; then + echo "Queue item: ${queue_url}" + { + echo "### Jenkins Triggered" + echo + echo "- Job: \`${JENKINS_JOB}\`" + echo "- Pull request: \`${PR_NUMBER}\`" + echo "- Branch: \`${BRANCH_NAME}\`" + echo "- Commit: \`${GIT_COMMIT}\`" + echo "- Queue item: ${queue_url}" + } >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 6f5b6756dc..afa7f4d7f7 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -13,6 +13,9 @@ concurrency: jobs: build: + # Temporary guard while validating the Jenkins-backed PR workflow on the + # `jenkins-ci-pipeline` branch without running the normal Docker build matrix. + if: github.event.pull_request.head.ref != 'jenkins-ci-pipeline' runs-on: ubuntu-latest strategy: matrix: @@ -42,6 +45,7 @@ jobs: path: /tmp/star-sw-${{ env.STARENV }}.tar test: + if: github.event.pull_request.head.ref != 'jenkins-ci-pipeline' runs-on: ubuntu-latest needs: build strategy: @@ -68,6 +72,7 @@ jobs: sh -c "set -e; MALLOC_CHECK_=3 $TEST_CMD 2>&1 | tee log; grep 'Run completed' log" ROOT5_test_doEvents: + if: github.event.pull_request.head.ref != 'jenkins-ci-pipeline' runs-on: ubuntu-latest needs: build strategy: @@ -93,6 +98,7 @@ jobs: sh -c "set -e; $TEST_CMD 2>&1 | tee log; grep ' IO:' log" ROOT5_test_find_vertex: + if: github.event.pull_request.head.ref != 'jenkins-ci-pipeline' runs-on: ubuntu-latest needs: build strategy: diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000..5e9a48493e --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,114 @@ +pipeline { + agent none + + options { + timestamps() + skipDefaultCheckout(true) + parallelsAlwaysFailFast() + } + + parameters { + string(name: 'PR_NUMBER', defaultValue: '', description: 'Pull request number, optional') + string(name: 'BRANCH_NAME', defaultValue: 'main', description: 'Branch to build') + string(name: 'GIT_COMMIT', defaultValue: '', description: 'Commit SHA to build, optional') + string(name: 'REPO_URL', defaultValue: 'https://github.com/star-bnl/star-sw.git', description: 'Repository URL') + } + + environment { + ARTIFACT_DIR = 'artifacts' + } + + stages { + stage('Build Docker Images') { + matrix { + agent any + + axes { + axis { + name 'STAR_BASE' + values 'root5', 'root6' + } + axis { + name 'COMPILER' + values 'gcc485', 'gcc11' + } + } + + stages { + stage('Checkout') { + steps { + checkout([ + $class: 'GitSCM', + branches: [[name: "*/${params.BRANCH_NAME}"]], + userRemoteConfigs: [[ + url: "${params.REPO_URL}", + refspec: '+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*' + ]] + ]) + } + } + + stage('Checkout exact commit if provided') { + when { + expression { return params.GIT_COMMIT?.trim() } + } + steps { + sh ''' + set -euxo pipefail + git fetch --all --tags + git checkout "${GIT_COMMIT}" + git rev-parse HEAD + ''' + } + } + + stage('Prepare Docker Buildx') { + steps { + sh ''' + set -euxo pipefail + command -v docker + docker --version + docker buildx version + docker buildx use default || true + docker buildx inspect default --bootstrap || docker buildx inspect --bootstrap + ''' + } + } + + stage('Build Docker Image') { + steps { + sh ''' + set -euxo pipefail + starenv="${STAR_BASE}-${COMPILER}" + image_tag="ghcr.io/star-bnl/star-sw-${starenv}" + image_tar="${ARTIFACT_DIR}/star-sw-${starenv}.tar" + + mkdir -p "${ARTIFACT_DIR}" + + docker buildx build \ + --progress plain \ + --build-arg "starenv=${STAR_BASE}" \ + --build-arg "compiler=${COMPILER}" \ + --tag "${image_tag}" \ + --output "type=docker,dest=${image_tar}" \ + . + ''' + } + } + + stage('Archive Docker Image') { + steps { + archiveArtifacts artifacts: "artifacts/star-sw-${STAR_BASE}-${COMPILER}.tar", fingerprint: true + } + } + } + } + } + } + + post { + always { + echo "Build finished: ${currentBuild.currentResult}" + } + } +}