1414 required : false
1515 type : boolean
1616 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 "
1919 required : false
2020 type : boolean
21- default : false
21+ default : true
2222 push :
2323 branches : [ "main" ]
2424 pull_request :
3030 - " e2e-tests/llm-katan/**"
3131
3232jobs :
33- # Parallel job for building both images
34- build_and_push :
35- runs-on : ubuntu-latest
33+ # Separate job for native builds vs emulated builds
34+ build_native :
35+ runs-on : ${{ matrix.arch == 'arm64' && ' ubuntu-latest-arm64' || 'ubuntu-latest' }}
3636 permissions :
3737 contents : read
3838 packages : write
3939 strategy :
4040 matrix :
4141 image : [extproc, llm-katan]
42- fail-fast : false # Continue building other images if one fails
42+ # Smart architecture selection:
43+ # - PR: Only AMD64 for faster feedback
44+ # - Main/Release: Multi-arch for production
45+ # arch: ${{ github.event_name == 'pull_request' && fromJSON('["amd64"]') || fromJSON('["amd64", "arm64"]') }}
46+ arch : ["amd64", "arm64"]
47+ fail-fast : false
4348
4449 steps :
4550 - name : Check out the repo
4853 - name : Set up Docker Buildx
4954 uses : docker/setup-buildx-action@v3
5055
51- - name : Set up QEMU (only for multi-arch builds )
52- if : inputs.skip_multiarch != true && github.event_name != 'pull_request '
56+ - name : Set up QEMU (fallback for ARM64 if native runners unavailable )
57+ if : matrix.arch == 'arm64' && runner.arch != 'ARM64 '
5358 uses : docker/setup-qemu-action@v3
5459 with :
5560 platforms : arm64
6166 username : ${{ github.actor }}
6267 password : ${{ secrets.GITHUB_TOKEN }}
6368
69+ # Enhanced Rust caching for extproc builds with incremental compilation
70+ - name : Cache Rust dependencies (extproc)
71+ if : matrix.image == 'extproc'
72+ uses : actions/cache@v4
73+ with :
74+ path : |
75+ ~/.cargo/registry/index/
76+ ~/.cargo/registry/cache/
77+ ~/.cargo/git/db/
78+ candle-binding/target/
79+ ~/.rustup/
80+ key : ${{ runner.os }}-cargo-${{ matrix.arch }}-${{ hashFiles('candle-binding/Cargo.toml') }}-${{ hashFiles('candle-binding/Cargo.lock') }}-${{ hashFiles('candle-binding/src/**/*.rs') }}
81+ restore-keys : |
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+
64100 - name : Generate date tag for nightly builds
65101 id : date
66102 if : inputs.is_nightly == true
@@ -69,63 +105,40 @@ jobs:
69105 - name : Set lowercase repository owner
70106 run : echo "REPOSITORY_OWNER_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
71107
72- # Set build context and dockerfile based on matrix
73108 - name : Set build parameters
74109 id : build-params
75110 run : |
76- # Skip multi-arch for PRs to speed up CI, unless explicitly disabled
77- PLATFORMS="linux/amd64"
78- if [ "${{ inputs.skip_multiarch }}" != "true" ] && [ "${{ github.event_name }}" != "pull_request" ]; then
79- PLATFORMS="linux/amd64,linux/arm64"
80- fi
111+ # Default to cross-compilation for ARM64 (always enabled for better performance)
112+ USE_CROSS_COMPILATION="${{ inputs.use_cross_compilation || 'true' }}"
81113
82114 if [ "${{ matrix.image }}" = "extproc" ]; then
83115 echo "context=." >> $GITHUB_OUTPUT
84- echo "dockerfile=./Dockerfile.extproc" >> $GITHUB_OUTPUT
85- echo "platforms=${PLATFORMS}" >> $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
86123 elif [ "${{ matrix.image }}" = "llm-katan" ]; then
87124 echo "context=./e2e-tests/llm-katan" >> $GITHUB_OUTPUT
88125 echo "dockerfile=./e2e-tests/llm-katan/Dockerfile" >> $GITHUB_OUTPUT
89- echo "platforms=${PLATFORMS }" >> $GITHUB_OUTPUT
126+ echo "platform=linux/${{ matrix.arch } }" >> $GITHUB_OUTPUT
90127 fi
91128
92- # Extract version for llm-katan
93- - name : Extract version from pyproject.toml
94- id : version
95- if : matrix.image == 'llm-katan'
96- run : |
97- VERSION=$(grep '^version = ' e2e-tests/llm-katan/pyproject.toml | sed 's/version = "\(.*\)"/\1/')
98- echo "version=$VERSION" >> $GITHUB_OUTPUT
99-
100- # Generate tags for extproc
101- - name : Generate extproc tags
102- id : extproc-tags
103- if : matrix.image == 'extproc'
129+ - name : Generate tags
130+ id : tags
104131 run : |
105132 REPO_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')
106- if [ "${{ inputs.is_nightly }}" = "true" ]; then
107- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/extproc:nightly-${{ steps.date.outputs.date_tag }}" >> $GITHUB_OUTPUT
108- else
109- if [ "${{ github.event_name }}" != "pull_request" ]; then
110- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/extproc:${{ github.sha }},ghcr.io/${REPO_LOWER}/semantic-router/extproc:latest" >> $GITHUB_OUTPUT
111- else
112- echo "tags=ghcr.io/${REPO_LOWER}/semantic-router/extproc:${{ github.sha }}" >> $GITHUB_OUTPUT
113- fi
114- fi
133+ ARCH_SUFFIX="${{ matrix.arch }}"
115134
116- # Generate tags for llm-katan
117- - name : Generate llm-katan tags
118- id : llm-katan-tags
119- if : matrix.image == 'llm-katan'
120- run : |
121- REPO_LOWER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')
122135 if [ "${{ inputs.is_nightly }}" = "true" ]; then
123- 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
124137 else
125138 if [ "${{ github.event_name }}" != "pull_request" ]; then
126- 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
127140 else
128- 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
129142 fi
130143 fi
131144
@@ -135,23 +148,89 @@ jobs:
135148 with :
136149 context : ${{ steps.build-params.outputs.context }}
137150 file : ${{ steps.build-params.outputs.dockerfile }}
138- platforms : ${{ steps.build-params.outputs.platforms }}
151+ platforms : ${{ steps.build-params.outputs.platform }}
139152 push : ${{ github.event_name != 'pull_request' }}
140153 load : ${{ github.event_name == 'pull_request' }}
141- tags : ${{ matrix.image == 'extproc' && steps.extproc-tags.outputs.tags || steps.llm-katan-tags.outputs.tags }}
142- cache-from : type=gha
143- cache-to : type=gha,mode=max
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
144159 build-args : |
145160 BUILDKIT_INLINE_CACHE=1
146- CARGO_BUILD_JOBS=4
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
147165 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_native
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+ TAG="${{ github.sha }}"
213+ fi
214+
215+ # Create and push manifest
216+ docker buildx imagetools create \
217+ --tag ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG} \
218+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-amd64 \
219+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-arm64
220+
221+ # Also tag as latest for non-nightly builds
222+ if [ "${{ inputs.is_nightly }}" != "true" ]; then
223+ docker buildx imagetools create \
224+ --tag ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:latest \
225+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-amd64 \
226+ ghcr.io/${REPO_LOWER}/semantic-router/${{ matrix.image }}:${TAG}-arm64
227+ fi
148228
149229 - name : Build summary
150230 if : always()
151231 run : |
152- if [ "${{ steps.build.conclusion }}" = "success" ]; then
153- echo "::notice title=Build Success::${{ matrix.image }} build completed successfully"
154- echo "Image digest: ${{ steps.build.outputs.digest }}"
232+ if [ "${{ job.status }}" = "success" ]; then
233+ echo "::notice title=Build Success::${{ matrix.image }} multi-arch manifest created successfully"
155234 else
156235 echo "::error title=Build Failed::${{ matrix.image }} build failed"
157236 fi
0 commit comments