blob: 71c177f94b45cd97f61cf52e8a36ae7b095b71ff [file] [edit]
# Builds the prebuilt libraries that hook/build.dart downloads in the `fetch`
# build mode, attaches them to a `boring-binaries-<date>-<commit>` GitHub
# pre-release, and opens a pull request that pins their SHA-256 hashes in
# lib/src/hook_helpers/hashes.dart.
#
# Runs when a change to the native sources (src/, the vendored BoringSSL, or
# this pipeline) lands on main, for example the weekly BoringSSL update, and on
# `workflow_dispatch` on main. The `analyze` job of boring.yaml checks that the
# pinned libraries were built from the current native sources, so a release of
# package:boring can't ship stale ones: merge the pin pull request first.
#
# Pull requests that change the pipeline or the native build only run the
# `precompile` job, as does `workflow_dispatch` on other branches, and upload
# workflow artifacts without creating a release.
name: package:boring binaries
permissions:
contents: read
on:
push:
branches: [ main ]
paths:
- '.github/workflows/boring_binaries.yaml'
- 'pkgs/boring/src/**'
- 'pkgs/boring/third_party/boringssl/**'
- 'pkgs/boring/tool/REVISION'
- 'pkgs/boring/tool/precompile_binaries.dart'
pull_request:
branches: [ main ]
paths:
- '.github/workflows/boring_binaries.yaml'
- 'pkgs/boring/src/**'
- 'pkgs/boring/tool/precompile_binaries.dart'
workflow_dispatch:
# Releases are named after the commit, so builds of the same ref run one after
# the other instead of racing for the release.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
precompile:
name: Precompile (${{ matrix.target-os }}-${{ matrix.target-arch }}${{ matrix.ios-sdk && format('-{0}', matrix.ios-sdk) || '' }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write # To sign the provenance attestations of the libraries.
attestations: write # To store the attestations with the repository.
defaults:
run:
working-directory: pkgs/boring/
shell: bash
strategy:
fail-fast: false
matrix:
# Keep in sync with `prebuiltTargets` in lib/src/hook_helpers/targets.dart.
# `max-glibc` and `min-os` are the ABI floors that the libraries are
# checked against below. Keep `min-os` in sync with
# `macOSDeploymentTargets` and `iOSDeploymentTarget` in
# tool/precompile_binaries.dart.
include:
# The oldest available Ubuntu, so the libraries only require glibc
# 2.35.
- os: ubuntu-22.04
target-os: linux
target-arch: x64
max-glibc: '2.35'
- os: ubuntu-22.04-arm
target-os: linux
target-arch: arm64
max-glibc: '2.35'
- os: macos-14
target-os: macos
target-arch: arm64
min-os: '11.0'
- os: macos-14
target-os: macos
target-arch: x64
min-os: '10.15'
- os: windows-latest
target-os: windows
target-arch: x64
- os: macos-14
target-os: ios
target-arch: arm64
ios-sdk: iphoneos
min-os: '12.0'
- os: macos-14
target-os: ios
target-arch: arm64
ios-sdk: iphonesimulator
# The arm64 simulator only exists since iOS 14.
min-os: '14.0'
- os: macos-14
target-os: ios
target-arch: x64
ios-sdk: iphonesimulator
min-os: '12.0'
# The Android libraries don't depend on the host, so any Ubuntu does.
- os: ubuntu-24.04
target-os: android
target-arch: arm
- os: ubuntu-24.04
target-os: android
target-arch: arm64
- os: ubuntu-24.04
target-os: android
target-arch: x64
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
with:
sdk: stable
- name: Setup NASM
if: matrix.target-os == 'windows'
uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b
- name: Install dependencies
run: dart pub get
# Builds the dynamic library, which is bundled when linking is disabled,
# and the static library, which hook/link.dart links into a dynamic
# library with only the functions an application uses.
- name: Precompile the libraries
env:
TARGET_OS: ${{ matrix.target-os }}
TARGET_ARCH: ${{ matrix.target-arch }}
IOS_SDK: ${{ matrix.ios-sdk }}
run: |
dart run tool/precompile_binaries.dart --target-os="$TARGET_OS" --target-arch="$TARGET_ARCH" \
${IOS_SDK:+--ios-sdk="$IOS_SDK"} \
${ANDROID_NDK_LATEST_HOME:+--android-ndk="$ANDROID_NDK_LATEST_HOME"}
- name: List the libraries
run: ls -l bin
# The dynamic library is bundled as is, so it must run on every OS version
# that Dart and Flutter support, not only on the build machine's.
- name: Check the ABI floor of the dynamic library (Linux)
if: matrix.target-os == 'linux'
env:
MAX_GLIBC: ${{ matrix.max-glibc }}
run: |
glibc="$(objdump -T bin/*.so | grep -oE 'GLIBC_[0-9.]+' | sed 's/GLIBC_//' | sort -Vu | tail -1)"
echo "Newest glibc symbol version: $glibc (allowed: $MAX_GLIBC)"
if [ "$(printf '%s\n%s\n' "$glibc" "$MAX_GLIBC" | sort -V | tail -1)" != "$MAX_GLIBC" ]; then
echo "::error::The library requires glibc $glibc, newer than $MAX_GLIBC. Build on an older runner."
exit 1
fi
- name: Check the ABI floor of the dynamic library (macOS, iOS)
if: matrix.target-os == 'macos' || matrix.target-os == 'ios'
env:
MIN_OS: ${{ matrix.min-os }}
TARGET_OS: ${{ matrix.target-os }}
run: |
minos="$(otool -l bin/*.dylib | awk '/minos/ {print $2}' | sort -u)"
echo "Minimum $TARGET_OS version: $minos (expected: $MIN_OS)"
if [ "$minos" != "$MIN_OS" ]; then
echo "::error::The library requires $TARGET_OS '$minos' instead of $MIN_OS. Check the deployment targets in tool/precompile_binaries.dart."
exit 1
fi
- name: Check the ABI of the dynamic library (Android)
if: matrix.target-os == 'android'
env:
TARGET_ARCH: ${{ matrix.target-arch }}
run: |
# Android 15+ requires 16 KB page alignment of 64-bit native
# libraries, and the libraries must only depend on the C library
# (see src/CMakeLists.txt), not on libc++_shared.so.
case "$TARGET_ARCH" in
arm|ia32) expected_alignment=0x1000 ;;
*) expected_alignment=0x4000 ;;
esac
alignments="$(readelf -lW bin/*.so | awk '$1 == "LOAD" {print $NF}' | sort -u)"
needed="$(readelf -d bin/*.so | awk '/NEEDED/ {print $NF}' | tr -d '[]' | sort -u | tr '\n' ' ')"
echo "LOAD segment alignment: $alignments (expected: $expected_alignment)"
echo "Needed libraries: $needed"
readelf -n bin/*.so | grep -A2 'Android' || true
if [ "$alignments" != "$expected_alignment" ]; then
echo "::error::The LOAD segments are aligned to $alignments instead of $expected_alignment. Use NDK r28 or newer, or ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES."
exit 1
fi
case " $needed" in
*" libc++"*|*" libstdc++"*)
echo "::error::The library depends on a C++ runtime: $needed. See the C++ runtime notes in src/CMakeLists.txt."
exit 1
;;
esac
- name: Check the ABI floor of the dynamic library (Windows)
if: matrix.target-os == 'windows'
run: |
# The C runtime is linked statically (see src/CMakeLists.txt), so the
# DLL must not import the Visual C++ Redistributable's DLLs.
imports="$(grep -a -o -i -E '(vcruntime|msvcp|msvcr)[0-9_]*\.dll|api-ms-win-crt-[a-z0-9-]*\.dll' bin/*.dll | sort -u || true)"
if [ -n "$imports" ]; then
echo "$imports"
echo "::error::The DLL imports the C runtime dynamically. Check CMAKE_MSVC_RUNTIME_LIBRARY in src/CMakeLists.txt."
exit 1
fi
echo "The DLL doesn't import the C runtime dynamically."
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: binaries-${{ matrix.target-os }}-${{ matrix.target-arch }}${{ matrix.ios-sdk && format('-{0}', matrix.ios-sdk) || '' }}
path: pkgs/boring/bin/*
if-no-files-found: error
# Verify with `gh attestation verify <file> --repo dart-lang/tools`.
- name: Attest the provenance of the release libraries
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8
with:
subject-path: pkgs/boring/bin/*
release:
name: Attach the binaries to a GitHub release
needs: precompile
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write # To create the release and upload its assets.
outputs:
tag: ${{ steps.release.outputs.tag }}
id: ${{ steps.release.outputs.id }}
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
path: release-assets
merge-multiple: true
- name: Create the GitHub release
id: release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
COMMIT: ${{ github.sha }}
run: |
id="$(date -u +%Y%m%d)-${COMMIT::12}"
tag="boring-binaries-$id"
echo "id=$id" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
if ! gh release view "$tag" > /dev/null 2>&1; then
gh release create "$tag" --prerelease --target "$COMMIT" \
--title "package:boring binaries $tag" \
--notes "The prebuilt BoringSSL libraries for package:boring, built from $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$COMMIT by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID. Verify a library with \`gh attestation verify <file> --repo $GITHUB_REPOSITORY\`."
fi
# lib/src/hook_helpers/hashes.dart pins the hashes of the assets, so
# never replace them. Delete a failed release to rebuild a commit.
if [ "$(gh release view "$tag" --json assets --jq '.assets | length')" -ne 0 ]; then
echo "::error::Release $tag already has assets. Delete the release to upload new ones."
exit 1
fi
gh release upload "$tag" release-assets/*
echo "Created $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$tag"
# Pins the hashes of the release in hashes.dart, like the refresh job of
# boring.yaml updates BoringSSL. The Dart Bot token is needed so that the
# PR's CI runs: its `generated` job re-hashes the release assets, and the
# tree-shaking tests download them in the `fetch` build mode.
pin:
name: Open the pull request that pins the binaries
needs: release
if: github.repository_owner == 'dart-lang'
runs-on: ubuntu-latest
permissions:
contents: write # To push the branch of the pull request.
pull-requests: write # To open the pull request.
steps:
- name: Generate Dart Bot token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with:
client-id: ${{ vars.ECOSYSTEM_BOT_CLIENT_ID }}
private-key: ${{ secrets.ECOSYSTEM_BOT_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: main
token: ${{ steps.generate-token.outputs.token }}
persist-credentials: false
- uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1
with:
sdk: stable
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
path: release-assets
merge-multiple: true
- name: Install dependencies
working-directory: pkgs/boring/
run: dart pub get
- name: Pin the hashes
working-directory: pkgs/boring/
env:
TAG: ${{ needs.release.outputs.tag }}
COMMIT: ${{ github.sha }}
GH_REPO: ${{ github.repository }}
run: dart run tool/regenerate_hashes.dart --repository "$GH_REPO" --source-commit "$COMMIT" --assets "$GITHUB_WORKSPACE/release-assets" "$TAG"
- name: Create pull request
uses: peter-evans/create-pull-request@653ba8b281fa5e1ee78ec20dda96cf9a79102c04
with:
token: ${{ steps.generate-token.outputs.token }}
committer: dart-ecosystem-bot <dart-ecosystem-bot@google.com>
author: dart-ecosystem-bot <dart-ecosystem-bot@google.com>
base: main
add-paths: pkgs/boring/lib/src/hook_helpers/hashes.dart
commit-message: "[boring] Pin the prebuilt binaries of ${{ needs.release.outputs.tag }}"
branch: "boring-pin-binaries-${{ needs.release.outputs.id }}"
title: "[boring] Pin the prebuilt binaries of ${{ needs.release.outputs.tag }}"
body: >-
Pins the SHA-256 hashes of the prebuilt libraries of
[`${{ needs.release.outputs.tag }}`](${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.release.outputs.tag }}),
built from ${{ github.sha }} by
[this run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
of `boring_binaries.yaml`.
The `generated` job re-hashes the release assets, and the
tree-shaking tests download them in the `fetch` build mode.
labels: skip-changelog-check
delete-branch: true