Skip to content

CI: [7214/merge]

CI: [7214/merge] #33

Workflow file for this run

# The main Github Actions workflow
name: CI
run-name: "CI: [${{ github.ref_name }}]"
on:
merge_group:
types:
- checks_requested
workflow_dispatch:
pull_request:
types:
- opened
- reopened
- synchronize
# Set default permissions
permissions:
contents: read # default for all jobs
# Set default shell
defaults:
run:
shell: bash
# Configure concurrency
concurrency:
group: ci-${{ github.head_ref || github.ref || github.run_id }}
# Always cancel duplicate jobs
cancel-in-progress: true
env:
TEST_ARCHIVE_FILE: "~/test_archive.tar.zst"
BTC_VERSION: "25.0"
jobs:
# --- Global Variables Setup ---------------------------------------------
# Bridges global env variables to the jobs context so they are safely
# accessible by downstream reusable workflows.
# Also define default runs-on anchor.
setup-env:
name: "Setup: Global Variables"
runs-on: &shared_config ubuntu-latest
outputs:
test-archive-file: ${{ steps.set-env.outputs.test_archive_file }}
btc-version: ${{ steps.set-env.outputs.btc_version }}
job-should-run: ${{ steps.set-env.outputs.job_should_run }}
steps:
- name: Export Env Variables
id: set-env
run: |
# Capture the archive file input, then expand the leading ~ to $HOME
INPUT_FILE="${{ env.TEST_ARCHIVE_FILE }}"
ARCHIVE_FILE="${INPUT_FILE/#\~/$HOME}"
echo "test_archive_file=$ARCHIVE_FILE" >> $GITHUB_OUTPUT
echo "btc_version=${{ env.BTC_VERSION }}" >> $GITHUB_OUTPUT
# Check that evaluates if various jobs should run. Generally we care if it's a workflow dispatch, PR, or merge group.
echo "job_should_run=${{ github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group' }}" >> $GITHUB_OUTPUT
# --- Changelog check ------------------------------------------------------
# Checks for changelog updates (or the 'No Changelog' label) on a PR
# Required to pass for all subsequent jobs
check-changelog:
name: "Check: Changelog"
runs-on: *shared_config
permissions:
contents: read
pull-requests: read
steps:
# Checkout the code
# - only .github is required for this job
- name: Checkout the latest code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
sparse-checkout: |
.github
# Run script to detect changelog entries
- name: Check for changelog fragments
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const changelog = require('./.github/scripts/changelog.js');
await changelog({ github, context, core });
# --- Check Rust Formatting ------------------------------------------------
# Checks for correct formatting
# Required to pass for all subsequent jobs
check-rustfmt:
name: "Check: Rust Formatting"
needs:
- check-changelog
runs-on: *shared_config
steps:
- name: Checkout the latest code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
# Setup the rust toolchain with specified rustfmt
- name: Setup Rust Toolchain
uses: ./.github/actions/setup-rust-toolchain
with:
components: rustfmt
# Check Rust formatting
- name: Rustfmt
run: bash ./.github/scripts/rustfmt.sh
# --- Check if this is a release build -------------------------------------
# Outputs:
# - node_tag: Tag of the stacks-node if the branch is a release one (example: release/3.4.0.0.1), null otherwise
# - signer_tag: Tag of the stacks-signer if the branch is a release one (example: release/3.4.0.0.1.0), null otherwise
# - is_node_release: True if the branch represents a 'stacks-node' release, false otherwise.
# - If this is true, 'is_signer_release' will also be true, since a 'stacks-signer' binary is always released alongside 'stacks-node'.
# - is_signer_release: True if the branch represents a 'stacks-signer' release, false otherwise.
check-release:
name: "Check: Release"
needs:
- check-changelog
- check-rustfmt
if: >-
github.event.repository.visibility == 'public'
runs-on: *shared_config
outputs:
is_release: ${{ steps.check-release.outputs.is_node_release || steps.check-release.outputs.is_signer_release }}
steps:
# Checkout the code
# - only .github is required for this job
- name: Checkout the latest code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
sparse-checkout: |
.github
# Run script to check if this is a release and set outputs
- name: Check Release
id: check-release
env:
BRANCH: ${{ github.ref_name }}
run: bash ./.github/scripts/check_release.sh
# --- Cargo Hack check------------------------------------------------------
check-cargo-hack:
name: "Check: Cargo Hack"
needs:
- check-changelog
- check-release
- check-rustfmt
- setup-env
if: >-
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/check-cargo-hack.yml
# --- Validate constants dumped by stacks-inspect --------------------------
check-constants:
name: "Check: Constants"
needs:
- check-changelog
- check-release
- check-rustfmt
- setup-env
if: >-
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/check-constants.yml
# --- Setup reusable caches for tests ------------------------------------
setup-test-caches:
name: "Setup: Test Caches"
needs:
- check-changelog
- check-release
- check-rustfmt
- setup-env
if: >-
needs.check-release.outputs.is_release == 'true' ||
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/setup-test-caches.yml
with:
archive-file: ${{ needs.setup-env.outputs.test-archive-file }}
btc-version: ${{ needs.setup-env.outputs.btc-version }}
# --- Run stacks core tests ------------------------------------------------
tests-stacks-core:
name: "Tests: Stacks Core"
needs:
- check-changelog
- check-release
- check-rustfmt
- setup-test-caches
- setup-env
if: >-
needs.check-release.outputs.is_release == 'true' ||
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/tests-stacks-core.yml
with:
archive-file: ${{ needs.setup-env.outputs.test-archive-file }}
# --- Run bitcoin Tests ----------------------------------------------------
tests-bitcoin:
name: "Tests: Bitcoin"
needs:
- check-changelog
- check-release
- check-rustfmt
- setup-test-caches
- setup-env
if: >-
needs.check-release.outputs.is_release == 'true' ||
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/tests-bitcoin.yml
with:
archive-file: ${{ needs.setup-env.outputs.test-archive-file }}
# --- Run bitcoin RPC Tests ------------------------------------------------
tests-bitcoin-rpc:
name: "Tests: Bitcoin RPC"
needs:
- check-changelog
- check-rustfmt
- check-release
- setup-test-caches
- setup-env
if: >-
needs.check-release.outputs.is_release == 'true' ||
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/tests-bitcoin-rpc.yml
with:
archive-file: ${{ needs.setup-env.outputs.test-archive-file }}
btc-version: ${{ needs.setup-env.outputs.btc-version }}
# --- Run P2P Tests --------------------------------------------------------
tests-p2p:
name: "Tests: P2P"
needs:
- check-changelog
- check-rustfmt
- check-release
- setup-test-caches
- setup-env
if: >-
needs.check-release.outputs.is_release == 'true' ||
needs.setup-env.outputs.job-should-run == 'true'
uses: ./.github/workflows/tests-p2p.yml
with:
archive-file: ${{ needs.setup-env.outputs.test-archive-file }}
# --- Create a release ---------------------------------------
# Creates binary archives for several architectures
# Creates Docker images and pushes to ghcr registry
# Creates a draft GitHub release
create-release:
name: "Create: Release"
needs:
- check-changelog
- check-release
- check-rustfmt
if: >-
github.event.repository.visibility == 'public' &&
needs.check-release.outputs.is_release == 'true'
permissions:
contents: write # required for github release
id-token: write # required for attestation
attestations: write # required for attestation
packages: write # required for image push to ghcr
uses: ./.github/workflows/release-github.yml
with:
node_tag: ${{ needs.check-release.outputs.node_tag }} # 5 place version format like 1.2.3.4.5
signer_tag: ${{ needs.check-release.outputs.signer_tag }} # 6 place version format like 1.2.3.4.5.6
is_node_release: ${{ needs.check-release.outputs.is_node_release }} # boolean used in matrix conditional in .github/workflows/release-github.yml
# --- Epoch Tests ----------------------------------------------------------
# Only run for releases
tests-epoch:
name: "Tests: Epoch"
needs:
- check-changelog
- check-rustfmt
- check-release
- setup-test-caches
- setup-env
if: >-
needs.check-release.outputs.is_release == 'true'
uses: ./.github/workflows/tests-epoch.yml
with:
archive-file: ${{ needs.setup-env.outputs.test-archive-file }}
# --- Create Code Coverage Report -------------------------------------------
create-code-coverage-report:
if: >-
always() &&
github.event.repository.visibility == 'public'
name: "Create: Code Coverage Report"
runs-on: *shared_config
needs:
- check-changelog
- check-rustfmt
- check-release
- tests-bitcoin
- tests-epoch
- tests-p2p
- tests-stacks-core
env:
REPORT_FILES_DIR: "code_coverage_files"
REPORT_FILES_EXT: "info"
REPORT_MERGE_NUM_THREADS: 4
steps:
# Fail this job explicitly if any upstream job fails
- name: Check for upstream failures
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
shell: bash
run: |
echo "ERROR: One or more required CI jobs failed or were cancelled. Failing coverage report."
exit 1
# Checkout the code (Coveralls requires source code to be available when action is called)
- name: Checkout the latest code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Download the code coverage .info files generated by tests from artifacts (prefixed by commit SHA)
- name: Download code coverage artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 8.0.1
with:
pattern: ${{ github.sha }}-*.${{ env.REPORT_FILES_EXT }}
path: ${{ env.REPORT_FILES_DIR }}
merge-multiple: true
# Check for at least 1 code coverage files
- name: Check for at least 1 code coverage file
shell: bash
run: |
EXT="${{ env.REPORT_FILES_EXT }}"
DIR="${{ env.REPORT_FILES_DIR }}"
file_count=$(find -type f -wholename "./$DIR/*.$EXT" | wc -l)
if [ "$file_count" -eq 0 ]; then
echo "ERROR: no code coverage files of type .$EXT found to merge. Verify that they were correctly generated and uploaded in prior CI steps"
exit 1
fi
# Install lcov for merging the reports
- name: Install lcov
shell: bash
run: |
sudo apt-get install -y --no-install-recommends lcov
# Merge n coverage report files into 1 file using lcov
- name: Merge code coverage files
shell: bash
run: |
EXT="${{ env.REPORT_FILES_EXT }}"
DIR="${{ env.REPORT_FILES_DIR }}"
INTERMEDIATE_FILES=()
PIDS=() # Array to track background process IDs
cd "$DIR" || exit 1
# 1. Collect all files into an array
mapfile -d '' ALL_FILES < <(find . -type f -name "*.$EXT" -print0)
# 2. Process in chunks (Dynamically calculated for 4 threads)
TOTAL_FILES=${#ALL_FILES[@]}
CHUNK_SIZE=$(( (TOTAL_FILES + 3) / $REPORT_MERGE_NUM_THREADS ))
# Guardrail: Ensure CHUNK_SIZE is at least 1 to prevent infinite loops
if [ "$CHUNK_SIZE" -le 0 ]; then
CHUNK_SIZE=1
fi
echo "Total files to process: $TOTAL_FILES. Calculated chunk size for $REPORT_MERGE_NUM_THREADS threads: $CHUNK_SIZE"
for (( i=0; i<TOTAL_FILES; i+=CHUNK_SIZE )); do
# Slice the array for the current chunk
CHUNK=("${ALL_FILES[@]:i:CHUNK_SIZE}")
# Build the lcov arguments for this specific chunk
CHUNK_ARGS=()
for file in "${CHUNK[@]}"; do
CHUNK_ARGS+=(-a "$file")
done
# Define a unique name for the intermediate report
PART_FILE="code_coverage_part_$((i / CHUNK_SIZE)).$EXT"
INTERMEDIATE_FILES+=("$PART_FILE")
# Run lcov in the background
echo "Processing chunk $((i / CHUNK_SIZE + 1))..."
lcov "${CHUNK_ARGS[@]}" -o "$PART_FILE" &
PIDS+=("$!") # Capture the PID of the process just put in the background
done
# 3. Wait for all background processes to finish and catch errors
echo "Waiting for ${#PIDS[@]} background jobs to complete..."
FAILED=0
for pid in "${PIDS[@]}"; do
if ! wait "$pid"; then
echo "ERROR: Background process with PID $pid failed."
FAILED=1
fi
done
if [ "$FAILED" -ne 0 ]; then
echo "ERROR: One or more lcov chunk operations failed. Aborting final merge."
exit 1
fi
# 4. Final Merge: Combine the intermediate files into the final report
FINAL_ARGS=()
for part in "${INTERMEDIATE_FILES[@]}"; do
FINAL_ARGS+=(-a "$part")
done
echo "Performing final merge of ${#INTERMEDIATE_FILES[@]} intermediate files..."
lcov "${FINAL_ARGS[@]}" -o "code-coverage-report.$EXT"
cd ..
# Upload the merged code coverage file to Coveralls
- name: Upload code coverage to Coveralls
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
with:
file: ${{ env.REPORT_FILES_DIR }}/code-coverage-report.${{ env.REPORT_FILES_EXT }}
compare-ref: ${{ github.base_ref }} # defaults to master if this isn't supplied
build-number: ${{ github.run_id }}-${{ github.run_attempt }} # include run attempt in build so that repeated CI job runs don't cause a "job already closed" error on upload
fail-on-error: true