Skip to content

Commit d8ef7d5

Browse files
committed
feat: enhance Dockerfiles for cross-compilation and optimize CI pipeline
Signed-off-by: liuhy <[email protected]>
1 parent 5aa2708 commit d8ef7d5

File tree

5 files changed

+3632
-61
lines changed

5 files changed

+3632
-61
lines changed

.github/workflows/docker-publish.yml

Lines changed: 135 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ on:
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:
@@ -30,16 +30,21 @@ on:
3030
- "e2e-tests/llm-katan/**"
3131

3232
jobs:
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
@@ -48,8 +53,8 @@ jobs:
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
@@ -61,6 +66,37 @@ jobs:
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

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Rust
22
target/
33
**/*.rs.bk
4-
Cargo.lock
4+
# Note: Cargo.lock should be committed for applications and workspace roots
5+
# Cargo.lock
56

67
# Python
78
*.pyc

Dockerfile.extproc

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
11
# Build the Rust library using Makefile
22
FROM rust:1.85 as rust-builder
33

4-
# Install make and other build dependencies
4+
# Install make and other build dependencies including cross-compilation tools
55
RUN apt-get update && apt-get install -y \
66
make \
77
build-essential \
88
pkg-config \
9+
lld \
10+
clang \
11+
gcc-aarch64-linux-gnu \
912
&& rm -rf /var/lib/apt/lists/*
1013

14+
# Set up Rust for cross-compilation
15+
RUN rustup target add aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu
16+
17+
# Configure Cargo for optimized builds
18+
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
19+
ENV CARGO_INCREMENTAL=1
20+
ENV CARGO_PROFILE_RELEASE_LTO=thin
21+
ENV CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1
22+
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld"
23+
1124
WORKDIR /app
1225

13-
# Copy only essential files for Rust build
26+
# Copy dependency files first for better layer caching
27+
COPY candle-binding/Cargo.toml ./candle-binding/
28+
# Copy Cargo.lock if it exists in the build context
29+
COPY candle-binding/Cargo.loc[k] ./candle-binding/
1430
COPY tools/make/ tools/make/
1531
COPY Makefile ./
16-
COPY candle-binding/Cargo.toml candle-binding/
17-
COPY candle-binding/src/ candle-binding/src/
32+
33+
# Pre-build dependencies to cache them
34+
RUN cd candle-binding && \
35+
mkdir -p src && \
36+
echo "fn main() {}" > src/lib.rs && \
37+
cargo build --release && \
38+
rm -rf src
39+
40+
# Copy source code and build
41+
COPY candle-binding/src/ ./candle-binding/src/
1842

1943
# Use Makefile to build the Rust library
2044
RUN make rust

0 commit comments

Comments
 (0)