Files
kicad-ci/kibot-ci.yml
2026-01-17 15:16:14 +10:00

447 lines
13 KiB
YAML

# KiCad CI Pipeline with hfsntree Integration
# Include this file in your project's .gitlab-ci.yml and set PROJECT_NAME variable
#
# Example project .gitlab-ci.yml:
# include:
# - local: '.gitlab/kibot-ci.yml'
# variables:
# PROJECT_NAME: "my_project_name"
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
variables:
GIT_STRATEGY: clone
GIT_SUBMODULE_STRATEGY: recursive
GIT_SUBMODULE_FORCE_HTTPS: "true"
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/kicad"
stages:
- preflight
- fabrication
- inventree
- release
image:
name: ghcr.io/inti-cmnb/kicad9_auto:1.8.4
# =============================================================================
# Rule Templates
# =============================================================================
.main_rules:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
- if: $CI_COMMIT_BRANCH == "main"
- if: $GITLAB_CI == 'false'
.dev_rules:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "dev"'
- if: $CI_COMMIT_BRANCH == "dev"
- if: $GITLAB_CI == 'false'
# =============================================================================
# Stage: Preflight
# =============================================================================
extract_version:
stage: preflight
script:
- |
# Extract version from MR title (pattern: V followed by numbers and dots)
if [[ -n "$CI_MERGE_REQUEST_TITLE" ]]; then
VERSION=$(echo "$CI_MERGE_REQUEST_TITLE" | grep -oE 'V[0-9]+(\.[0-9]+)*' | head -1)
fi
# Fallback to git tag or commit SHA
if [[ -z "$VERSION" ]]; then
VERSION=$(git describe --tags 2>/dev/null || echo "$CI_COMMIT_SHORT_SHA")
fi
echo "VERSION=$VERSION" >> build.env
echo "Extracted version: $VERSION"
artifacts:
reports:
dotenv: build.env
run_erc:
stage: preflight
needs:
- job: extract_version
artifacts: true
script:
- |
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" ]; then
echo "Running ERC..."
cat > /tmp/erc.kibot.yaml << 'EOF'
kibot:
version: 1
preflight:
erc:
enabled: true
warnings_as_errors: false
EOF
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-c /tmp/erc.kibot.yaml \
-d Validation/
fi
artifacts:
when: always
paths:
- Validation/
expire_in: 1 week
run_drc:
stage: preflight
needs:
- job: extract_version
artifacts: true
script:
- |
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then
echo "Running DRC..."
# Extract part number from project name for text variables
PART_NUM=$(echo "${PROJECT_NAME}" | cut -d"-" -f1)
cat > /tmp/drc.kibot.yaml << EOF
kibot:
version: 1
preflight:
set_text_variables:
- name: 'name'
text: '${PART_NUM}'
- name: 'rev'
text: '${VERSION}'
- name: 'rev_pcb'
text: '${VERSION}'
drc:
enabled: true
warnings_as_errors: false
EOF
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-b ${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb \
-c /tmp/drc.kibot.yaml \
-d Validation/
fi
artifacts:
when: always
paths:
- Validation/
expire_in: 1 week
# =============================================================================
# Stage: Fabrication
# =============================================================================
.fabrication_base:
stage: fabrication
needs:
- job: extract_version
artifacts: true
- job: run_erc
artifacts: false
- job: run_drc
artifacts: false
before_script:
- |
# Ensure submodules are initialized (needed for gitlab-ci-local)
git submodule update --init --recursive 2>/dev/null || true
# Export version for KiBot text variable injection
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "KiBot version variables set to: $VERSION"
# Setup user config
USER_FILE="$CI_PROJECT_DIR/.gitlab/configs/blank.kibot.yaml"
if [ -f "${PROJECT_NAME}/kibot.yaml" ]; then
USER_FILE="${PROJECT_NAME}/kibot.yaml"
fi
cp $USER_FILE $CI_PROJECT_DIR/.gitlab/configs/user.kibot.yaml
artifacts:
when: always
paths:
- Fabrication/
expire_in: 1 week
generate_schematic:
extends: .fabrication_base
script:
- |
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "Using version: $VERSION"
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" ]; then
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-c $CI_PROJECT_DIR/.gitlab/configs/sch.kibot.yaml \
-d $CI_PROJECT_DIR/Fabrication/${PROJECT_NAME}
mv Fabrication/${PROJECT_NAME}/*.pdf Fabrication/ 2>/dev/null || true
fi
generate_bom:
extends: .fabrication_base
script:
- |
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "Using version: $VERSION"
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" ]; then
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-c $CI_PROJECT_DIR/.gitlab/configs/sch.kibot.yaml \
-d $CI_PROJECT_DIR/Fabrication/${PROJECT_NAME}
mv Fabrication/${PROJECT_NAME}/*.csv Fabrication/ 2>/dev/null || true
mv Fabrication/${PROJECT_NAME}/*.xlsx Fabrication/ 2>/dev/null || true
fi
generate_3d:
extends: .fabrication_base
script:
- |
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "Using version: $VERSION"
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-c $CI_PROJECT_DIR/.gitlab/configs/mech.kibot.yaml \
-d $CI_PROJECT_DIR/Fabrication/${PROJECT_NAME}
mv Fabrication/${PROJECT_NAME}/*.step Fabrication/ 2>/dev/null || true
fi
generate_gerbers:
extends: .fabrication_base
rules:
- !reference [.main_rules, rules]
script:
- |
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "Using version: $VERSION"
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-c $CI_PROJECT_DIR/.gitlab/configs/pcb_main.kibot.yaml \
-d $CI_PROJECT_DIR/Fabrication/${PROJECT_NAME}
mv Fabrication/${PROJECT_NAME}/*.zip Fabrication/ 2>/dev/null || true
fi
generate_position:
extends: .fabrication_base
rules:
- !reference [.main_rules, rules]
script:
- |
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "Using version: $VERSION"
if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then
kibot -e ${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch \
-c $CI_PROJECT_DIR/.gitlab/configs/pos.kibot.yaml \
-d $CI_PROJECT_DIR/Fabrication/${PROJECT_NAME}
mv Fabrication/${PROJECT_NAME}/*cpl*.csv Fabrication/ 2>/dev/null || true
fi
generate_panel:
extends: .fabrication_base
rules:
- !reference [.main_rules, rules]
script:
- |
export KIBOT_VAR_rev="${VERSION}"
export KIBOT_VAR_rev_pcb="${VERSION}"
echo "Using version: $VERSION"
# Find panel configuration
PANEL_JSON=$(find ${PROJECT_NAME} -name "*_panel.json" 2>/dev/null | head -1)
if [ -n "$PANEL_JSON" ] && [ -f "$PANEL_JSON" ]; then
echo "Found panel config: $PANEL_JSON"
# Create panel directory
PANEL_NAME=$(basename "${PANEL_JSON%.json}")
mkdir -p panels/${PANEL_NAME}
PCB_FILE="${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb"
# Run pre-panel script if exists
if [ -f ".gitlab/.scripts/pre_panel.py" ]; then
python3 .gitlab/.scripts/pre_panel.py $PCB_FILE $PANEL_JSON
fi
# Generate panel
kikit panelize -p $PANEL_JSON $PCB_FILE panels/${PANEL_NAME}/${PANEL_NAME}.kicad_pcb
# Copy required files for panel
cp .gitlab/templates/micromelon_default/micromelon_default.kicad_sch panels/${PANEL_NAME}/${PANEL_NAME}.kicad_sch 2>/dev/null || true
cp ${PROJECT_NAME}/fp-lib-table panels/${PANEL_NAME}/ 2>/dev/null || true
# Copy libs folder for 3D models
cp -r ${PROJECT_NAME}/libs panels/${PANEL_NAME}/ 2>/dev/null || true
# Run post-panel script if exists
if [ -f ".gitlab/.scripts/post_panel.py" ]; then
python3 .gitlab/.scripts/post_panel.py panels/${PANEL_NAME}/${PANEL_NAME}.kicad_pro $PCB_FILE
fi
# Generate panel gerbers
# Set name variable explicitly for panel (extract part number from project name)
export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1)
cd panels
kibot -e ${PANEL_NAME}/${PANEL_NAME}.kicad_sch \
-c $CI_PROJECT_DIR/.gitlab/configs/panel.kibot.yaml \
-d $CI_PROJECT_DIR/Fabrication/panels
cd $CI_PROJECT_DIR
mv Fabrication/panels/*.zip Fabrication/ 2>/dev/null || true
else
echo "No panel configuration found, skipping panel generation"
fi
# =============================================================================
# Stage: InvenTree
# =============================================================================
.inventree_base:
stage: inventree
image: python:3.11-slim
before_script:
- |
cd hfsntree
pip install -e . --quiet
cd $CI_PROJECT_DIR
inventree_dev:
extends: .inventree_base
rules:
- !reference [.dev_rules, rules]
needs:
- job: extract_version
artifacts: true
- job: generate_schematic
artifacts: true
- job: generate_bom
artifacts: true
- job: generate_3d
artifacts: true
script:
- |
echo "Running InvenTree upload in dry-run mode for dev branch"
cd hfsntree
python main.py batch $CI_PROJECT_DIR/Fabrication --dry-run -y
inventree_main:
extends: .inventree_base
rules:
- !reference [.main_rules, rules]
needs:
- job: extract_version
artifacts: true
- job: generate_schematic
artifacts: true
- job: generate_bom
artifacts: true
- job: generate_3d
artifacts: true
- job: generate_gerbers
artifacts: true
- job: generate_position
artifacts: true
- job: generate_panel
artifacts: true
optional: true
artifacts:
when: always
paths:
- Fabrication/
expire_in: 1 week
script:
- |
echo "Running InvenTree upload for main branch"
cd hfsntree
python main.py batch $CI_PROJECT_DIR/Fabrication -y
# Generate Samsung P&P files (requires parts to exist in InvenTree)
echo "Generating Samsung pick-and-place files..."
python main.py samsung $CI_PROJECT_DIR/Fabrication
cd $CI_PROJECT_DIR
# =============================================================================
# Stage: Release
# =============================================================================
upload_packages:
stage: release
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == "main"
needs:
- job: extract_version
artifacts: true
- job: inventree_main
artifacts: true
script:
- apt-get update && apt-get -y install zip
- zip -r Fabrication/All.zip Fabrication/
- |
for zipfile in $(find Fabrication/ -maxdepth 1 -name '*.zip'); do
basename=$(basename "$zipfile" .zip)
url="${PACKAGE_REGISTRY_URL}/${VERSION}/${basename}-${VERSION}.zip"
echo "Uploading: $zipfile to $url"
python3 << EOF
import urllib.request
with open('$zipfile', 'rb') as f:
data = f.read()
req = urllib.request.Request('$url', data=data, method='PUT')
req.add_header('JOB-TOKEN', '${CI_JOB_TOKEN}')
urllib.request.urlopen(req)
print('Upload complete')
EOF
done
artifacts:
when: always
paths:
- Fabrication/
expire_in: 1 week
create_release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == "main"
needs:
- job: extract_version
artifacts: true
- job: upload_packages
artifacts: true
script:
- |
apk add jq curl
echo "Creating release for version: $VERSION"
# Get package ID
packid=$(curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages" | jq '.[-1].id')
# Build release command with asset links
echo "#!/bin/sh" > release.sh
echo "release-cli create --name \"Release $VERSION\" --tag-name \"$VERSION\" \\" >> release.sh
# Get package files
files_json=$(curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/$packid/package_files")
cnt=$(echo "$files_json" | jq 'length')
for i in $(seq 0 $((cnt - 1))); do
id=$(echo "$files_json" | jq -r ".[$i].id")
name=$(echo "$files_json" | jq -r ".[$i].file_name")
echo " --assets-link \"{\\\"name\\\":\\\"$name\\\",\\\"url\\\":\\\"${CI_PROJECT_URL}/-/package_files/$id/download\\\"}\" \\" >> release.sh
done
chmod +x release.sh
./release.sh