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" ] 
24+   pull_request :
25+     paths :
26+       - " .github/workflows/docker-publish.yml" 
27+       - " Dockerfile*" 
28+       - " candle-binding/**" 
29+       - " src/**" 
30+       - " e2e-tests/llm-katan/**" 
2431
2532jobs :
26-   #  Parallel  job for building both  images
27-   build_and_push :
33+   #  Build  job for multi-architecture Docker  images
34+   build_multiarch :
2835    runs-on : ubuntu-latest 
2936    permissions :
3037      contents : read 
3138      packages : write 
3239    strategy :
3340      matrix :
3441        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 
3648
3749    steps :
3850      - name : Check out the repo 
4153      - name : Set up Docker Buildx 
4254        uses : docker/setup-buildx-action@v3 
4355
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' 
4658        uses : docker/setup-qemu-action@v3 
4759        with :
4860          platforms : arm64 
@@ -54,91 +66,175 @@ jobs:
5466          username : ${{ github.actor }} 
5567          password : ${{ secrets.GITHUB_TOKEN }} 
5668
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' 
6872        uses : actions/cache@v4 
6973        with :
7074          path : | 
71-             ~/.cargo/bin/ 
7275            ~/.cargo/registry/index/ 
7376            ~/.cargo/registry/cache/ 
7477            ~/.cargo/git/db/ 
7578            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') }} 
7781          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 
79107
80- #  Set build context and dockerfile based on matrix
81108      - name : Set build parameters 
82109        id : build-params 
83110        run : | 
111+           # Default to cross-compilation for ARM64 (always enabled for better performance) 
112+           USE_CROSS_COMPILATION="${{ inputs.use_cross_compilation || 'true' }}" 
113+ 
84114          if [ "${{ matrix.image }}" = "extproc" ]; then 
85115            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 
88123          elif [ "${{ matrix.image }}" = "llm-katan" ]; then 
89124            echo "context=./e2e-tests/llm-katan" >> $GITHUB_OUTPUT 
90125            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 
92127          fi 
93128
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 
106131        run : | 
107132          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 }}" 
117134
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:]') 
124135          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 
126137          else 
127138            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 
129140            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 
131142            fi 
132143          fi 
133144
134145name : Build and push ${{ matrix.image }} Docker image 
146+         id : build 
135147        uses : docker/build-push-action@v5 
136148        with :
137149          context : ${{ steps.build-params.outputs.context }} 
138150          file : ${{ steps.build-params.outputs.dockerfile }} 
139-           platforms : ${{ steps.build-params.outputs.platforms  }} 
151+           platforms : ${{ steps.build-params.outputs.platform  }} 
140152          push : ${{ github.event_name != 'pull_request' }} 
141153          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 
143159          build-args : | 
144160            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