Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions .github/workflows/build-error-analyzer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
name: Build Error Analysis Report

on:
# Trigger on workflow completions for better artifact handling
workflow_run:
workflows: ["Create New Release"]
types:
- completed

# Also trigger directly on pull requests
pull_request:
types: [opened, synchronize, reopened]

jobs:
# Job for analyzing artifacts from a failed workflow
analyze_workflow_artifacts:
name: Analyze Workflow Artifacts
runs-on: macos-15
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_branch }}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Git LFS
run: |
git lfs install
git lfs pull

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Download Workflow Artifacts
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');

// List artifacts from the failed workflow
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});

console.log(`Found ${artifacts.data.artifacts.length} artifacts`);

// Download all artifacts to analyze
for (const artifact of artifacts.data.artifacts) {
console.log(`Downloading: ${artifact.name} (${artifact.id})`);

const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip'
});

// Save with artifact name to keep track of everything
const filename = `${artifact.name.replace(/[
^
a-zA-Z0-9]/g, '_')}.zip`;
fs.writeFileSync(filename, Buffer.from(download.data));
console.log(`Saved to ${filename}`);
}

- name: Process Artifacts and Find Build Logs
id: process_artifacts
run: |
# Make artifact manager script executable
chmod +x scripts/ci/artifact-manager.sh

# Process artifacts to find build logs
./scripts/ci/artifact-manager.sh

# Check if a valid build log was found
if [ -f "build_log.txt" ] && [ -s "build_log.txt" ]; then
echo "build_log_found=true" >> $GITHUB_OUTPUT
echo "✅ Build log found and ready for analysis"
else
echo "build_log_found=false" >> $GITHUB_OUTPUT
echo "⚠️ No valid build logs found in artifacts"
fi

- name: Analyze Build Errors
if: steps.process_artifacts.outputs.build_log_found == 'true'
id: analysis
run: |
# Run the error analysis script
python3 scripts/ci/auto-fix-build-errors.py build_log.txt || echo "Analysis completed with warnings"

# Check if reports were generated
if [ -f "build_error_report.html" ]; then
echo "report_generated=true" >> $GITHUB_OUTPUT
echo "✅ Build error report generated successfully"
else
echo "report_generated=false" >> $GITHUB_OUTPUT
echo "⚠️ No error report was generated"
fi

- name: Upload Error Reports
if: steps.analysis.outputs.report_generated == 'true'
uses: actions/upload-artifact@v4
with:
name: build-error-reports
path: |
build_error_report.html
build_error_report.txt
build_error_report.json
build-logs/
retention-days: 30

- name: Generate Summary
if: steps.analysis.outputs.report_generated == 'true'
run: |
# Use script to generate GitHub step summary
./scripts/ci/generate-report-summary.sh

- name: Determine PR Number
id: pr-finder
if: steps.analysis.outputs.report_generated == 'true'
uses: actions/github-script@v7
with:
script: |
// Get PR number from the workflow run
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }}
});

// Extract PR number from the run data
const prNumber = run.data.pull_requests[0]?.number;
if (prNumber) {
console.log(`Found PR number: ${prNumber}`);
return prNumber;
} else {
console.log("Could not determine PR number from workflow run");
return '';
}
result-encoding: string

- name: Create Comment on PR
if: steps.analysis.outputs.report_generated == 'true' && steps.pr-finder.outputs.result != ''
run: |
# Use script to create PR comment
./scripts/ci/create-pr-comment.sh "${{ steps.pr-finder.outputs.result }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Direct analysis job for pull requests
analyze_pr_build:
name: Analyze PR Build
runs-on: macos-15
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Build Project for Analysis
id: build
continue-on-error: true
run: |
# Create a logs directory
mkdir -p build-logs

# Try to build the project, capturing output to a log file
set +e
xcodebuild -project backdoor.xcodeproj -scheme "backdoor (Release)" -configuration Release CODE_SIGNING_ALLOWED=NO | tee build-logs/xcodebuild.log
BUILD_RESULT=$?
set -e

# Check if build failed (we want to analyze failures)
if [ $BUILD_RESULT -ne 0 ]; then
echo "build_failed=true" >> $GITHUB_OUTPUT
echo "✅ Build failed as expected, logs captured for analysis"
else
echo "build_failed=false" >> $GITHUB_OUTPUT
echo "Build succeeded, no errors to analyze"
fi

# Always link the log file to the expected location
cp build-logs/xcodebuild.log build_log.txt

- name: Analyze Build Errors
if: steps.build.outputs.build_failed == 'true'
id: analysis
run: |
# Run the error analysis script
python3 scripts/ci/auto-fix-build-errors.py build_log.txt || echo "Analysis completed with warnings"

# Check if reports were generated
if [ -f "build_error_report.html" ]; then
echo "report_generated=true" >> $GITHUB_OUTPUT
echo "✅ Build error report generated successfully"
else
echo "report_generated=false" >> $GITHUB_OUTPUT
echo "⚠️ No error report was generated"
fi

