name: "KiBot PCB Generation" # Controls when the action will run on: push: paths: - '**/*.kicad_sch' - '**/*.kicad_pcb' - '**/*.kicad_pro' - 'kibot.yaml' - '.gitea/workflows/*.yml' pull_request: paths: - '**/*.kicad_sch' - '**/*.kicad_pcb' - '**/*.kicad_pro' - 'kibot.yaml' - '.gitea/workflows/*.yml' repository_dispatch: types: [run_qs] workflow_dispatch: inputs: board_layers: description: 'Number of PCB layers' required: false default: '4' type: choice options: - '2' - '4' - '6' diff_old: description: 'Old revision for diff (e.g., HEAD~1, commit hash, tag)' required: false default: 'HEAD~1' type: string diff_new: description: 'New revision for diff (e.g., HEAD, commit hash, tag)' required: false default: 'HEAD' type: string enable_interactive_diff: description: 'Generate interactive HTML diff' required: false default: false type: boolean # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: generate: runs-on: kicad-kibot-runner steps: - uses: actions/checkout@v3 with: # Fetch full history for diff generation fetch-depth: '0' - name: Find KiCad project id: find_project run: | # Find all .kicad_pcb files in the repository PCB_FILES=$(find . -name "*.kicad_pcb" -type f | grep -v -E '/(backups?|backup|old|archive|deprecated)/' | head -5) if [ -z "$PCB_FILES" ]; then echo "Error: No KiCad PCB files found" exit 1 fi # Use the first PCB file found PCB_FILE=$(echo "$PCB_FILES" | head -1) PROJECT_DIR=$(dirname "$PCB_FILE") PROJECT_NAME=$(basename "$PCB_FILE" .kicad_pcb) echo "Found KiCad project: $PROJECT_NAME" echo "Project directory: $PROJECT_DIR" echo "PCB file: $PCB_FILE" # Find schematic file SCH_FILE=$(find "$PROJECT_DIR" -name "*.kicad_sch" -o -name "*.sch" | head -1) echo "pcb_file=$PCB_FILE" >> $GITHUB_OUTPUT echo "project_dir=$PROJECT_DIR" >> $GITHUB_OUTPUT echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT echo "sch_file=$SCH_FILE" >> $GITHUB_OUTPUT # List all found projects for debugging echo "All PCB files found:" echo "$PCB_FILES" - name: Set layer count id: layers run: | # Use workflow input or default to 4 layers LAYERS="${{ github.event.inputs.board_layers }}" if [ -z "$LAYERS" ]; then LAYERS="4" fi echo "layers=$LAYERS" >> $GITHUB_OUTPUT echo "Using $LAYERS layer configuration" - name: Check for KiBot config id: config_check run: | # Check if kibot.yaml exists in repository root if [ -f "kibot.yaml" ]; then echo "found=true" >> $GITHUB_OUTPUT echo "Using kibot.yaml configuration from repository root" else echo "found=false" >> $GITHUB_OUTPUT echo "No kibot.yaml found in repository root - will use --quick-start mode" fi - name: Generate schematic diff id: generate_diff if: github.event_name == 'push' || github.event_name == 'pull_request' run: | PROJECT_DIR="${{ steps.find_project.outputs.project_dir }}" PROJECT_NAME="${{ steps.find_project.outputs.project_name }}" # Create DIFF output directory mkdir -p "${GITHUB_WORKSPACE}/Generated/DIFF" cd "$PROJECT_DIR" # Get the previous commit hash if [ "${{ github.event_name }}" = "pull_request" ]; then BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" else # For push events, compare with previous commit HEAD_SHA="${{ github.sha }}" BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "") fi if [ -z "$BASE_SHA" ]; then echo "No previous commit found for comparison" echo "has_diff=false" >> $GITHUB_OUTPUT exit 0 fi echo "Comparing commits: $BASE_SHA...$HEAD_SHA" # Find all schematic files SCH_FILES=$(find . -name "*.kicad_sch" -o -name "*.sch" | head -10) if [ -z "$SCH_FILES" ]; then echo "No schematic files found" echo "has_diff=false" >> $GITHUB_OUTPUT exit 0 fi # Generate diff for each schematic DIFF_GENERATED=false for SCH_FILE in $SCH_FILES; do SCH_NAME=$(basename "$SCH_FILE" | sed 's/\.[^.]*$//') # Check if file changed between commits if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q "$SCH_FILE"; then echo "Generating diff for $SCH_FILE" # Create temp directory for processing TEMP_DIR=$(mktemp -d) # Export old version git show "$BASE_SHA:$SCH_FILE" > "$TEMP_DIR/${SCH_NAME}_old.kicad_sch" 2>/dev/null || continue # Copy current version cp "$SCH_FILE" "$TEMP_DIR/${SCH_NAME}_new.kicad_sch" # Try to use kidiff if available (better visual diff tool for KiCad) if command -v kidiff &> /dev/null; then kidiff -o "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.pdf" \ "$TEMP_DIR/${SCH_NAME}_old.kicad_sch" \ "$TEMP_DIR/${SCH_NAME}_new.kicad_sch" || true fi # Also generate text diff git diff "$BASE_SHA" "$HEAD_SHA" -- "$SCH_FILE" > "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_changes.txt" # Generate a simple HTML diff for easy viewing cat > "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.html" << EOF
EOF
# Add the diff content with HTML escaping
git diff "$BASE_SHA" "$HEAD_SHA" -- "$SCH_FILE" | \
sed 's/&/\&/g; s/\</g; s/>/\>/g' | \
sed 's/^+[^+].*/&<\/span>/g' | \
sed 's/^-[^-].*/&<\/span>/g' >> "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.html"
echo "" >> "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.html"
rm -rf "$TEMP_DIR"
DIFF_GENERATED=true
fi
done
# Generate PCB diff if PCB file changed
PCB_FILE="${{ steps.find_project.outputs.pcb_file }}"
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q "$PCB_FILE"; then
echo "Generating PCB diff"
PCB_NAME=$(basename "$PCB_FILE" | sed 's/\.[^.]*$//')
# Generate text diff for PCB
git diff "$BASE_SHA" "$HEAD_SHA" -- "$PCB_FILE" > "${GITHUB_WORKSPACE}/Generated/DIFF/${PCB_NAME}_pcb_changes.txt"
# Summary of changes
git diff --stat "$BASE_SHA" "$HEAD_SHA" -- "$PCB_FILE" > "${GITHUB_WORKSPACE}/Generated/DIFF/${PCB_NAME}_pcb_summary.txt"
DIFF_GENERATED=true
fi
# Create a summary file
cat > "${GITHUB_WORKSPACE}/Generated/DIFF/README.md" << EOF
# KiCad Project Diff Report
**Base Commit:** ${BASE_SHA:0:8}
**Head Commit:** ${HEAD_SHA:0:8}
**Date:** $(date)
## Changed Files:
$(git diff --name-status "$BASE_SHA" "$HEAD_SHA" -- "*.kicad_sch" "*.sch" "*.kicad_pcb" "*.pcb")
## Summary:
$(git diff --stat "$BASE_SHA" "$HEAD_SHA" -- "*.kicad_sch" "*.sch" "*.kicad_pcb" "*.pcb")
## Commit Messages:
$(git log --oneline "$BASE_SHA".."$HEAD_SHA")
EOF
if [ "$DIFF_GENERATED" = true ]; then
echo "has_diff=true" >> $GITHUB_OUTPUT
else
echo "No schematic or PCB changes detected"
echo "has_diff=false" >> $GITHUB_OUTPUT
fi
- name: Run ERC
if: steps.config_check.outputs.found == 'true'
run: |
cd "${{ steps.find_project.outputs.project_dir }}"
# Clean up any existing output directories
rm -rf Fabrication Generated Output
if [ -n "${{ steps.find_project.outputs.sch_file }}" ]; then
echo "Running ERC on schematic files..."
# Use absolute path to config file in repo root
# Skip ERC step in preflight to avoid stopping on errors
kibot -c "${GITHUB_WORKSPACE}/kibot.yaml" -s run_erc,run_drc -v || true
else
echo "No schematic files found, skipping ERC"
fi
continue-on-error: true
- name: Run DRC
if: steps.config_check.outputs.found == 'true'
run: |
cd "${{ steps.find_project.outputs.project_dir }}"
echo "Running DRC on PCB..."
# Use absolute path to config file in repo root
# Skip both ERC and DRC in preflight to avoid stopping on errors
kibot -c "${GITHUB_WORKSPACE}/kibot.yaml" -s run_erc,run_drc -v || true
continue-on-error: true
- name: Generate outputs with custom config
if: steps.config_check.outputs.found == 'true'
run: |
cd "${{ steps.find_project.outputs.project_dir }}"
echo "Generating outputs for ${{ steps.layers.outputs.layers }} layer board"
echo "Project: ${{ steps.find_project.outputs.project_name }}"
echo "Using config: ${GITHUB_WORKSPACE}/kibot.yaml"
# First, let's check what's in the current directory
echo "Current directory contents:"
ls -la
# Check if Fabrication exists and what it is
if [ -e "Fabrication" ]; then
echo "Fabrication exists as:"
file Fabrication
echo "Removing it..."
rm -rf Fabrication
fi
# Create a temporary modified config without the global output setting
cp "${GITHUB_WORKSPACE}/kibot.yaml" /tmp/kibot_temp.yaml
# Remove or comment out the global output line if it exists
sed -i 's/^ output:.*$/ # output: commented out for workflow/' /tmp/kibot_temp.yaml
# Update diff revisions if specified
if [ -n "${{ github.event.inputs.diff_old }}" ]; then
sed -i "s/old: 'HEAD~1'/old: '${{ github.event.inputs.diff_old }}'/" /tmp/kibot_temp.yaml
fi
if [ -n "${{ github.event.inputs.diff_new }}" ]; then
sed -i "s/new: 'HEAD'/new: '${{ github.event.inputs.diff_new }}'/" /tmp/kibot_temp.yaml
fi
# Use a timestamp-based directory to ensure uniqueness
OUTPUT_DIR="kibot_output_$(date +%s)"
# Determine which outputs to run
EXTRA_OUTPUTS=""
if [ "${{ github.event.inputs.enable_interactive_diff }}" = "true" ]; then
EXTRA_OUTPUTS="interactive_diff svg_diff"
fi
# Run KiBot with the modified configuration
# Skip ERC/DRC preflight checks to avoid stopping on errors
echo "Running KiBot with output directory: $OUTPUT_DIR"
# Run all outputs (KiBot will run all by default when no specific outputs are listed)
# Only add extra outputs if interactive diff is enabled
if [ "${{ github.event.inputs.enable_interactive_diff }}" = "true" ]; then
# Run with interactive diff outputs explicitly enabled
kibot -c /tmp/kibot_temp.yaml -s run_erc,run_drc -d "$OUTPUT_DIR" -v interactive_diff svg_diff
else
# Run all default outputs (everything with run_by_default: true)
kibot -c /tmp/kibot_temp.yaml -s run_erc,run_drc -d "$OUTPUT_DIR" -v
fi
# Move outputs to root Generated folder
mkdir -p "${GITHUB_WORKSPACE}/Generated"
if [ -d "$OUTPUT_DIR" ]; then
echo "Copying outputs from $OUTPUT_DIR to Generated/"
cp -r "$OUTPUT_DIR"/* "${GITHUB_WORKSPACE}/Generated/"
rm -rf "$OUTPUT_DIR"
elif [ -d "Fabrication" ]; then
echo "KiBot used default Fabrication directory, copying from there"
cp -r Fabrication/* "${GITHUB_WORKSPACE}/Generated/"
rm -rf Fabrication
else
echo "Warning: No output directory was created"
fi
- name: Check downloaded datasheets
if: steps.config_check.outputs.found == 'true'
continue-on-error: true
run: |
echo "Checking for downloaded datasheets..."
# KiBot should download datasheets to the configured directory
# Check if datasheets were downloaded
if [ -d "${GITHUB_WORKSPACE}/Generated/Datasheets" ]; then
echo "Datasheets folder found in Generated/"
# Create README
cat > "${GITHUB_WORKSPACE}/Generated/Datasheets/README.md" << EOF
# Component Datasheets
This folder contains downloaded datasheets for all components in the project.
Organized by component type:
- **ICs/** - Integrated circuits (U references)
- **Capacitors/** - Capacitors (C references)
- **Resistors/** - Resistors (R references)
- **Transistors/** - Transistors (Q references)
- **Diodes/** - Diodes (D references)
- **Connectors/** - Connectors (J, P references)
- **Inductors/** - Inductors (L references)
- And more...
Files are named: REFERENCE_VALUE.pdf (e.g., U1_LM7805.pdf)
Generated on: $(date)
Project: ${{ steps.find_project.outputs.project_name }}
EOF
echo "Datasheet folder structure:"
find "${GITHUB_WORKSPACE}/Generated/Datasheets" -type f -name "*.pdf" | head -20
# Count PDFs
PDF_COUNT=$(find "${GITHUB_WORKSPACE}/Generated/Datasheets" -type f -name "*.pdf" | wc -l)
echo "Total PDFs downloaded: $PDF_COUNT"
else
echo "No Datasheets folder found - KiBot may not have downloaded any datasheets"
echo "Check that components have valid Datasheet URLs in the schematic"
fi
- name: Quick Start fallback
if: steps.config_check.outputs.found == 'false'
run: |
cd "${{ steps.find_project.outputs.project_dir }}"
echo "Running KiBot in quick-start mode"
kibot --quick-start
# Move outputs to root Generated folder
mkdir -p "${GITHUB_WORKSPACE}/Generated"
if [ -d "Generated" ]; then
cp -r Generated/* "${GITHUB_WORKSPACE}/Generated/"
fi
- name: Add board information to outputs
run: |
echo "Board configuration: ${{ steps.layers.outputs.layers }} layers" > Generated/board_info.txt
echo "Project: ${{ steps.find_project.outputs.project_name }}" >> Generated/board_info.txt
echo "Project path: ${{ steps.find_project.outputs.project_dir }}" >> Generated/board_info.txt
echo "Generated on: $(date)" >> Generated/board_info.txt
echo "Commit: ${{ github.sha }}" >> Generated/board_info.txt
- name: Fix file permissions
run: |
chmod -R a+rw Generated || true
- name: Retrieve results - All outputs
uses: actions/upload-artifact@v3
with:
name: Automatic_outputs
path: Generated
- name: Retrieve results - Gerbers
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: Gerbers-${{ steps.layers.outputs.layers }}layer
path: Generated/Gerbers
- name: Retrieve results - Assembly
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: Assembly-${{ steps.layers.outputs.layers }}layer
path: Generated/Assembly
- name: Retrieve results - BOM
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: BOM-${{ steps.layers.outputs.layers }}layer
path: Generated/BoM
- name: Retrieve results - Datasheets
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: Datasheets
path: Generated/Datasheets
- name: Retrieve results - 3D Model
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: 3D-Model
path: Generated/3D
- name: Retrieve results - Fabrication Package
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: Fabrication-Package-${{ steps.layers.outputs.layers }}layer
path: Generated/*.zip
- name: Retrieve results - JLCPCB Package
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: JLCPCB-${{ steps.layers.outputs.layers }}layer
path: Generated/*_JLCPCB_compress.zip
- name: Retrieve results - Interactive Viewers
if: steps.config_check.outputs.found == 'true'
uses: actions/upload-artifact@v3
with:
name: Interactive-Viewers
path: Generated/Interactive
- name: Retrieve results - Diff Report
if: steps.generate_diff.outputs.has_diff == 'true'
uses: actions/upload-artifact@v3
with:
name: Schematic-PCB-Diff
path: Generated/DIFF
# Deploy to documentation branch
deploy:
runs-on: kicad-kibot-runner
needs: generate
steps:
- uses: actions/checkout@v4
- name: Retrieve results
uses: actions/download-artifact@v3
with:
name: Automatic_outputs
path: Generated
- name: Disable Jekyll
# Jekyll will filter the KiRi folders
run: |
touch Generated/.nojekyll
- name: Push to docu branch
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
# Set up git identity
git config --global user.email "${{ gitea.actor }}@noreply.localhost"
git config --global user.name "${{ gitea.actor }}"
# Get the repository URL and add authentication
REPO_URL=$(git config --get remote.origin.url)
AUTHED_URL=${REPO_URL/https:\/\//https:\/\/${GITEA_TOKEN}@}
# Clone the docu branch into a separate folder
git clone --depth 1 --branch docu "$AUTHED_URL" docu-branch || \
git clone --depth 1 "$AUTHED_URL" docu-branch
cd docu-branch
git checkout -B docu
# Clean old contents and copy new files
rm -rf ./*
cp -r ../Generated/* ./
# Commit and push if there are changes
git add .
if ! git diff --cached --quiet; then
git commit -m "chore: update deployed documentation"
git push --force "$AUTHED_URL" docu
else
echo "No changes to deploy"
fi