14
14
required : false
15
15
type : boolean
16
16
default : false
17
- skip_multiarch :
18
- description : " Skip multi-architecture build for faster CI "
17
+ use_cross_compilation :
18
+ description : " Use cross-compilation instead of emulation for ARM64 "
19
19
required : false
20
20
type : boolean
21
- default : false
21
+ default : true
22
22
push :
23
23
branches : [ "main" ]
24
+ pull_request :
25
+ paths :
26
+ - " .github/workflows/docker-publish.yml"
27
+ - " Dockerfile*"
28
+ - " candle-binding/**"
29
+ - " src/**"
30
+ - " e2e-tests/llm-katan/**"
24
31
25
32
jobs :
26
- # Parallel job for building both images
27
- build_and_push :
33
+ # Build job for multi-architecture Docker images
34
+ build_multiarch :
28
35
runs-on : ubuntu-latest
29
36
permissions :
30
37
contents : read
31
38
packages : write
32
39
strategy :
33
40
matrix :
34
41
image : [extproc, llm-katan]
35
- fail-fast : false # Continue building other images if one fails
42
+ # Multi-architecture build strategy:
43
+ # - AMD64: Native build on ubuntu-latest (fast)
44
+ # - ARM64: Cross-compilation on ubuntu-latest (faster than emulation)
45
+ # arch: ${{ github.event_name == 'pull_request' && fromJSON('["amd64"]') || fromJSON('["amd64", "arm64"]') }}
46
+ arch : ["amd64", "arm64"]
47
+ fail-fast : false
36
48
37
49
steps :
38
50
- name : Check out the repo
41
53
- name : Set up Docker Buildx
42
54
uses : docker/setup-buildx-action@v3
43
55
44
- - name : Set up QEMU (only for multi-arch builds)
45
- if : inputs.skip_multiarch != true
56
+ - name : Set up QEMU for cross-compilation
57
+ if : matrix.arch == 'arm64'
46
58
uses : docker/setup-qemu-action@v3
47
59
with :
48
60
platforms : arm64
@@ -54,91 +66,175 @@ jobs:
54
66
username : ${{ github.actor }}
55
67
password : ${{ secrets.GITHUB_TOKEN }}
56
68
57
- - name : Generate date tag for nightly builds
58
- id : date
59
- if : inputs.is_nightly == true
60
- run : echo "date_tag=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
61
-
62
- - name : Set lowercase repository owner
63
- run : echo "REPOSITORY_OWNER_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
64
-
65
- # Rust build cache for extproc - only use GitHub Actions cache for non-PR builds
66
- - name : Cache Rust dependencies (extproc only)
67
- if : matrix.image == 'extproc' && github.event_name != 'pull_request'
69
+ # Enhanced Rust caching for extproc builds with incremental compilation
70
+ - name : Cache Rust dependencies (extproc)
71
+ if : matrix.image == 'extproc'
68
72
uses : actions/cache@v4
69
73
with :
70
74
path : |
71
- ~/.cargo/bin/
72
75
~/.cargo/registry/index/
73
76
~/.cargo/registry/cache/
74
77
~/.cargo/git/db/
75
78
candle-binding/target/
76
- key : ${{ runner.os }}-cargo-extproc-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }}
79
+ ~/.rustup/
80
+ key : ${{ runner.os }}-cargo-${{ matrix.arch }}-${{ hashFiles('candle-binding/Cargo.toml') }}-${{ hashFiles('candle-binding/Cargo.lock') }}-${{ hashFiles('candle-binding/src/**/*.rs') }}
77
81
restore-keys : |
78
- ${{ runner.os }}-cargo-extproc-
82
+ ${{ runner.os }}-cargo-${{ matrix.arch }}-${{ hashFiles('candle-binding/Cargo.toml') }}-${{ hashFiles('candle-binding/Cargo.lock') }}-
83
+ ${{ runner.os }}-cargo-${{ matrix.arch }}-${{ hashFiles('candle-binding/Cargo.toml') }}-
84
+ ${{ runner.os }}-cargo-${{ matrix.arch }}-
85
+ ${{ runner.os }}-cargo-
86
+
87
+ # Python caching for llm-katan builds
88
+ - name : Cache Python dependencies (llm-katan)
89
+ if : matrix.image == 'llm-katan'
90
+ uses : actions/cache@v4
91
+ with :
92
+ path : |
93
+ ~/.cache/pip
94
+ e2e-tests/llm-katan/.venv
95
+ key : ${{ runner.os }}-pip-${{ matrix.arch }}-${{ hashFiles('e2e-tests/llm-katan/requirements.txt', 'e2e-tests/llm-katan/pyproject.toml') }}
96
+ restore-keys : |
97
+ ${{ runner.os }}-pip-${{ matrix.arch }}-
98
+ ${{ runner.os }}-pip-
99
+
100
+ - name : Generate date tag for nightly builds
101
+ id : date
102
+ if : inputs.is_nightly == true
103
+ run : echo "date_tag=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
104
+
105
+ - name : Set lowercase repository owner
106
+ run : echo "REPOSITORY_OWNER_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
79
107
80
- # Set build context and dockerfile based on matrix
81
108
- name : Set build parameters
82
109
id : build-params
83
110
run : |
111
+ # Default to cross-compilation for ARM64 (always enabled for better performance)
112
+ USE_CROSS_COMPILATION="${{ inputs.use_cross_compilation || 'true' }}"
113
+
84
114
if [ "${{ matrix.image }}" = "extproc" ]; then
85
115
echo "context=." >> $GITHUB_OUTPUT
86
- echo "dockerfile=./Dockerfile.extproc" >> $GITHUB_OUTPUT
87
- echo "platforms=${{ inputs.skip_multiarch == true && 'linux/amd64' || 'linux/amd64,linux/arm64' }}" >> $GITHUB_OUTPUT
116
+ if [ "$USE_CROSS_COMPILATION" = "true" ] && [ "${{ matrix.arch }}" = "arm64" ]; then
117
+ echo "dockerfile=./Dockerfile.extproc.cross" >> $GITHUB_OUTPUT
118
+ echo "platform=linux/${{ matrix.arch }}" >> $GITHUB_OUTPUT
119
+ else
120
+ echo "dockerfile=./Dockerfile.extproc" >> $GITHUB_OUTPUT
121
+ echo "platform=linux/${{ matrix.arch }}" >> $GITHUB_OUTPUT
122
+ fi
88
123
elif [ "${{ matrix.image }}" = "llm-katan" ]; then
89
124
echo "context=./e2e-tests/llm-katan" >> $GITHUB_OUTPUT
90
125
echo "dockerfile=./e2e-tests/llm-katan/Dockerfile" >> $GITHUB_OUTPUT
91
- echo "platforms= ${{ inputs.skip_multiarch == true && 'linux/amd64' || 'linux/amd64,linux/arm64' }}" >> $GITHUB_OUTPUT
126
+ echo "platform=linux/ ${{ matrix.arch }}" >> $GITHUB_OUTPUT
92
127
fi
93
128
94
- # Extract version for llm-katan
95
- - name : Extract version from pyproject.toml
96
- id : version
97
- if : matrix.image == 'llm-katan'
98
- run : |
99
- VERSION=$(grep '^version = ' e2e-tests/llm-katan/pyproject.toml | sed 's/version = "\(.*\)"/\1/')
100
- echo "version=$VERSION" >> $GITHUB_OUTPUT
101
-
102
- # Generate tags for extproc
103
- - name : Generate extproc tags
104
- id : extproc-tags
105
- if : matrix.image == 'extproc'
129
+ - name : Generate tags
130
+ id : tags
106
131
run : |
107
132
REPO_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')
108
- if [ "${{ inputs.is_nightly }}" = "true" ]; then
109
- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/extproc:nightly-${{ steps.date.outputs.date_tag }}" >> $GITHUB_OUTPUT
110
- else
111
- if [ "${{ github.event_name }}" != "pull_request" ]; then
112
- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/extproc:${{ github.sha }},ghcr.io/${REPO_LOWER}/semantic-router/extproc:latest" >> $GITHUB_OUTPUT
113
- else
114
- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/extproc:${{ github.sha }}" >> $GITHUB_OUTPUT
115
- fi
116
- fi
133
+ ARCH_SUFFIX="${{ matrix.arch }}"
117
134
118
- # Generate tags for llm-katan
119
- - name : Generate llm-katan tags
120
- id : llm-katan-tags
121
- if : matrix.image == 'llm-katan'
122
- run : |
123
- REPO_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')
124
135
if [ "${{ inputs.is_nightly }}" = "true" ]; then
125
- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/llm-katan :nightly-${{ steps.date.outputs.date_tag }}" >> $GITHUB_OUTPUT
136
+ echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }} :nightly-${{ steps.date.outputs.date_tag }}-${ARCH_SUFFIX }" >> $GITHUB_OUTPUT
126
137
else
127
138
if [ "${{ github.event_name }}" != "pull_request" ]; then
128
- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/llm-katan: ${{ github.sha }},ghcr.io/${REPO_LOWER}/semantic-router/llm-katan:latest,ghcr.io/${REPO_LOWER}/semantic-router/llm-katan:v${{ steps.version.outputs.version } }" >> $GITHUB_OUTPUT
139
+ echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${{ github.sha }}-${ARCH_SUFFIX }" >> $GITHUB_OUTPUT
129
140
else
130
- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/llm-katan: ${{ github.sha } }" >> $GITHUB_OUTPUT
141
+ echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:pr- ${{ github.event.number }}-${ARCH_SUFFIX }" >> $GITHUB_OUTPUT
131
142
fi
132
143
fi
133
144
134
145
- name : Build and push ${{ matrix.image }} Docker image
146
+ id : build
135
147
uses : docker/build-push-action@v5
136
148
with :
137
149
context : ${{ steps.build-params.outputs.context }}
138
150
file : ${{ steps.build-params.outputs.dockerfile }}
139
- platforms : ${{ steps.build-params.outputs.platforms }}
151
+ platforms : ${{ steps.build-params.outputs.platform }}
140
152
push : ${{ github.event_name != 'pull_request' }}
141
153
load : ${{ github.event_name == 'pull_request' }}
142
- tags : ${{ matrix.image == 'extproc' && steps.extproc-tags.outputs.tags || steps.llm-katan-tags.outputs.tags }}
154
+ tags : ${{ steps.tags.outputs.tags }}
155
+ cache-from : |
156
+ type=gha
157
+ type=local,src=/tmp/.buildx-cache
158
+ cache-to : type=local,dest=/tmp/.buildx-cache,mode=max
143
159
build-args : |
144
160
BUILDKIT_INLINE_CACHE=1
161
+ CARGO_BUILD_JOBS=${{ github.event_name == 'pull_request' && '8' || '16' }}
162
+ CARGO_INCREMENTAL=1
163
+ RUSTC_WRAPPER=""
164
+ CARGO_NET_GIT_FETCH_WITH_CLI=true
165
+ BUILDKIT_PROGRESS=plain
166
+ TARGETARCH=${{ matrix.arch }}
167
+ # Optimize Rust compilation for ARM64
168
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
169
+ # Enable link-time optimization for release builds
170
+ CARGO_PROFILE_RELEASE_LTO=thin
171
+ CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1
172
+ # Use faster linker
173
+ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-fuse-ld=lld"
174
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-fuse-ld=lld"
175
+
176
+ # Create multi-arch manifest for final images
177
+ create_manifest :
178
+ needs : build_multiarch
179
+ runs-on : ubuntu-latest
180
+ permissions :
181
+ contents : read
182
+ packages : write
183
+ if : github.event_name != 'pull_request'
184
+ strategy :
185
+ matrix :
186
+ image : [extproc, llm-katan]
187
+
188
+ steps :
189
+ - name : Log in to GitHub Container Registry
190
+ uses : docker/login-action@v3
191
+ with :
192
+ registry : ghcr.io
193
+ username : ${{ github.actor }}
194
+ password : ${{ secrets.GITHUB_TOKEN }}
195
+
196
+ - name : Set lowercase repository owner
197
+ run : echo "REPOSITORY_OWNER_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
198
+
199
+ - name : Generate date tag for nightly builds
200
+ id : date
201
+ if : inputs.is_nightly == true
202
+ run : echo "date_tag=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
203
+
204
+ - name : Create and push manifest
205
+ run : |
206
+ REPO_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')
207
+
208
+ # Create manifest for the specific image
209
+ if [ "${{ inputs.is_nightly }}" = "true" ]; then
210
+ TAG="nightly-${{ steps.date.outputs.date_tag }}"
211
+ else
212
+ if [ "${{ github.event_name }}" != "pull_request" ]; then
213
+ TAG="${{ github.sha }}"
214
+ else
215
+ TAG="pr-${{ github.event.number }}"
216
+ fi
217
+ fi
218
+
219
+ # Create and push manifest by combining architecture-specific images
220
+ docker buildx imagetools create \
221
+ --tag ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG} \
222
+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-amd64 \
223
+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-arm64
224
+
225
+ # Also tag as latest for non-nightly builds
226
+ if [ "${{ inputs.is_nightly }}" != "true" ]; then
227
+ docker buildx imagetools create \
228
+ --tag ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:latest \
229
+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-amd64 \
230
+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-arm64
231
+ fi
232
+
233
+ - name : Build summary
234
+ if : always()
235
+ run : |
236
+ if [ "${{ job.status }}" = "success" ]; then
237
+ echo "::notice title=Build Success::${{ matrix.image }} multi-arch manifest created successfully"
238
+ else
239
+ echo "::error title=Build Failed::${{ matrix.image }} build failed"
240
+ fi
0 commit comments