Merge pull request #31 from mikemaccana-edwardbot/chore/bump-dependen… #145
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: ASM | |
| on: | |
| schedule: | |
| - cron: "0 0 * * *" | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| branches: | |
| - main | |
| env: | |
| MAX_JOBS: 64 | |
| MIN_PROJECTS_PER_JOB: 4 | |
| MIN_PROJECTS_FOR_MATRIX: 4 | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| changed_projects: ${{ steps.analyze.outputs.changed_projects }} | |
| total_projects: ${{ steps.analyze.outputs.total_projects }} | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dorny/paths-filter@v3 | |
| id: changes | |
| if: github.event_name == 'pull_request' | |
| with: | |
| list-files: shell | |
| filters: | | |
| asm: | |
| - added|modified: '**/asm/**' | |
| workflow: | |
| - added|modified: '.github/workflows/solana-asm.yml' | |
| - name: Analyze Changes | |
| id: analyze | |
| run: | | |
| # Generate ignore pattern, excluding comments | |
| ignore_pattern=$(grep -v '^#' .github/.ghaignore | grep -v '^$' | tr '\n' '|' | sed 's/|$//') | |
| echo "Ignore pattern: $ignore_pattern" | |
| # Single source of truth for "what is a framework project": a directory | |
| # whose name is exactly "asm". `find -type d -name asm` gives us that by | |
| # construction — no substring matching, no path-segment trickery, so | |
| # anything containing the substring "asm" (e.g. "wasm/", "plasma/") can | |
| # never enter the build list. | |
| function get_projects() { | |
| find . -type d -name "asm" | grep -vE "$ignore_pattern" | sort | |
| } | |
| # Filter the full project list down to projects touched by the given | |
| # changed files. A file "touches" a project iff it lives inside that | |
| # project directory (prefix match on "<project>/"). This is an | |
| # intersection against get_projects(), so the result is always a subset | |
| # of the authoritative project list. | |
| function filter_by_changes() { | |
| local all_projects="$1" | |
| shift | |
| local changed_files=("$@") | |
| echo "$all_projects" | while read -r project; do | |
| [ -z "$project" ] && continue | |
| local project_prefix="${project#./}/" | |
| for file in "${changed_files[@]}"; do | |
| if [[ "$file" == "$project_prefix"* ]]; then | |
| echo "$project" | |
| break | |
| fi | |
| done | |
| done | sort -u | |
| } | |
| # Determine which projects to build and test | |
| if [[ "${{ github.event_name }}" == "push" || "${{ github.event_name }}" == "schedule" || "${{ steps.changes.outputs.workflow }}" == "true" ]]; then | |
| projects=$(get_projects) | |
| elif [[ "${{ steps.changes.outputs.asm }}" == "true" ]]; then | |
| changed_files=(${{ steps.changes.outputs.asm_files }}) | |
| projects=$(filter_by_changes "$(get_projects)" "${changed_files[@]}") | |
| else | |
| projects="" | |
| fi | |
| # Output project information | |
| if [[ -n "$projects" ]]; then | |
| echo "Projects to build and test" | |
| echo "$projects" | |
| total_projects=$(echo "$projects" | wc -l) | |
| echo "Total projects: $total_projects" | |
| echo "total_projects=$total_projects" >> $GITHUB_OUTPUT | |
| echo "changed_projects=$(echo "$projects" | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT | |
| else | |
| echo "No projects to build and test." | |
| echo "total_projects=0" >> $GITHUB_OUTPUT | |
| echo "changed_projects=[]" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Generate matrix | |
| id: matrix | |
| run: | | |
| total_projects=${{ steps.analyze.outputs.total_projects }} | |
| max_jobs=${{ env.MAX_JOBS }} | |
| min_projects_per_job=${{ env.MIN_PROJECTS_PER_JOB }} | |
| min_projects_for_matrix=${{ env.MIN_PROJECTS_FOR_MATRIX }} | |
| if [ "$total_projects" -lt "$min_projects_for_matrix" ]; then | |
| echo "matrix=[0]" >> $GITHUB_OUTPUT | |
| else | |
| projects_per_job=$(( (total_projects + max_jobs - 1) / max_jobs )) | |
| projects_per_job=$(( projects_per_job > min_projects_per_job ? projects_per_job : min_projects_per_job )) | |
| num_jobs=$(( (total_projects + projects_per_job - 1) / projects_per_job )) | |
| indices=$(seq 0 $(( num_jobs - 1 ))) | |
| echo "matrix=[$(echo $indices | tr ' ' ',')]" >> $GITHUB_OUTPUT | |
| fi | |
| build-and-test: | |
| needs: changes | |
| if: needs.changes.outputs.total_projects != '0' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| index: ${{ fromJson(needs.changes.outputs.matrix) }} | |
| name: build-and-test-group-${{ matrix.index }} | |
| outputs: | |
| failed_projects: ${{ steps.set-failed.outputs.failed_projects }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # The previous `npm install --global pnpm` step picked up a pnpm release that | |
| # treats ignored build scripts (bufferutil, utf-8-validate) as a hard error and | |
| # fails `pnpm install --frozen-lockfile`. The other workflows (anchor, native, | |
| # pinocchio, typescript) use pnpm/action-setup@v4, which pins a known-good pnpm | |
| # release (10.33.0 at time of writing) that only warns. Match that here. | |
| - uses: pnpm/action-setup@v4 | |
| - name: Use Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "lts/*" | |
| check-latest: true | |
| - name: Setup build environment | |
| id: setup | |
| run: | | |
| # Create the build and test function | |
| cat << 'EOF' > build_and_test.sh | |
| function build_and_test() { | |
| local project=$1 | |
| local solana_version=$2 | |
| echo "Building and Testing $project with Solana $solana_version" | |
| cd "$project" || return 1 | |
| # Install dependencies | |
| if ! pnpm install --frozen-lockfile; then | |
| echo "::error::pnpm install failed for $project" | |
| echo "$project: pnpm install failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt | |
| cd - > /dev/null | |
| return 1 | |
| fi | |
| # Build and Test | |
| if ! pnpm build-and-test; then | |
| echo "::error::build-and-test failed for $project" | |
| echo "$project: build-and-test failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt | |
| cd - > /dev/null | |
| return 1 | |
| fi | |
| # Run Rust unit tests | |
| if [ -d "program" ]; then | |
| echo "Running Rust unit tests for $project" | |
| if ! cargo test --manifest-path=./program/Cargo.toml; then | |
| echo "::error::Rust unit tests failed for $project" | |
| echo "$project: Rust unit tests failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt | |
| cd - > /dev/null | |
| return 1 | |
| fi | |
| fi | |
| echo "Build and tests succeeded for $project with $solana_version version." | |
| cd - > /dev/null | |
| return 0 | |
| } | |
| function process_projects() { | |
| local solana_version=$1 | |
| readarray -t all_projects < <(echo '${{ needs.changes.outputs.changed_projects }}' | jq -r '.[]?') | |
| start_index=$(( ${{ matrix.index }} * ${{ env.MIN_PROJECTS_PER_JOB }} )) | |
| end_index=$(( start_index + ${{ env.MIN_PROJECTS_PER_JOB }} )) | |
| end_index=$(( end_index > ${{ needs.changes.outputs.total_projects }} ? ${{ needs.changes.outputs.total_projects }} : end_index )) | |
| echo "Projects to build and test in this job" | |
| for i in $(seq $start_index $(( end_index - 1 ))); do | |
| echo "${all_projects[$i]}" | |
| done | |
| failed=false | |
| for i in $(seq $start_index $(( end_index - 1 ))); do | |
| echo "::group::Building and testing ${all_projects[$i]}" | |
| if ! build_and_test "${all_projects[$i]}" "$solana_version"; then | |
| failed=true | |
| fi | |
| echo "::endgroup::" | |
| done | |
| return $([ "$failed" = true ] && echo 1 || echo 0) | |
| } | |
| EOF | |
| # Make the script executable | |
| chmod +x build_and_test.sh | |
| # pnpm is installed by pnpm/action-setup@v4 above. Avoid `npm install --global pnpm` | |
| # here because that resolves to pnpm 10+, which errors on ignored build scripts. | |
| # Install sbpf assembler | |
| cargo install --git https://github.com/blueshift-gg/sbpf.git | |
| - name: Setup Solana Stable | |
| uses: heyAyushh/setup-solana@v5.9 | |
| with: | |
| # setup-anchor resolves tags like stable by querying GitHub API for latest release which can fail with 429 errors | |
| solana-cli-version: 3.1.14 | |
| - name: Build and Test with Stable | |
| run: | | |
| source build_and_test.sh | |
| solana -V | |
| rustc -V | |
| process_projects "stable" | |
| # continue-on-error because the beta channel may not have a valid release | |
| # (e.g. v4.0 returns 404 from release.anza.xyz). This is an upstream issue | |
| # with heyAyushh/setup-solana — beta setup clears the stable install first. | |
| - name: Setup Solana Beta | |
| continue-on-error: true | |
| uses: heyAyushh/setup-solana@v5.9 | |
| with: | |
| solana-cli-version: beta | |
| - name: Build and Test with Beta | |
| continue-on-error: true | |
| run: | | |
| source build_and_test.sh | |
| solana -V | |
| rustc -V | |
| process_projects "beta" | |
| - name: Set failed projects output | |
| id: set-failed | |
| if: failure() | |
| run: | | |
| if [ -f "$GITHUB_WORKSPACE/failed_projects.txt" ]; then | |
| failed_projects=$(cat $GITHUB_WORKSPACE/failed_projects.txt | jq -R -s -c 'split("\n")[:-1]') | |
| echo "failed_projects=$failed_projects" >> $GITHUB_OUTPUT | |
| else | |
| echo "failed_projects=[]" >> $GITHUB_OUTPUT | |
| fi | |
| summary: | |
| needs: [changes, build-and-test] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Create job summary | |
| run: | | |
| echo "## ASM Workflow Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- Total projects: ${{ needs.changes.outputs.total_projects }}" >> $GITHUB_STEP_SUMMARY | |
| # List all processed projects | |
| echo "<details>" >> $GITHUB_STEP_SUMMARY | |
| echo "<summary>Projects processed (click to expand)</summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo '${{ needs.changes.outputs.changed_projects }}' | jq -r '.[]' | while read project; do | |
| echo "- $project" >> $GITHUB_STEP_SUMMARY | |
| done | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| # Report build and test results | |
| if [[ "${{ needs.build-and-test.result }}" == "failure" ]]; then | |
| echo "## :x: Build or tests failed" >> $GITHUB_STEP_SUMMARY | |
| echo "<details>" >> $GITHUB_STEP_SUMMARY | |
| echo "<summary>Failed projects (click to expand)</summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| failed_projects='${{ needs.build-and-test.outputs.failed_projects }}' | |
| if [[ -n "$failed_projects" ]]; then | |
| echo "$failed_projects" | jq -r '.[]' | while IFS=: read -r project failure_reason; do | |
| echo "- **$project**" >> $GITHUB_STEP_SUMMARY | |
| echo " - Failure reason: $failure_reason" >> $GITHUB_STEP_SUMMARY | |
| done | |
| else | |
| echo "No failed projects reported. This might indicate an unexpected error in the workflow." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| elif [[ "${{ needs.build-and-test.result }}" == "success" ]]; then | |
| echo "## :white_check_mark: All builds and tests passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "## :warning: Build and test job was skipped or canceled" >> $GITHUB_STEP_SUMMARY | |
| fi |