- name: Upload Error Reports
if: steps.build.outputs.build_failed == 'true' && steps.analysis.outputs.report_generated == 'true'
uses: actions/upload-artifact@v4
with:
name: pr-build-error-reports
path: |
build_error_report.html
build_error_report.txt
build_error_report.json
build-logs/
retention-days: 30

- name: Generate Summary
if: steps.build.outputs.build_failed == 'true' && steps.analysis.outputs.report_generated == 'true'
run: |
# Use script to generate GitHub step summary
./scripts/ci/generate-report-summary.sh

- name: Create Comment on PR
if: steps.build.outputs.build_failed == 'true' && steps.analysis.outputs.report_generated == 'true'
run: |
# Use script to create PR comment
./scripts/ci/create-pr-comment.sh "${{ github.event.pull_request.number }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
122 changes: 122 additions & 0 deletions scripts/ci/artifact-manager.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/bin/bash
set -eo pipefail

# Focused Artifact Manager Script
# Prioritizes finding and analyzing build logs from artifacts

echo "📦 Artifact Manager: Finding build logs in artifacts"

# Create directories for organized processing
mkdir -p artifact-extracts
mkdir -p build-logs

# Find all artifacts in the current directory and artifact-contents
find_artifacts() {
echo "🔍 Searching for artifact files..."

# Common locations where artifacts might be found in GitHub Actions
ARTIFACT_DIRS=(
"."
"artifact-contents"
"artifacts"
"downloads"
"/home/runner/work/_temp"
)

# Look for zip files in these directories
for DIR in "${ARTIFACT_DIRS[@]}"; do
if [ -d "$DIR" ]; then
echo "Searching in $DIR"
find "$DIR" -name "*.zip" -type f | while read -r ZIP_FILE; do
echo "Found artifact: $ZIP_FILE"
extract_artifact "$ZIP_FILE"
done
fi
done
}

# Extract an artifact file
extract_artifact() {
ZIP_FILE="$1"
EXTRACT_DIR="artifact-extracts/$(basename "$ZIP_FILE" .zip)"

echo "Extracting $ZIP_FILE to $EXTRACT_DIR"
mkdir -p "$EXTRACT_DIR"
unzip -q -o "$ZIP_FILE" -d "$EXTRACT_DIR" || echo "Warning: Extraction issues with $ZIP_FILE"

# Look for build logs in the extracted content
find_logs_in_extract "$EXTRACT_DIR"
}

# Find log files in an extracted artifact
find_logs_in_extract() {
EXTRACT_DIR="$1"
echo "Searching for logs in $EXTRACT_DIR"

# Common build log patterns
LOG_PATTERNS=(
"*build*.log"
"*build*.txt"
"*xcodebuild*.log"
"*output*.log"
"*compile*.log"
"*.build.log"
"*.txt"
)

# Find all potential log files
for PATTERN in "${LOG_PATTERNS[@]}"; do
find "$EXTRACT_DIR" -type f -iname "$PATTERN" 2>/dev/null | while read -r LOG_FILE; do
echo "Checking potential log: $LOG_FILE"

# Check if this looks like a build log (contains error/warning messages)
if grep -q -E "error:|warning:|fatal error:|linker command failed|swift|xcodebuild" "$LOG_FILE"; then
echo "✅ Found build log: $LOG_FILE"
cp "$LOG_FILE" "build-logs/$(basename "$LOG_FILE")"
fi
done
done
}

# Combine all found logs into a single file for analysis
combine_logs() {
echo "Combining logs for analysis..."

if [ -z "$(ls -A build-logs 2>/dev/null)" ]; then
echo "::warning::No build logs found in artifacts"
echo "No build log found. This is a placeholder." > build_log.txt
return 1
fi

# Combine all logs with headers separating them
> combined_build_log.txt
for LOG_FILE in build-logs/*; do
echo "=== $(basename "$LOG_FILE") ===" >> combined_build_log.txt
echo "" >> combined_build_log.txt
cat "$LOG_FILE" >> combined_build_log.txt
echo "" >> combined_build_log.txt
echo "=== END OF $(basename "$LOG_FILE") ===" >> combined_build_log.txt
echo "" >> combined_build_log.txt
done

# Create the main build log file for backward compatibility
cp combined_build_log.txt build_log.txt
echo "Created combined log file: build_log.txt"

# Create a manifest of log files
find build-logs -type f | sort > build-logs/log_manifest.txt
echo "Created log manifest: build-logs/log_manifest.txt"

return 0
}

# Main execution flow
find_artifacts
combine_logs

# Summary
echo "✅ Artifact processing complete"
if [ -f "build_log.txt" ]; then
echo "Log files ready for analysis: build_log.txt"
echo "Individual logs available in: build-logs/"
fi
Loading