Skip to content

Commit e5b89cf

Browse files
SukuWcclaude
andcommitted
Refactor CI: SHA-based Dockerfile detection and composite action
Replace dorny/paths-filter Dockerfile change detection with Git SHA comparison using the org.opencontainers.image.revision OCI label stamped on each built image. Each build workflow now self-determines whether a rebuild is needed, removing the check-dockerfile job and dockerfile_changed input from all orchestrator and reusable workflows. Extract shared image prep logic (GHCR login + pull/compare/build/push) into a composite action at .github/actions/prepare-builder-image. Standardize all workflows on inline docker run --rm, dropping the background container pattern from mechanical and electrical. All three build workflows now follow the same shape: prepare image, docker run, fix ownership, upload artifacts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5f29555 commit e5b89cf

8 files changed

Lines changed: 86 additions & 162 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Prepare builder image
2+
description: >
3+
Pull or rebuild a GHCR builder image using Git SHA-based Dockerfile
4+
change detection. Rebuilds only when the current Dockerfile differs from
5+
the one that produced the cached image. Pushes to GHCR only on main/master.
6+
7+
inputs:
8+
image-name:
9+
description: Short image name, e.g. knot-firmware-builder
10+
required: true
11+
dockerfile:
12+
description: Path to the Dockerfile, e.g. docker/firmware-builder/Dockerfile
13+
required: true
14+
15+
runs:
16+
using: composite
17+
steps:
18+
- name: Log in to GitHub Container Registry
19+
uses: docker/login-action@v3
20+
with:
21+
registry: ghcr.io
22+
username: ${{ github.actor }}
23+
password: ${{ github.token }}
24+
25+
- name: Pull or rebuild image
26+
shell: bash
27+
run: |
28+
IMAGE=ghcr.io/${{ github.repository_owner }}/${{ inputs.image-name }}:latest
29+
DOCKERFILE=${{ inputs.dockerfile }}
30+
31+
NEEDS_REBUILD=true
32+
if docker pull $IMAGE 2>/dev/null; then
33+
IMAGE_SHA=$(docker inspect $IMAGE \
34+
--format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' 2>/dev/null || echo "")
35+
if [ -n "$IMAGE_SHA" ]; then
36+
OLD_DOCKERFILE=$(git show ${IMAGE_SHA}:${DOCKERFILE} 2>/dev/null || echo "")
37+
if [ "$(cat $DOCKERFILE)" = "$OLD_DOCKERFILE" ]; then
38+
echo "Dockerfile unchanged, reusing image."
39+
NEEDS_REBUILD=false
40+
fi
41+
fi
42+
fi
43+
44+
if [ "$NEEDS_REBUILD" = "true" ]; then
45+
docker build \
46+
--label org.opencontainers.image.revision=${{ github.sha }} \
47+
-t $IMAGE \
48+
-f $DOCKERFILE .
49+
if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master" ]]; then
50+
docker push $IMAGE
51+
fi
52+
fi

.github/workflows/_electrical_build.yml

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
on:
22
workflow_call:
3-
inputs:
4-
dockerfile_changed:
5-
description: "Whether the electrical Dockerfile was changed"
6-
required: false
7-
type: boolean
8-
default: false
93

104
jobs:
115
build:
@@ -21,37 +15,22 @@ jobs:
2115

2216
steps:
2317
- uses: actions/checkout@v2
24-
25-
- name: Log in to GitHub Container Registry
26-
uses: docker/login-action@v3
2718
with:
28-
registry: ghcr.io
29-
username: ${{ github.actor }}
30-
password: ${{ secrets.GITHUB_TOKEN }}
19+
fetch-depth: 0
3120

3221
- name: Prepare image
22+
uses: ./.github/actions/prepare-builder-image
23+
with:
24+
image-name: knot-electrical-builder
25+
dockerfile: docker/electrical-builder/Dockerfile
26+
27+
- name: Run build
3328
run: |
34-
if [ "${{ inputs.dockerfile_changed }}" == "true" ]; then
35-
docker build -t builder:local -f docker/electrical-builder/Dockerfile .
36-
if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master" ]]; then
37-
docker tag builder:local ghcr.io/${{ github.repository_owner }}/knot-electrical-builder:latest
38-
docker push ghcr.io/${{ github.repository_owner }}/knot-electrical-builder:latest
39-
fi
40-
else
41-
docker pull ghcr.io/${{ github.repository_owner }}/knot-electrical-builder:latest
42-
docker tag ghcr.io/${{ github.repository_owner }}/knot-electrical-builder:latest builder:local
43-
fi
44-
docker run -d --name builder \
29+
docker run --rm \
4530
-v ${{ github.workspace }}:/work \
4631
-w /work \
47-
builder:local tail -f /dev/null
48-
49-
- name: Run PCB stuff
50-
run: docker exec builder bash -c "make -C /work/Electrical/Design/${{ matrix.board }}"
51-
52-
- name: Stop container
53-
if: always()
54-
run: docker rm -f builder
32+
ghcr.io/${{ github.repository_owner }}/knot-electrical-builder:latest \
33+
bash -c "make -C /work/Electrical/Design/${{ matrix.board }}"
5534
5635
- name: Fix file ownership
5736
if: always()

.github/workflows/_firmware_build.yml

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
on:
22
workflow_call:
3-
inputs:
4-
dockerfile_changed:
5-
description: "Whether the firmware Dockerfile was changed"
6-
required: false
7-
type: boolean
8-
default: false
93

104
jobs:
115
build:
@@ -17,24 +11,13 @@ jobs:
1711
uses: actions/checkout@v2
1812
with:
1913
submodules: 'recursive'
20-
21-
- name: Log in to GitHub Container Registry
22-
uses: docker/login-action@v3
23-
with:
24-
registry: ghcr.io
25-
username: ${{ github.actor }}
26-
password: ${{ secrets.GITHUB_TOKEN }}
14+
fetch-depth: 0
2715

2816
- name: Prepare image
29-
run: |
30-
if [ "${{ inputs.dockerfile_changed }}" == "true" ]; then
31-
docker build -t ghcr.io/${{ github.repository_owner }}/knot-firmware-builder:latest -f docker/firmware-builder/Dockerfile .
32-
if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master" ]]; then
33-
docker push ghcr.io/${{ github.repository_owner }}/knot-firmware-builder:latest
34-
fi
35-
else
36-
docker pull ghcr.io/${{ github.repository_owner }}/knot-firmware-builder:latest
37-
fi
17+
uses: ./.github/actions/prepare-builder-image
18+
with:
19+
image-name: knot-firmware-builder
20+
dockerfile: docker/firmware-builder/Dockerfile
3821

3922
- name: Run script in Docker container
4023
run: docker run -v $PWD:/project -w /project/ ghcr.io/${{ github.repository_owner }}/knot-firmware-builder:latest sh -c "cd Firmware && ./esp_build_firmware.sh"

.github/workflows/_mechanical_build.yml

Lines changed: 19 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
on:
22
workflow_call:
3-
inputs:
4-
dockerfile_changed:
5-
description: "Whether the mechanical Dockerfile was changed"
6-
required: false
7-
type: boolean
8-
default: false
93

104
jobs:
115
build:
@@ -24,61 +18,31 @@ jobs:
2418
steps:
2519
- name: Checkout code
2620
uses: actions/checkout@v4
27-
28-
- name: Log in to GitHub Container Registry
29-
uses: docker/login-action@v3
3021
with:
31-
registry: ghcr.io
32-
username: ${{ github.actor }}
33-
password: ${{ secrets.GITHUB_TOKEN }}
22+
fetch-depth: 0
3423

3524
- name: Prepare image
36-
run: |
37-
if [ "${{ inputs.dockerfile_changed }}" == "true" ]; then
38-
docker build -t builder:local -f docker/mechanical-builder/Dockerfile .
39-
if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master" ]]; then
40-
docker tag builder:local ghcr.io/${{ github.repository_owner }}/knot-mechanical-builder:latest
41-
docker push ghcr.io/${{ github.repository_owner }}/knot-mechanical-builder:latest
42-
fi
43-
else
44-
docker pull ghcr.io/${{ github.repository_owner }}/knot-mechanical-builder:latest
45-
docker tag ghcr.io/${{ github.repository_owner }}/knot-mechanical-builder:latest builder:local
46-
fi
47-
docker run -d --name builder \
48-
-v ${{ github.workspace }}:/work \
49-
-w /work \
50-
builder:local tail -f /dev/null
51-
52-
- name: Start virtual display
53-
run: |
54-
docker exec builder bash -c "
55-
Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 &
56-
until xdpyinfo -display :99 > /dev/null 2>&1; do sleep 0.5; done
57-
x11vnc -display :99 -nopw -listen localhost -xkb -ncache 10 -ncache_cr -forever > /dev/null 2>&1 &
58-
"
25+
uses: ./.github/actions/prepare-builder-image
26+
with:
27+
image-name: knot-mechanical-builder
28+
dockerfile: docker/mechanical-builder/Dockerfile
5929

60-
- name: Run GUI application
30+
- name: Run build
6131
continue-on-error: true
6232
run: |
63-
docker exec -e DISPLAY=:99 builder bash -c "
64-
freecad --hidden /work/Mechanical/Design/${{ matrix.component }}/${{ matrix.component }}.FCStd /work/.github/workflows/freecad_export.py step pdf || true
65-
"
66-
67-
- name: Convert STEP to STL
68-
run: docker exec builder /opt/conda/envs/occenv/bin/python /work/.github/workflows/pythonocc_step_to_stl.py
69-
70-
- name: Generate Preview PNG
71-
run: docker exec builder bash -c "/work/.github/workflows/openscad_preview.sh"
72-
73-
- name: Copy reference files
74-
run: |
75-
docker exec builder bash -c "
76-
find /work/Mechanical/Design/${{ matrix.component }}/ -maxdepth 1 -name '${{ matrix.component }}*' ! -name '*.FCStd' ! -name '*.FCBak' -exec cp {} /work/temp/ \; || true
77-
"
78-
79-
- name: Stop container
80-
if: always()
81-
run: docker rm -f builder
33+
docker run --rm \
34+
-v ${{ github.workspace }}:/work \
35+
-w /work \
36+
ghcr.io/${{ github.repository_owner }}/knot-mechanical-builder:latest \
37+
bash -c "
38+
Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 &
39+
until xdpyinfo -display :99 > /dev/null 2>&1; do sleep 0.5; done
40+
x11vnc -display :99 -nopw -listen localhost -xkb -ncache 10 -ncache_cr -forever > /dev/null 2>&1 &
41+
DISPLAY=:99 freecad --hidden /work/Mechanical/Design/${{ matrix.component }}/${{ matrix.component }}.FCStd /work/.github/workflows/freecad_export.py step pdf || true
42+
/opt/conda/envs/occenv/bin/python /work/.github/workflows/pythonocc_step_to_stl.py
43+
/work/.github/workflows/openscad_preview.sh
44+
find /work/Mechanical/Design/${{ matrix.component }}/ -maxdepth 1 -name '${{ matrix.component }}*' ! -name '*.FCStd' ! -name '*.FCBak' -exec cp {} /work/temp/ \; || true
45+
"
8246
8347
- name: Fix file ownership
8448
if: always()

.github/workflows/electrical_workflow.yml

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,8 @@ on:
88
- 'docker/electrical-builder/Dockerfile'
99

1010
jobs:
11-
check-dockerfile:
12-
runs-on: ubuntu-latest
13-
outputs:
14-
dockerfile-changed: ${{ steps.filter.outputs.dockerfile }}
15-
steps:
16-
- uses: actions/checkout@v3
17-
- uses: dorny/paths-filter@v3
18-
id: filter
19-
with:
20-
filters: |
21-
dockerfile:
22-
- 'docker/electrical-builder/Dockerfile'
23-
2411
build:
25-
needs: check-dockerfile
2612
uses: ./.github/workflows/_electrical_build.yml
27-
with:
28-
dockerfile_changed: ${{ needs.check-dockerfile.outputs.dockerfile-changed == 'true' }}
2913
secrets: inherit
3014

3115
deploy-results:

.github/workflows/firmware_workflow.yml

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,8 @@ concurrency:
99
cancel-in-progress: true
1010

1111
jobs:
12-
check-dockerfile:
13-
runs-on: ubuntu-latest
14-
outputs:
15-
dockerfile-changed: ${{ steps.filter.outputs.dockerfile }}
16-
steps:
17-
- uses: actions/checkout@v3
18-
- uses: dorny/paths-filter@v3
19-
id: filter
20-
with:
21-
filters: |
22-
dockerfile:
23-
- 'docker/firmware-builder/Dockerfile'
24-
2512
build:
26-
needs: check-dockerfile
2713
uses: ./.github/workflows/_firmware_build.yml
28-
with:
29-
dockerfile_changed: ${{ needs.check-dockerfile.outputs.dockerfile-changed == 'true' }}
3014
secrets: inherit
3115

3216
publish-nightly:

.github/workflows/mechanical_workflow.yml

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,8 @@ on:
99
- 'docker/mechanical-builder/Dockerfile'
1010

1111
jobs:
12-
check-dockerfile:
13-
runs-on: ubuntu-latest
14-
outputs:
15-
dockerfile-changed: ${{ steps.filter.outputs.dockerfile }}
16-
steps:
17-
- uses: actions/checkout@v3
18-
- uses: dorny/paths-filter@v3
19-
id: filter
20-
with:
21-
filters: |
22-
dockerfile:
23-
- 'docker/mechanical-builder/Dockerfile'
24-
2512
build:
26-
needs: check-dockerfile
2713
uses: ./.github/workflows/_mechanical_build.yml
28-
with:
29-
dockerfile_changed: ${{ needs.check-dockerfile.outputs.dockerfile-changed == 'true' }}
3014
secrets: inherit
3115

3216
deploy-results:

.github/workflows/release_workflow.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,14 @@ on:
66
jobs:
77
build-firmware:
88
uses: ./.github/workflows/_firmware_build.yml
9-
with:
10-
dockerfile_changed: false
119
secrets: inherit
1210

1311
build-mechanical:
1412
uses: ./.github/workflows/_mechanical_build.yml
15-
with:
16-
dockerfile_changed: false
1713
secrets: inherit
1814

1915
build-electrical:
2016
uses: ./.github/workflows/_electrical_build.yml
21-
with:
22-
dockerfile_changed: false
2317
secrets: inherit
2418

2519
publish-release:

0 commit comments

Comments
 (0)