Repository navigation
Build #63098
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| name: Build | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| force_build: | |
| description: "Force run build job when manually triggered" | |
| required: false | |
| default: "true" | |
| docker_only: | |
| description: "Only rebuild and push Docker images" | |
| required: false | |
| default: "false" | |
| schedule: | |
| - cron: '*/30 * * * *' | |
| # A full third party build runs for hours while the schedule fires every 30 | |
| # minutes. Without a concurrency group the runs stack up and race each other on | |
| # the same release tag. | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| cancel-in-progress: false | |
| jobs: | |
| prerelease: | |
| name: Prerelease | |
| # This job only resolves a hash and pre-fetches sources, so it does not need | |
| # a macOS runner. ubuntu-latest also ships GNU md5sum, which keeps archive | |
| # verification on rather than at the mercy of what the runner happens to have. | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_REPO: ${{ github.repository }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MACOS_X86_MAX_AGE_DAYS: ${{ vars.MACOS_X86_MAX_AGE_DAYS || '7' }} | |
| permissions: | |
| contents: write | |
| outputs: | |
| should_release: ${{ steps.check_diff.outputs.should_release }} | |
| should_update_docker: ${{ steps.check_diff.outputs.should_update_docker }} | |
| should_build_macos_x86: ${{ steps.check_diff.outputs.should_build_macos_x86 }} | |
| thirdparty_commit_hash: ${{ steps.check_diff.outputs.thirdparty_commit_hash }} | |
| doris_version: ${{ steps.check_diff.outputs.doris_version }} | |
| attempt: ${{ steps.check_diff.outputs.attempt }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| repository: 'apache/doris' | |
| fetch-depth: 0 | |
| - name: Check Diff | |
| id: check_diff | |
| env: | |
| MANUAL_FORCE_BUILD: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_build == 'true' }} | |
| MANUAL_DOCKER_ONLY: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.docker_only == 'true' }} | |
| run: | | |
| if [[ -z "$(gh release list)" ]] || | |
| ! gh release list | awk -F "\t" '{ print $3 }' | grep 'automation' >/dev/null; then | |
| gh release create -t 'Apache Doris Third Party Prebuilt' automation | |
| fi | |
| max_attempts=3 | |
| # The release note is the only state this pipeline keeps. Read it once | |
| # so the three fields below cannot come from three different revisions. | |
| note="$(gh release view automation)" | |
| last_version="$(echo "${note}" | sed -n -E 's/Doris Version: \*(.*)\*.*/\1/p')" | |
| last_status="$(echo "${note}" | sed -n -E 's/Status: \*(.*)\*.*/\1/p')" | |
| last_attempt="$(echo "${note}" | sed -n -E 's/Attempts: \*([0-9]+)\*.*/\1/p')" | |
| # A note without a usable counter must not enable retries: treating it | |
| # as attempt 0 is what turns every scheduled run into a full rebuild. | |
| [[ "${last_attempt}" =~ ^[0-9]+$ ]] || last_attempt="${max_attempts}" | |
| current_version="$(git log -1 --format='%H')" | |
| thirdparty_commit_hash="$(git log -1 --format='%H' -- thirdparty)" | |
| echo "Last Version: ${last_version}" | |
| echo "Last Status: ${last_status}" | |
| echo "Last Attempt: ${last_attempt}" | |
| echo "Current Version: ${current_version}" | |
| echo "Thirdparty Commit: ${thirdparty_commit_hash}" | |
| should_release=false | |
| should_update_docker=false | |
| attempt=1 | |
| if [[ -z "${last_version}" ]]; then | |
| echo "The first release was detected." | |
| should_release=true | |
| should_update_docker=true | |
| elif [[ "${last_version}" != "${current_version}" ]]; then | |
| changed_files="$(git diff --name-only "${last_version}" "${current_version}")" | |
| echo -e "Changed files:\n${changed_files}" | |
| if grep -qE '^thirdparty/' <<<"${changed_files}"; then | |
| should_release=true | |
| should_update_docker=true | |
| fi | |
| if grep -qx 'docker/compilation/Dockerfile' <<<"${changed_files}"; then | |
| should_update_docker=true | |
| fi | |
| elif [[ "${last_status}" == 'FAILURE' ]] && [[ "${last_attempt}" -lt "${max_attempts}" ]]; then | |
| # Downloads from third party mirrors fail often enough that a single | |
| # red run should not park master until the next thirdparty/ commit | |
| # lands. Retry a bounded number of times, never forever. | |
| attempt=$((last_attempt + 1)) | |
| echo "Previous build failed, retrying (attempt ${attempt}/${max_attempts})." | |
| should_release=true | |
| should_update_docker=true | |
| fi | |
| # docker_only takes precedence over force_build because force_build | |
| # defaults to true for backwards compatibility with existing callers. | |
| if [[ "${MANUAL_DOCKER_ONLY}" == 'true' ]]; then | |
| echo 'Manual Docker-only build requested.' | |
| should_release=false | |
| should_update_docker=true | |
| elif [[ "${MANUAL_FORCE_BUILD}" == 'true' ]]; then | |
| echo 'Manual full build requested.' | |
| should_release=true | |
| should_update_docker=true | |
| fi | |
| # Docker-only failures are deterministic image/workflow failures. Do | |
| # not turn their scheduled retry into an expensive full thirdparty build. | |
| if [[ "${should_release}" == 'false' ]] && [[ "${should_update_docker}" == 'true' ]]; then | |
| attempt="${max_attempts}" | |
| fi | |
| # The macOS x86_64 leg is by far the slowest of the four - 4h22m41s of the | |
| # 5h01m run 31988123966 took, against 1h27m54s for Linux arm64 - and it is | |
| # already allowed to fail without holding up a release. Rebuild it only once | |
| # its published archive has gone stale rather than on every thirdparty change. | |
| should_build_macos_x86="${should_release}" | |
| if [[ "${should_build_macos_x86}" == 'true' ]]; then | |
| macos_x86_asset='doris-thirdparty-prebuilt-darwin-x86_64.tar.xz' | |
| asset_updated_at="$(gh release view automation --json assets \ | |
| --jq ".assets[] | select(.name == \"${macos_x86_asset}\") | .updatedAt")" || asset_updated_at='' | |
| if [[ -n "${asset_updated_at}" ]]; then | |
| age_days="$(( ($(date -u +%s) - $(date -u -d "${asset_updated_at}" +%s)) / 86400 ))" | |
| echo "macOS x86_64 archive is ${age_days} day(s) old, rebuild threshold is ${MACOS_X86_MAX_AGE_DAYS} day(s)" | |
| if [[ "${age_days}" -lt "${MACOS_X86_MAX_AGE_DAYS}" ]]; then | |
| should_build_macos_x86=false | |
| fi | |
| else | |
| echo "No macOS x86_64 archive published yet, building it" | |
| fi | |
| fi | |
| if "${should_release}" || "${should_update_docker}"; then | |
| echo -ne "Update Time: *$(date)*\nDoris Version: *${current_version}*\nStatus: *BUILDING*\nAttempts: *${attempt}*" >release_note.md | |
| else | |
| gh release view automation | sed -n '/--/,$p' | awk '{ if (NR > 1) print $0 }' | sed "{ | |
| s/Update Time:.*/Update Time: *$(date)*/ | |
| s/Doris Version:.*/Doris Version: *${current_version}*/ | |
| }" >release_note.md | |
| fi | |
| gh release edit -F release_note.md automation | |
| { | |
| echo "should_release=${should_release}" | |
| echo "should_update_docker=${should_update_docker}" | |
| echo "should_build_macos_x86=${should_build_macos_x86}" | |
| echo "thirdparty_commit_hash=${thirdparty_commit_hash}" | |
| echo "doris_version=${current_version}" | |
| echo "attempt=${attempt}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Download Source and Upload | |
| if: steps.check_diff.outputs.should_release == 'true' | |
| run: | | |
| cd thirdparty | |
| sed '/# unpacking thirdpart archives/,$d' download-thirdparty.sh | bash - | |
| tar -zcvf doris-thirdparty-source.tgz src | |
| # Drop the old asset first: `gh release upload --clobber` intermittently | |
| # exits 1 with "release not found" while deleting the asset it is | |
| # replacing, even though the upload itself went through. | |
| gh release delete-asset automation doris-thirdparty-source.tgz --yes || true | |
| gh release upload --clobber automation doris-thirdparty-source.tgz | |
| # One job per platform instead of one matrix job, so that `update-docker` can depend | |
| # on the Linux x86_64 archive alone. As a matrix it had to wait for all four: in run | |
| # 31988123966 the archive it consumes was published at 05:19 and the Docker job did | |
| # not start until 06:58, held up by macOS x86_64. | |
| build-linux-x86_64: | |
| name: Build | |
| needs: prerelease | |
| if: needs.prerelease.outputs.should_release == 'true' | |
| permissions: | |
| contents: write | |
| uses: ./.github/workflows/build-target.yml | |
| with: | |
| target: Linux | |
| runs_on: ubuntu-22.04 | |
| thirdparty_commit_hash: ${{ needs.prerelease.outputs.thirdparty_commit_hash }} | |
| build-linux-arm64: | |
| name: Build | |
| needs: prerelease | |
| if: needs.prerelease.outputs.should_release == 'true' | |
| permissions: | |
| contents: write | |
| uses: ./.github/workflows/build-target.yml | |
| with: | |
| target: Linux-arm64 | |
| runs_on: ubuntu-22.04-arm | |
| thirdparty_commit_hash: ${{ needs.prerelease.outputs.thirdparty_commit_hash }} | |
| build-macos-arm64: | |
| name: Build | |
| needs: prerelease | |
| if: needs.prerelease.outputs.should_release == 'true' | |
| permissions: | |
| contents: write | |
| uses: ./.github/workflows/build-target.yml | |
| with: | |
| target: macOS-arm64 | |
| runs_on: macos-14 | |
| thirdparty_commit_hash: ${{ needs.prerelease.outputs.thirdparty_commit_hash }} | |
| # Skipped while its published archive is still fresh enough - see the prerelease job. | |
| build-macos-x86_64: | |
| name: Build | |
| needs: prerelease | |
| if: needs.prerelease.outputs.should_build_macos_x86 == 'true' | |
| permissions: | |
| contents: write | |
| uses: ./.github/workflows/build-target.yml | |
| with: | |
| target: macOS-x86_64 | |
| runs_on: macos-15-intel | |
| tolerate_failure: true | |
| thirdparty_commit_hash: ${{ needs.prerelease.outputs.thirdparty_commit_hash }} | |
| update-docker: | |
| name: Update Docker Image | |
| needs: [prerelease, build-linux-x86_64] | |
| # Only the Linux x86_64 archive goes into the image, so nothing else is waited for. | |
| if: | | |
| always() && | |
| needs.build-linux-x86_64.result != 'cancelled' && | |
| needs.prerelease.outputs.should_update_docker == 'true' | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_REPO: ${{ github.repository }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - name: Verify Linux x86_64 prebuilt provenance | |
| run: | | |
| expected_commit="${{ needs.prerelease.outputs.thirdparty_commit_hash }}" | |
| if [[ -z "${expected_commit}" ]]; then | |
| echo 'Expected thirdparty commit hash is empty' >&2 | |
| exit 1 | |
| fi | |
| archive='doris-thirdparty-prebuilt-linux-x86_64.tar.xz' | |
| gh release download automation --pattern "${archive}" | |
| actual_commit="$(tar -xOf "${archive}" installed/_doris_thirdparty_commit_)" | |
| if [[ "${actual_commit}" != "${expected_commit}" ]]; then | |
| echo "Linux prebuilt provenance mismatch: expected ${expected_commit}, got ${actual_commit}" >&2 | |
| exit 1 | |
| fi | |
| echo "Verified Linux prebuilt thirdparty commit: ${actual_commit}" | |
| rm -f "${archive}" | |
| - name: Checkout docker/setup-buildx-action | |
| run: | | |
| rm -rf ./.github/actions/setup-buildx-action | |
| git clone https://github.com/docker/setup-buildx-action .github/actions/setup-buildx-action | |
| pushd .github/actions/setup-buildx-action &>/dev/null | |
| git checkout 8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| popd &>/dev/null | |
| - name: Checkout docker/login-action | |
| run: | | |
| rm -rf ./.github/actions/login-action | |
| git clone https://github.com/docker/login-action .github/actions/login-action | |
| pushd .github/actions/login-action &>/dev/null | |
| git checkout c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| popd &>/dev/null | |
| - name: Checkout docker/build-push-action | |
| run: | | |
| rm -rf ./.github/actions/build-push-action | |
| git clone https://github.com/docker/build-push-action .github/actions/build-push-action | |
| pushd .github/actions/build-push-action &>/dev/null | |
| git checkout ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 | |
| popd &>/dev/null | |
| - name: Set up Docker Buildx | |
| uses: ./.github/actions/setup-buildx-action | |
| - name: Login to Docker Hub | |
| uses: ./.github/actions/login-action | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Prepare Dockerfiles | |
| run: | | |
| # Bind the image to the exact Doris revision selected by prerelease. | |
| curl -fsSL "https://raw.githubusercontent.com/apache/doris/${{ needs.prerelease.outputs.doris_version }}/docker/compilation/Dockerfile" \ | |
| -o Dockerfile.upstream | |
| python3 - << 'PYEOF' | |
| import sys, re | |
| with open('Dockerfile.upstream') as f: | |
| content = f.read() | |
| # Patch 1: replace "clone & build thirdparty" block with downloading | |
| # our prebuilt artifact. The block starts with "# clone lastest source | |
| # code" comment and ends with "rm -rf ${DEFAULT_DIR}/doris". | |
| prebuilt_block = ( | |
| '# Download prebuilt thirdparty from GitHub Release (built by doris-thirdparty automation)\n' | |
| 'ARG GITHUB_REPOSITORY\n' | |
| 'RUN mkdir -p /var/local/thirdparty \\\n' | |
| ' && wget -q "https://github.com/${GITHUB_REPOSITORY}/releases/download/automation/doris-thirdparty-prebuilt-linux-x86_64.tar.xz" \\\n' | |
| ' -O /tmp/prebuilt.tar.xz \\\n' | |
| ' && tar -xf /tmp/prebuilt.tar.xz -C /var/local/thirdparty \\\n' | |
| ' && rm /tmp/prebuilt.tar.xz\n' | |
| ) | |
| patched_2 = re.sub( | |
| r'# clone lastest source code.*?rm -rf \$\{DEFAULT_DIR\}/doris\n', | |
| prebuilt_block, | |
| content, flags=re.DOTALL | |
| ) | |
| assert patched_2 != content, "Patch 1 was a no-op: 'clone lastest source code' block not found in upstream Dockerfile" | |
| patched = patched_2 | |
| # Write normal image Dockerfile | |
| with open('Dockerfile.patched', 'w') as f: | |
| f.write(patched) | |
| # Patch 2 (no-avx2): add USE_AVX2=0 to the ENV block in builder stage. | |
| # The ENV block ends with PATH=... just before "# install ccache". | |
| old_avx2 = 'PATH="/var/local/ldb-toolchain/bin/:$PATH"\n # USE_AVX2=0' | |
| new_avx2 = 'PATH="/var/local/ldb-toolchain/bin/:$PATH" \\\n USE_AVX2=0' | |
| assert old_avx2 in patched, "Patch 2 failed: '# USE_AVX2=0' comment not found; upstream Dockerfile may have changed" | |
| noavx2 = patched.replace(old_avx2, new_avx2) | |
| assert noavx2 != patched, "Patch 2 was a no-op: no-avx2 Dockerfile is identical to normal Dockerfile" | |
| with open('Dockerfile.patched-noavx2', 'w') as f: | |
| f.write(noavx2) | |
| print("=== normal: check ===") | |
| for line in open('Dockerfile.patched'): | |
| if any(k in line for k in ['COPY doris', 'build-thirdparty', 'ARG GITHUB', 'prebuilt', 'USE_AVX2']): | |
| print(line, end='') | |
| print("=== noavx2: check ===") | |
| for line in open('Dockerfile.patched-noavx2'): | |
| if any(k in line for k in ['COPY doris', 'build-thirdparty', 'ARG GITHUB', 'prebuilt', 'USE_AVX2']): | |
| print(line, end='') | |
| PYEOF | |
| - name: Build and Push Docker Image (normal) | |
| uses: ./.github/actions/build-push-action | |
| with: | |
| context: . | |
| file: Dockerfile.patched | |
| build-args: | | |
| GITHUB_REPOSITORY=${{ github.repository }} | |
| push: true | |
| tags: ${{ vars.DOCKER_TAGS || 'apache/doris:build-env-ldb-toolchain-latest' }} | |
| platforms: linux/amd64 | |
| - name: Build and Push Docker Image (no-avx2) | |
| uses: ./.github/actions/build-push-action | |
| with: | |
| context: . | |
| file: Dockerfile.patched-noavx2 | |
| build-args: | | |
| GITHUB_REPOSITORY=${{ github.repository }} | |
| push: true | |
| tags: ${{ vars.DOCKER_TAGS_NOAVX2 || 'apache/doris:build-env-ldb-toolchain-no-avx2-latest' }} | |
| platforms: linux/amd64 | |
| success: | |
| name: Success | |
| needs: [prerelease, build-linux-x86_64, build-linux-arm64, build-macos-arm64, build-macos-x86_64, update-docker] | |
| # `!cancelled() && !failure()` rather than the default: build-macos-x86_64 is | |
| # skipped on most runs, and a skipped dependency would otherwise skip this job and | |
| # leave the release note stuck at BUILDING forever. | |
| if: | | |
| !cancelled() && !failure() && | |
| (needs.prerelease.outputs.should_release == 'true' || needs.prerelease.outputs.should_update_docker == 'true') | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_REPO: ${{ github.repository }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DORIS_VERSION: ${{ needs.prerelease.outputs.doris_version }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Update Checksums | |
| run: | | |
| gh release download automation | |
| # Write the version this run actually built rather than parsing it back | |
| # out of the note, so the terminal state never depends on the note | |
| # having survived intact. | |
| echo -ne "Update Time: *$(date)*\nDoris Version: *${DORIS_VERSION}*\nStatus: *SUCCESS*\n\n## SHA256 Checksums\n\`\`\`\n$(sha256sum *)\n\`\`\`" >release_note.md | |
| gh release edit --latest -F release_note.md automation | |
| failure: | |
| name: Failure | |
| needs: [prerelease, build-linux-x86_64, build-linux-arm64, build-macos-arm64, build-macos-x86_64, update-docker] | |
| # Only report on a build that actually ran. If prerelease itself failed there | |
| # is no version to record, and writing an empty one is exactly what used to | |
| # make the next scheduled run rebuild from scratch, forever. | |
| if: always() && failure() && needs.prerelease.result == 'success' | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_REPO: ${{ github.repository }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DORIS_VERSION: ${{ needs.prerelease.outputs.doris_version }} | |
| ATTEMPT: ${{ needs.prerelease.outputs.attempt }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Update Checksums | |
| run: | | |
| gh release download automation | |
| # Keep Doris Version: the prerelease job keys off it, and dropping it | |
| # made every scheduled run look like a first release. #405 guarded this | |
| # by reading the line back out of the note and bailing out when it was | |
| # missing, which protects an intact note but cannot repair one that is | |
| # already broken -- and a note that has lost the line is exactly the | |
| # state that keeps the rebuild loop running. Take the value from the | |
| # prerelease job instead, so it is always available. | |
| echo -ne "Update Time: *$(date)*\nDoris Version: *${DORIS_VERSION}*\nStatus: *FAILURE*\nAttempts: *${ATTEMPT}*\n\n## SHA256 Checksums\n\`\`\`\n$(sha256sum *)\n\`\`\`" >release_note.md | |
| gh release edit --latest -F release_note.md automation |