steel.yml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. name: Steel
  2. on:
  3. schedule:
  4. - cron: "0 0 * * *"
  5. push:
  6. branches:
  7. - main
  8. pull_request:
  9. types: [opened, synchronize, reopened]
  10. branches:
  11. - main
  12. env:
  13. MAX_JOBS: 64
  14. MIN_PROJECTS_PER_JOB: 4
  15. MIN_PROJECTS_FOR_MATRIX: 4
  16. jobs:
  17. changes:
  18. runs-on: ubuntu-latest
  19. permissions:
  20. pull-requests: read
  21. outputs:
  22. changed_projects: ${{ steps.analyze.outputs.changed_projects }}
  23. total_projects: ${{ steps.analyze.outputs.total_projects }}
  24. matrix: ${{ steps.matrix.outputs.matrix }}
  25. steps:
  26. - uses: actions/checkout@v4
  27. - uses: dorny/paths-filter@v3
  28. id: changes
  29. if: github.event_name == 'pull_request'
  30. with:
  31. list-files: shell
  32. filters: |
  33. steel:
  34. - added|modified: '**/steel/**'
  35. workflow:
  36. - added|modified: '.github/workflows/steel.yml'
  37. - name: Analyze Changes
  38. id: analyze
  39. run: |
  40. # Generate ignore pattern, excluding comments
  41. ignore_pattern=$(grep -v '^#' .github/.ghaignore | grep -v '^$' | tr '\n' '|' | sed 's/|$//')
  42. echo "Ignore pattern: $ignore_pattern"
  43. function get_projects() {
  44. find . -type d -name "steel" | grep -vE "$ignore_pattern" | sort
  45. }
  46. # Determine which projects to build and test
  47. if [[ "${{ github.event_name }}" == "push" || "${{ github.event_name }}" == "schedule" || "${{ steps.changes.outputs.workflow }}" == "true" ]]; then
  48. projects=$(get_projects)
  49. elif [[ "${{ steps.changes.outputs.steel }}" == "true" ]]; then
  50. changed_files=(${{ steps.changes.outputs.steel_files }})
  51. projects=$(for file in "${changed_files[@]}"; do dirname "${file}" | grep steel | sed 's#/steel/.*#/steel#g'; done | grep -vE "$ignore_pattern" | sort -u)
  52. else
  53. projects=""
  54. fi
  55. # Output project information
  56. if [[ -n "$projects" ]]; then
  57. echo "Projects to build and test"
  58. echo "$projects"
  59. total_projects=$(echo "$projects" | wc -l)
  60. echo "Total projects: $total_projects"
  61. echo "total_projects=$total_projects" >> $GITHUB_OUTPUT
  62. echo "changed_projects=$(echo "$projects" | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
  63. else
  64. echo "No projects to build and test."
  65. echo "total_projects=0" >> $GITHUB_OUTPUT
  66. echo "changed_projects=[]" >> $GITHUB_OUTPUT
  67. fi
  68. - name: Generate matrix
  69. id: matrix
  70. run: |
  71. total_projects=${{ steps.analyze.outputs.total_projects }}
  72. max_jobs=${{ env.MAX_JOBS }}
  73. min_projects_per_job=${{ env.MIN_PROJECTS_PER_JOB }}
  74. min_projects_for_matrix=${{ env.MIN_PROJECTS_FOR_MATRIX }}
  75. if [ "$total_projects" -lt "$min_projects_for_matrix" ]; then
  76. echo "matrix=[0]" >> $GITHUB_OUTPUT
  77. else
  78. projects_per_job=$(( (total_projects + max_jobs - 1) / max_jobs ))
  79. projects_per_job=$(( projects_per_job > min_projects_per_job ? projects_per_job : min_projects_per_job ))
  80. num_jobs=$(( (total_projects + projects_per_job - 1) / projects_per_job ))
  81. indices=$(seq 0 $(( num_jobs - 1 )))
  82. echo "matrix=[$(echo $indices | tr ' ' ',')]" >> $GITHUB_OUTPUT
  83. fi
  84. rust-checks:
  85. needs: changes
  86. if: ${{ github.event_name == 'pull_request' && needs.changes.outputs.total_projects != '0' }}
  87. name: Rust Checks
  88. runs-on: ubuntu-latest
  89. steps:
  90. - uses: actions/checkout@v4
  91. - uses: dtolnay/rust-toolchain@stable
  92. with:
  93. components: rustfmt, clippy
  94. - name: Run sccache-cache
  95. if: github.event_name != 'release'
  96. uses: mozilla-actions/sccache-action@v0.0.6
  97. - name: Set Rust cache env vars
  98. if: github.event_name != 'release'
  99. run: |
  100. echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
  101. echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
  102. - name: Run fmt and clippy
  103. run: |
  104. readarray -t all_projects < <(echo '${{ needs.changes.outputs.changed_projects }}' | jq -r '.[]?')
  105. for project in "${all_projects[@]}"; do
  106. echo "::group::Checking ${project}"
  107. if [ ! -f "${project}/Cargo.toml" ]; then
  108. echo "::error::No Cargo.toml found in ${project}"
  109. exit 1
  110. fi
  111. cd "${project}"
  112. cargo fmt --check
  113. cargo clippy --all-features -- -D warnings
  114. cd - > /dev/null
  115. echo "::endgroup::"
  116. done
  117. build-and-test:
  118. needs: changes
  119. if: needs.changes.outputs.total_projects != '0'
  120. runs-on: ubuntu-latest
  121. strategy:
  122. fail-fast: false
  123. matrix:
  124. index: ${{ fromJson(needs.changes.outputs.matrix) }}
  125. name: build-and-test-group-${{ matrix.index }}
  126. outputs:
  127. failed_projects: ${{ steps.set-failed.outputs.failed_projects }}
  128. steps:
  129. - uses: actions/checkout@v4
  130. - uses: dtolnay/rust-toolchain@stable
  131. - name: Run sccache-cache
  132. if: github.event_name != 'release'
  133. uses: mozilla-actions/sccache-action@v0.0.6
  134. - name: Set Rust cache env vars
  135. if: github.event_name != 'release'
  136. run: |
  137. echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
  138. echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
  139. - uses: actions/cache@v3
  140. with:
  141. path: ~/.cargo/bin/steel
  142. key: ${{ runner.os }}-steel-cli
  143. - name: Display Versions and Install pnpm
  144. run: |
  145. solana -V
  146. solana-keygen new --no-bip39-passphrase
  147. rustc -V
  148. anchor -V
  149. npm i -g pnpm
  150. - name: Use Node.js
  151. uses: actions/setup-node@v4
  152. with:
  153. node-version: 20.x
  154. check-latest: true
  155. - name: Setup build environment
  156. id: setup
  157. run: |
  158. npm install --global pnpm
  159. # Create the build and test function
  160. cat << 'EOF' > build_and_test.sh
  161. function build_and_test() {
  162. local project=$1
  163. local solana_version=$2
  164. echo "Building and Testing $project with Solana $solana_version"
  165. cd "$project" || return 1
  166. # Install dependencies
  167. if [ -f "package.json" ]; then
  168. if ! pnpm install --frozen-lockfile; then
  169. echo "::error::pnpm install failed for $project"
  170. echo "$project: pnpm install failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt
  171. cd - > /dev/null
  172. return 1
  173. fi
  174. # Build
  175. if ! pnpm build; then
  176. echo "::error::build failed for $project"
  177. echo "$project: build failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt
  178. cd - > /dev/null
  179. return 1
  180. fi
  181. # Test
  182. if ! pnpm build-and-test; then
  183. echo "::error::tests failed for $project"
  184. echo "$project: tests failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt
  185. cd - > /dev/null
  186. return 1
  187. fi
  188. else
  189. # Use Steel CLI
  190. if ! cargo install --quiet steel-cli; then
  191. echo "::error::steel-cli installation failed for $project"
  192. echo "$project: steel-cli installation failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt
  193. cd - > /dev/null
  194. return 1
  195. fi
  196. # Build
  197. if ! steel build; then
  198. echo "::error::steel build failed for $project"
  199. echo "$project: steel build failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt
  200. cd - > /dev/null
  201. return 1
  202. fi
  203. # Test
  204. if ! steel test; then
  205. echo "::error::steel test failed for $project"
  206. echo "$project: steel test failed with $solana_version" >> $GITHUB_WORKSPACE/failed_projects.txt
  207. cd - > /dev/null
  208. return 1
  209. fi
  210. fi
  211. echo "Build and tests succeeded for $project with $solana_version version."
  212. cd - > /dev/null
  213. return 0
  214. }
  215. function process_projects() {
  216. local solana_version=$1
  217. readarray -t all_projects < <(echo '${{ needs.changes.outputs.changed_projects }}' | jq -r '.[]?')
  218. start_index=$(( ${{ matrix.index }} * ${{ env.MIN_PROJECTS_PER_JOB }} ))
  219. end_index=$(( start_index + ${{ env.MIN_PROJECTS_PER_JOB }} ))
  220. end_index=$(( end_index > ${{ needs.changes.outputs.total_projects }} ? ${{ needs.changes.outputs.total_projects }} : end_index ))
  221. echo "Projects to build and test in this job"
  222. for i in $(seq $start_index $(( end_index - 1 ))); do
  223. echo "${all_projects[$i]}"
  224. done
  225. failed=false
  226. for i in $(seq $start_index $(( end_index - 1 ))); do
  227. echo "::group::Building and testing ${all_projects[$i]}"
  228. if ! build_and_test "${all_projects[$i]}" "$solana_version"; then
  229. failed=true
  230. fi
  231. echo "::endgroup::"
  232. done
  233. return $([ "$failed" = true ] && echo 1 || echo 0)
  234. }
  235. EOF
  236. # Make the script executable
  237. chmod +x build_and_test.sh
  238. - name: Setup Solana stable
  239. uses: heyAyushh/setup-solana@v5.4
  240. with:
  241. solana-cli-version: stable
  242. - name: Build and Test with Stable
  243. env:
  244. SCCACHE_GHA_ENABLED: "true"
  245. RUSTC_WRAPPER: "sccache"
  246. run: |
  247. source build_and_test.sh
  248. solana -V
  249. rustc -V
  250. process_projects "stable"
  251. sccache --show-stats
  252. - name: Setup Solana 1.18.17
  253. uses: heyAyushh/setup-solana@v5.4
  254. with:
  255. solana-cli-version: 1.18.17
  256. - name: Build and Test with 1.18.17
  257. env:
  258. SCCACHE_GHA_ENABLED: "true"
  259. RUSTC_WRAPPER: "sccache"
  260. run: |
  261. source build_and_test.sh
  262. solana -V
  263. rustc -V
  264. process_projects "1.18.17"
  265. sccache --show-stats
  266. - name: Set failed projects output
  267. id: set-failed
  268. if: failure()
  269. run: |
  270. if [ -f "$GITHUB_WORKSPACE/failed_projects.txt" ]; then
  271. failed_projects=$(cat $GITHUB_WORKSPACE/failed_projects.txt | jq -R -s -c 'split("\n")[:-1]')
  272. echo "failed_projects=$failed_projects" >> $GITHUB_OUTPUT
  273. else
  274. echo "failed_projects=[]" >> $GITHUB_OUTPUT
  275. fi
  276. summary:
  277. needs: [changes, build-and-test]
  278. if: always()
  279. runs-on: ubuntu-latest
  280. steps:
  281. - uses: actions/checkout@v4
  282. - name: Create job summary
  283. run: |
  284. echo "## Steel Workflow Summary" >> $GITHUB_STEP_SUMMARY
  285. echo "- Total projects: ${{ needs.changes.outputs.total_projects }}" >> $GITHUB_STEP_SUMMARY
  286. # List all processed projects
  287. echo "<details>" >> $GITHUB_STEP_SUMMARY
  288. echo "<summary>Projects processed (click to expand)</summary>" >> $GITHUB_STEP_SUMMARY
  289. echo "" >> $GITHUB_STEP_SUMMARY
  290. echo '${{ needs.changes.outputs.changed_projects }}' | jq -r '.[]' | while read project; do
  291. echo "- $project" >> $GITHUB_STEP_SUMMARY
  292. done
  293. echo "" >> $GITHUB_STEP_SUMMARY
  294. echo "</details>" >> $GITHUB_STEP_SUMMARY
  295. # Report build and test results
  296. if [[ "${{ needs.build-and-test.result }}" == "failure" ]]; then
  297. echo "## :x: Build or tests failed" >> $GITHUB_STEP_SUMMARY
  298. echo "<details>" >> $GITHUB_STEP_SUMMARY
  299. echo "<summary>Failed projects (click to expand)</summary>" >> $GITHUB_STEP_SUMMARY
  300. echo "" >> $GITHUB_STEP_SUMMARY
  301. failed_projects='${{ needs.build-and-test.outputs.failed_projects }}'
  302. if [[ -n "$failed_projects" ]]; then
  303. echo "$failed_projects" | jq -r '.[]' | while IFS=: read -r project failure_reason; do
  304. echo "- **$project**" >> $GITHUB_STEP_SUMMARY
  305. echo " - Failure reason: $failure_reason" >> $GITHUB_STEP_SUMMARY
  306. done
  307. else
  308. echo "No failed projects reported. This might indicate an unexpected error in the workflow." >> $GITHUB_STEP_SUMMARY
  309. fi
  310. echo "" >> $GITHUB_STEP_SUMMARY
  311. echo "</details>" >> $GITHUB_STEP_SUMMARY
  312. elif [[ "${{ needs.build-and-test.result }}" == "success" ]]; then
  313. echo "## :white_check_mark: All builds and tests passed" >> $GITHUB_STEP_SUMMARY
  314. else
  315. echo "## :warning: Build and test job was skipped or canceled" >> $GITHUB_STEP_SUMMARY
  316. fi