diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c84ea768..f4eadcd4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,8 +50,42 @@ jobs: - name: Lockfile drift guard run: git diff --exit-code -- Cargo.lock + resolve: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.release.outputs.tag }} + source_ref: ${{ steps.release.outputs.source_ref }} + sha: ${{ steps.release.outputs.sha }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Resolve release source + id: release + shell: bash + run: | + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + tag="v${{ inputs.version }}" + git fetch --force origin "refs/tags/${tag}:refs/tags/${tag}" + sha="$(git rev-list -n 1 "${tag}")" + source_ref="${tag}" + else + tag="${GITHUB_REF_NAME}" + sha="${GITHUB_SHA}" + source_ref="${GITHUB_REF_NAME}" + fi + + if [ -z "${sha}" ]; then + echo "Unable to resolve release source for ${tag}" >&2 + exit 1 + fi + + echo "tag=${tag}" >> "$GITHUB_OUTPUT" + echo "source_ref=${source_ref}" >> "$GITHUB_OUTPUT" + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + build: - needs: parity + needs: [parity, resolve] # `parity` is gated to tag-push events. On manual `workflow_dispatch`, # parity is skipped, so let `build` proceed when parity either succeeded # or was skipped — but never when it actually failed or the run was @@ -106,6 +140,8 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ needs.resolve.outputs.source_ref }} - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -121,6 +157,8 @@ jobs: sudo apt-get install -y libdbus-1-dev pkg-config - name: Build shell: bash + env: + DEEPSEEK_BUILD_SHA: ${{ needs.resolve.outputs.sha }} run: cargo build --release --locked --target ${{ matrix.target }} - name: Rename binary shell: bash @@ -137,14 +175,22 @@ jobs: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_name }} docker: - needs: build + needs: [build, resolve] if: ${{ !cancelled() && needs.build.result == 'success' }} runs-on: ubuntu-latest permissions: contents: read packages: write steps: - - uses: actions/checkout@v4 + - name: Checkout release source + uses: actions/checkout@v4 + with: + ref: ${{ needs.resolve.outputs.source_ref }} + path: source + - name: Checkout release infrastructure + uses: actions/checkout@v4 + with: + path: infra - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx @@ -170,22 +216,28 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern=v{{major}} type=ref,event=tag + type=semver,pattern={{version}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} + type=semver,pattern={{major}}.{{minor}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} + type=semver,pattern=v{{major}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }} type=raw,value=v${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }} type=raw,value=latest - name: Build and push uses: docker/build-push-action@v6 with: - context: . + context: source + file: infra/Dockerfile platforms: linux/amd64,linux/arm64 push: true + build-args: | + DEEPSEEK_BUILD_SHA=${{ needs.resolve.outputs.sha }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max release: - needs: [build, docker] + needs: [build, docker, resolve] if: ${{ !cancelled() && needs.build.result == 'success' && needs.docker.result == 'success' }} runs-on: ubuntu-latest permissions: @@ -208,18 +260,9 @@ jobs: printf '%s %s\n' "${hash}" "${base}" >> "${manifest}" done < <(find artifacts -type f ! -path 'artifacts/checksums/*' -print0 | sort -z) cat "${manifest}" - - name: Resolve release tag - id: release_tag - shell: bash - run: | - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - echo "tag=v${{ inputs.version }}" >> "$GITHUB_OUTPUT" - else - echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" - fi - uses: softprops/action-gh-release@v1 with: - tag_name: ${{ steps.release_tag.outputs.tag }} + tag_name: ${{ needs.resolve.outputs.tag }} files: artifacts/*/* prerelease: false body: | @@ -239,7 +282,7 @@ jobs: docker run --rm -it \ -e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \ -v ~/.deepseek:/home/deepseek/.deepseek \ - ghcr.io/hmbown/deepseek-tui:${{ steps.release_tag.outputs.tag }} + ghcr.io/hmbown/deepseek-tui:${{ needs.resolve.outputs.tag }} ``` The image ships the `deepseek` dispatcher and `deepseek-tui` runtime. The `latest` tag is also updated on release. diff --git a/Dockerfile b/Dockerfile index 89216529..458243aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,9 +20,24 @@ FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-bookworm AS builder ARG TARGETPLATFORM ARG TARGETARCH ARG BUILDPLATFORM +ARG DEEPSEEK_BUILD_SHA -RUN apt-get update && apt-get install -y --no-install-recommends \ - pkg-config libdbus-1-dev \ +ENV CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \ + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ + PKG_CONFIG_ALLOW_CROSS=1 \ + PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \ + DEEPSEEK_BUILD_SHA=${DEEPSEEK_BUILD_SHA} + +RUN if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]; then \ + dpkg --add-architecture arm64; \ + fi \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + pkg-config libdbus-1-dev \ + && if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]; then \ + apt-get install -y --no-install-recommends \ + gcc-aarch64-linux-gnu libc6-dev-arm64-cross libdbus-1-dev:arm64; \ + fi \ && rm -rf /var/lib/apt/lists/* # Translate Docker platform into Rust target triple.