vault-strategy: index-seeded strategies, auto-deploying deposits, and set_weight #386
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: Native | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| branches: | |
| - main | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| MAX_JOBS: 64 | |
| MIN_PROJECTS_PER_JOB: 4 | |
| MIN_PROJECTS_FOR_MATRIX: 4 | |
| # See https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| 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@v5 | |
| with: | |
| fetch-depth: 2 | |
| - uses: dorny/paths-filter@v4 | |
| id: changes | |
| if: github.event_name == 'pull_request' | |
| with: | |
| list-files: shell | |
| filters: | | |
| native: | |
| - added|modified: '**/native/**' | |
| workflow: | |
| - added|modified: '.github/workflows/native.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 "native". `find -type d -name native` gives us | |
| # that by construction — no substring matching, no path-segment trickery, | |
| # so siblings like "alternative/" can never enter the build list. | |
| function get_projects() { | |
| # An empty .ghaignore makes ignore_pattern empty, and `grep -vE ""` | |
| # matches everything, silently emptying the project list - only | |
| # filter when there is actually a pattern. | |
| if [[ -n "$ignore_pattern" ]]; then | |
| find . -type d -name "native" | grep -vE "$ignore_pattern" | sort | |
| else | |
| find . -type d -name "native" | sort | |
| fi | |
| } | |
| # 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 }}" == "schedule" || "${{ steps.changes.outputs.workflow }}" == "true" ]]; then | |
| # Workflow file changed or schedule — build everything | |
| projects=$(get_projects) | |
| elif [[ "${{ github.event_name }}" == "push" ]]; then | |
| # On push, only build projects with changes since parent commit | |
| mapfile -t changed_files < <(git diff --name-only HEAD~1 HEAD 2>/dev/null || true) | |
| if [ ${#changed_files[@]} -eq 0 ]; then | |
| projects=$(get_projects) | |
| else | |
| projects=$(filter_by_changes "$(get_projects)" "${changed_files[@]}") | |
| fi | |
| elif [[ "${{ steps.changes.outputs.native }}" == "true" ]]; then | |
| changed_files=(${{ steps.changes.outputs.native_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@v5 | |
| - name: Setup sccache | |
| uses: mozilla-actions/sccache-action@v0.0.9 | |
| - name: Configure sccache | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV | |
| echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV | |
| - name: Cache Cargo registry and git | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }} | |
| restore-keys: | | |
| cargo-${{ runner.os }}- | |
| - 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 | |
| # Collect program manifests: single-program projects use program/, | |
| # multi-program projects (e.g. cross-program-invocation) use programs/*/. | |
| local manifests=() | |
| if [ -d "program" ]; then | |
| manifests=("./program/Cargo.toml") | |
| elif [ -d "programs" ]; then | |
| for manifest in programs/*/Cargo.toml; do | |
| manifests+=("./$manifest") | |
| done | |
| fi | |
| if [ ${#manifests[@]} -eq 0 ]; then | |
| echo "::error::no program manifest found for $project" | |
| echo "$project: no program manifest found with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt | |
| cd - > /dev/null | |
| return 1 | |
| fi | |
| # Build the .so files first: the Rust + LiteSVM tests embed them | |
| # at compile time via include_bytes!, so the tests cannot even | |
| # compile without a build, and a stale .so would test old code. | |
| for manifest in "${manifests[@]}"; do | |
| if ! cargo build-sbf --manifest-path="$manifest"; then | |
| echo "::error::build failed for $project ($manifest)" | |
| echo "$project: build failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt | |
| cd - > /dev/null | |
| return 1 | |
| fi | |
| done | |
| # Run the Rust + LiteSVM tests | |
| for manifest in "${manifests[@]}"; do | |
| if ! cargo test --manifest-path="$manifest"; then | |
| echo "::error::tests failed for $project ($manifest)" | |
| echo "$project: tests failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt | |
| cd - > /dev/null | |
| return 1 | |
| fi | |
| done | |
| 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 | |
| - 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 | |
| - name: Show sccache stats | |
| if: always() | |
| run: sccache --show-stats | |
| summary: | |
| needs: [changes, build-and-test] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Create job summary | |
| run: | | |
| echo "## Native 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 |