@@ -196,62 +196,13 @@ jobs:
196196
197197 exit $XC_STATUS
198198
199- - name : Comment build failure on PR
200- if : failure() && github.event.pull_request.head.repo.fork == false
201- uses : actions/github-script@v8
199+ - name : Upload build log
200+ if : always()
201+ uses : actions/upload-artifact@v6
202202 with :
203- script : |
204- const fs = require('fs');
205- const path = 'build.log';
206- let body = '❌ iOS CI build failed.\n\n';
207- if (fs.existsSync(path)) {
208- const log = fs.readFileSync(path, 'utf8');
209- const lines = log.split(/\r?\n/);
210- const errorLines = lines.filter((line) => /^(.*?):(\d+):(\d+):\s+error:/i.test(line));
211- if (errorLines.length > 0) {
212- body += "Compiler error lines:\n\n```\n" + errorLines.join('\n') + '\n```\n';
213-
214- const repoRoot = process.env.GITHUB_WORKSPACE || process.cwd();
215- const pathMod = require('path');
216- const snippets = [];
217- for (const line of errorLines) {
218- const match = line.match(/^(.*?):(\d+):(\d+):\s+error:/);
219- if (!match) continue;
220- const filePath = match[1];
221- const lineNum = parseInt(match[2], 10);
222- const absPath = filePath.startsWith('/') ? filePath : pathMod.join(repoRoot, filePath);
223- if (!fs.existsSync(absPath)) continue;
224- const fileLines = fs.readFileSync(absPath, 'utf8').split(/\r?\n/);
225- const start = Math.max(0, lineNum - 3);
226- const end = Math.min(fileLines.length, lineNum + 2);
227- const snippet = fileLines
228- .slice(start, end)
229- .map((l, idx) => {
230- const ln = start + idx + 1;
231- return `${ln.toString().padStart(4, ' ')}| ${l}`;
232- })
233- .join('\n');
234- snippets.push(`File: ${filePath}:${lineNum}\n${snippet}`);
235- }
236- if (snippets.length > 0) {
237- body += "\nCode excerpts:\n\n```\n" + snippets.join('\n\n') + "\n```\n";
238- }
239- } else {
240- body += "No compiler-style error diagnostics were found in build.log.";
241- }
242- } else {
243- body += 'build.log not found.';
244- }
245- if (!context.payload.pull_request) {
246- core.info('No PR context; skipping comment.');
247- return;
248- }
249- await github.rest.issues.createComment({
250- owner: context.repo.owner,
251- repo: context.repo.repo,
252- issue_number: context.payload.pull_request.number,
253- body
254- });
203+ name : ios-build
204+ path : build.log
205+ if-no-files-found : ignore
255206
256207 test :
257208 name : Test (${{ matrix.name }})
@@ -476,26 +427,31 @@ jobs:
476427 path : test-results/${{ matrix.name }}
477428 if-no-files-found : ignore
478429
479- test- report :
480- name : Test Report
430+ report :
431+ name : Report
481432 runs-on : ubuntu-latest
482- needs : test
483- if : always() && needs.test.result == 'failure' && github.event.pull_request.head.repo.fork == false
433+ needs : [build, test]
434+ if : always() && ( needs.build.result == 'failure' || needs. test.result == 'failure') && github.event.pull_request.head.repo.fork == false
484435 steps :
485- - name : Download test logs
436+ - uses : actions/checkout@v5
437+
438+ - name : Download CI logs
486439 uses : actions/download-artifact@v7
440+ continue-on-error : true
487441 with :
488442 path : test-artifacts
489443
490- - name : Comment test failure on PR
444+ - name : Comment CI failure on PR
491445 uses : actions/github-script@v8
446+ env :
447+ BUILD_RESULT : ${{ needs.build.result }}
448+ TEST_RESULT : ${{ needs.test.result }}
492449 with :
493450 script : |
494451 const fs = require('fs');
495452 const path = require('path');
496453
497454 const root = 'test-artifacts';
498- const summaries = [];
499455
500456 function walk(directory) {
501457 if (!fs.existsSync(directory)) return [];
@@ -560,6 +516,64 @@ jobs:
560516 return selectedLines.slice(0, 30).join('\n');
561517 }
562518
519+ function extractBuildSection() {
520+ if (process.env.BUILD_RESULT !== 'failure') return '';
521+
522+ const logPath = path.join(root, 'ios-build', 'build.log');
523+ let section = '## Build failed\n\n';
524+
525+ if (!fs.existsSync(logPath)) {
526+ return section + 'No build log artifact was found. The build likely failed before xcodebuild started.\n\n';
527+ }
528+
529+ const log = fs.readFileSync(logPath, 'utf8');
530+ const lines = log.split(/\r?\n/);
531+ const errorLines = lines
532+ .filter((line) => /^(.*?):(\d+):(\d+):\s+error:/i.test(line))
533+ .map(cleanLine)
534+ .filter(Boolean);
535+
536+ if (errorLines.length <= 0) {
537+ return section + 'No compiler-style error diagnostics were found in build.log.\n\n';
538+ }
539+
540+ section += 'Compiler error lines:\n\n```text\n' + errorLines.join('\n') + '\n```\n\n';
541+
542+ const repoRoot = process.env.GITHUB_WORKSPACE || process.cwd();
543+ const snippets = [];
544+
545+ for (const line of errorLines) {
546+ const match = line.match(/^(.*?):(\d+):(\d+):\s+error:/);
547+ if (!match) continue;
548+
549+ const filePath = match[1];
550+ const lineNum = parseInt(match[2], 10);
551+ const absPath = filePath.startsWith('/') ? filePath : path.join(repoRoot, filePath);
552+ if (!fs.existsSync(absPath)) continue;
553+
554+ const fileLines = fs.readFileSync(absPath, 'utf8').split(/\r?\n/);
555+ const start = Math.max(0, lineNum - 3);
556+ const end = Math.min(fileLines.length, lineNum + 2);
557+ const snippet = fileLines
558+ .slice(start, end)
559+ .map((sourceLine, index) => {
560+ const currentLine = start + index + 1;
561+ return `${currentLine.toString().padStart(4, ' ')}| ${sourceLine}`;
562+ })
563+ .join('\n');
564+ snippets.push(`File: ${filePath}:${lineNum}\n${snippet}`);
565+ }
566+
567+ if (snippets.length <= 0) return section;
568+
569+ return section + 'Code excerpts:\n\n```text\n' + snippets.join('\n\n') + '\n```\n\n';
570+ }
571+
572+ function extractTestSection() {
573+ if (process.env.TEST_RESULT !== 'failure') return '';
574+
575+ const summaries = [];
576+
563577 for (const summaryPath of walk(root).filter((filePath) => path.basename(filePath) === 'summary.txt')) {
564578 const artifactName = summaryPath.split(path.sep)[1] || 'unknown';
565579 const artifactDirectory = path.dirname(summaryPath);
@@ -591,21 +605,25 @@ jobs:
591605 }
592606 }
593607
594- let body = '❌ iOS CI tests failed.\n\n';
608+ let section = '## Tests failed\n\n';
609+
610+ if (summaries.length <= 0) {
611+ return section + 'No failed scheme summary was found. Check the uploaded test log artifacts.\n\n';
612+ }
595613
596- if (summaries.length <= 0) {
597- body += 'No failed scheme summary was found. Check the uploaded test log artifacts.';
598- } else {
599- body += 'Failed schemes:\n\n';
614+ section += 'Failed schemes:\n\n';
600615 for (const summary of summaries) {
601- body += `- ${summary.scheme} (${summary.artifactName})\n`;
616+ section += `- ${summary.scheme} (${summary.artifactName})\n`;
602617 if (summary.snippet) {
603- body += '\n```text\n' + summary.snippet + '\n```\n\n';
618+ section += '\n```text\n' + summary.snippet + '\n```\n\n';
604619 }
605620 }
606- body += '\nCheck the uploaded test log artifacts for full diagnostics.';
621+
622+ return section + 'Check the uploaded test log artifacts for full diagnostics.\n\n';
607623 }
608624
625+ const body = '❌ iOS CI failed.\n\n' + extractBuildSection() + extractTestSection();
626+
609627 if (!context.payload.pull_request) {
610628 core.info('No PR context; skipping comment.');
611629 return;
0 commit comments