mirror of
https://github.com/gmeligio/flutter-docker-image.git
synced 2026-05-24 12:30:34 +00:00
f81107fe7b
- Pin `pnpm = "11.2.2"` in `mise.toml` so the `docs/src` build uses a manifest-pinned package manager, bringing the docs toolchain under the `ci-runtime-tool-versioning` invariant alongside `cue`, `node`, `gx`, and `git-cliff`.
381 lines
16 KiB
YAML
381 lines
16 KiB
YAML
on:
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
# Read-only permissions by default
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Resolves the bootstrap container tag — the latest published release tag.
|
|
# Reading from the releases API (instead of vars.FLUTTER_VERSION) makes
|
|
# fork PRs work, and reading from releases (instead of main's version.json)
|
|
# avoids the window between merging a version-bump PR and publishing its
|
|
# image during which main's version.json points to a tag that doesn't
|
|
# exist in GHCR yet.
|
|
setup:
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
flutter_version: ${{ steps.read.outputs.version }}
|
|
steps:
|
|
- name: Read latest release tag
|
|
id: read
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
version=$(gh api "repos/${{ github.repository }}/releases/latest" --jq '.tag_name')
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
build_image:
|
|
permissions:
|
|
packages: write
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
# image_ref is set per the p2 contract but GitHub Actions suppresses
|
|
# any job output whose value contains a registered secret. When
|
|
# github.repository_owner equals DOCKER_HUB_USERNAME (as on this repo),
|
|
# image_ref is masked-and-dropped. Consumers MUST use image_tag and
|
|
# reconstruct ghcr.io/${{ github.repository_owner }}/flutter-android:<tag>.
|
|
image_ref: ${{ steps.handoff.outputs.is_fork != 'true' && format('ghcr.io/{0}/flutter-android:{1}', github.repository_owner, steps.handoff.outputs.tag) || '' }}
|
|
image_tag: ${{ steps.handoff.outputs.is_fork != 'true' && steps.handoff.outputs.tag || '' }}
|
|
image_artifact: ${{ steps.handoff.outputs.is_fork == 'true' && format('image-{0}', github.run_id) || '' }}
|
|
image_local_tag: ${{ steps.local_tag.outputs.ref }}
|
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
|
env:
|
|
IMAGE_REPOSITORY_NAME: flutter-android
|
|
VERSION_MANIFEST: config/version.json
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Read environment variables from the version manifest
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
env:
|
|
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
|
|
IMAGE_REPOSITORY_NAME: ${{ env.IMAGE_REPOSITORY_NAME }}
|
|
VERSION_MANIFEST: ${{ env.VERSION_MANIFEST }}
|
|
with:
|
|
script: |
|
|
const script = require('./script/setEnvironmentVariables.js')
|
|
return await script({ core })
|
|
|
|
- name: Clean runner disk
|
|
uses: ./.github/actions/clean-runner-disk
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
|
|
|
# Secrets are not available to PRs from forks, so skip the Docker Hub
|
|
# login for those runs. The build still works against public base images.
|
|
- name: Login to Docker Hub
|
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
|
|
- name: Login to GHCR
|
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Compute handoff tag
|
|
id: handoff
|
|
env:
|
|
IS_PR: ${{ github.event_name == 'pull_request' }}
|
|
IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
REF_NAME: ${{ github.ref_name }}
|
|
run: |
|
|
echo "is_fork=$IS_FORK" >> "$GITHUB_OUTPUT"
|
|
if [[ "$IS_PR" == "true" ]]; then
|
|
echo "tag=pr-$PR_NUMBER" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "tag=branch-${REF_NAME//\//-}" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Load image metadata
|
|
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
|
id: metadata
|
|
with:
|
|
images: |
|
|
${{ env.IMAGE_REPOSITORY_PATH }}
|
|
tags: |
|
|
type=raw,value=${{ env.FLUTTER_VERSION }}
|
|
|
|
# outputs is just `type=docker` (load into local daemon). The GHCR
|
|
# handoff push is done by an explicit `docker push` step below — when
|
|
# combined with `type=docker`, buildkit silently ignores a `type=registry`
|
|
# output and pushes only via --tag (which points at docker.io, not GHCR).
|
|
- name: Build image and push to local Docker daemon
|
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
|
with:
|
|
file: android.Dockerfile
|
|
outputs: type=docker
|
|
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/flutter-android:buildcache
|
|
cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref=ghcr.io/{0}/flutter-android:buildcache,mode=max', github.repository_owner) || '' }}
|
|
labels: ${{ steps.metadata.outputs.labels }}
|
|
tags: ${{ steps.metadata.outputs.tags }}
|
|
target: android
|
|
build-args: |
|
|
flutter_version=${{ env.FLUTTER_VERSION }}
|
|
fastlane_version=${{ env.FASTLANE_VERSION }}
|
|
android_build_tools_version=${{ env.ANDROID_BUILD_TOOLS_VERSION }}
|
|
android_platform_versions=${{ env.ANDROID_PLATFORM_VERSIONS }}
|
|
android_ndk_version=${{ env.ANDROID_NDK_VERSION }}
|
|
cmake_version=${{ env.CMAKE_VERSION }}
|
|
|
|
# Re-tag the loaded image to a name that does NOT contain the owner.
|
|
# `image_local_tag` is exposed as a job output; GitHub Actions drops
|
|
# outputs whose value contains a registered secret (DOCKER_HUB_USERNAME
|
|
# == github.repository_owner on this repo), so the metadata tag
|
|
# `<owner>/flutter-android:<version>` cannot be passed through.
|
|
- name: Re-tag image for local handoff
|
|
id: local_tag
|
|
run: |
|
|
DEST="flutter-android:${{ env.FLUTTER_VERSION }}"
|
|
docker tag "${{ fromJSON(steps.metadata.outputs.json).tags[0] }}" "$DEST"
|
|
echo "ref=$DEST" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Push image to GHCR
|
|
if: steps.handoff.outputs.is_fork != 'true'
|
|
run: |
|
|
GHCR_REF="ghcr.io/${{ github.repository_owner }}/flutter-android:${{ steps.handoff.outputs.tag }}"
|
|
docker tag "${{ fromJSON(steps.metadata.outputs.json).tags[0] }}" "$GHCR_REF"
|
|
docker push "$GHCR_REF"
|
|
|
|
- name: Save image as artifact
|
|
if: steps.handoff.outputs.is_fork == 'true'
|
|
run: docker save "${{ steps.local_tag.outputs.ref }}" | gzip > image.tar.gz
|
|
|
|
- name: Upload image artifact
|
|
if: steps.handoff.outputs.is_fork == 'true'
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: image-${{ github.run_id }}
|
|
path: image.tar.gz
|
|
retention-days: 1
|
|
compression-level: 0
|
|
|
|
test_image:
|
|
needs: build_image
|
|
runs-on: ubuntu-24.04
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Clean runner disk
|
|
if: needs.build_image.outputs.image_artifact != ''
|
|
uses: ./.github/actions/clean-runner-disk
|
|
|
|
- name: Download image artifact
|
|
if: needs.build_image.outputs.image_artifact != ''
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
with:
|
|
name: ${{ needs.build_image.outputs.image_artifact }}
|
|
|
|
- name: Load image from artifact
|
|
if: needs.build_image.outputs.image_artifact != ''
|
|
run: gunzip -c image.tar.gz | docker load
|
|
|
|
- name: Login to GHCR
|
|
if: needs.build_image.outputs.image_artifact == ''
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Pull image from registry
|
|
if: needs.build_image.outputs.image_artifact == ''
|
|
run: docker pull "ghcr.io/${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.image_tag }}"
|
|
|
|
- name: Test image
|
|
uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0
|
|
with:
|
|
image: ${{ needs.build_image.outputs.image_artifact != '' && needs.build_image.outputs.image_local_tag || format('ghcr.io/{0}/flutter-android:{1}', github.repository_owner, needs.build_image.outputs.image_tag) }}
|
|
config: test/android.yml
|
|
|
|
scan_image:
|
|
needs: build_image
|
|
runs-on: ubuntu-24.04
|
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
|
permissions:
|
|
packages: read
|
|
pull-requests: write
|
|
security-events: write
|
|
steps:
|
|
- name: Download image artifact
|
|
if: needs.build_image.outputs.image_artifact != ''
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
with:
|
|
name: ${{ needs.build_image.outputs.image_artifact }}
|
|
|
|
- name: Load image from artifact
|
|
if: needs.build_image.outputs.image_artifact != ''
|
|
run: gunzip -c image.tar.gz | docker load
|
|
|
|
# Docker Hub login is required by Scout — it authenticates against the
|
|
# Docker Hub identity tied to the org secret, not the GHCR identity.
|
|
- name: Login to Docker Hub
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
|
|
- name: Login to GHCR
|
|
if: needs.build_image.outputs.image_artifact == ''
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Pull the PR-tagged GHCR image and re-tag it with the Docker Hub repo
|
|
# identity (<owner>/flutter-android:<version>). Scout's `compare` looks
|
|
# up the image's repo in its environment records — those records exist
|
|
# for the Docker Hub repo, not for `ghcr.io/<owner>/flutter-android`.
|
|
# Without this re-tag, Scout fails with "not in stream environment:prod".
|
|
- name: Pull image and re-tag for Scout
|
|
if: needs.build_image.outputs.image_artifact == ''
|
|
run: |
|
|
GHCR_REF="ghcr.io/${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.image_tag }}"
|
|
SCOUT_REF="${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.flutter_version }}"
|
|
docker pull "$GHCR_REF"
|
|
docker tag "$GHCR_REF" "$SCOUT_REF"
|
|
|
|
- name: Scan with Docker Scout
|
|
id: docker-scout
|
|
uses: docker/scout-action@bacf462e8d090c09660de30a6ccc718035f961e3 # v1.20.4
|
|
with:
|
|
command: compare, recommendations
|
|
image: local://${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.flutter_version }}
|
|
github-token: ${{ github.token }}
|
|
only-fixed: true
|
|
organization: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
to-env: prod
|
|
|
|
validate_version_files:
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Setup mise tools
|
|
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
|
|
|
- name: Validate version.json and flutter_version.json with CUE
|
|
run: |
|
|
cue vet config/schema.cue -d '#FlutterVersion' config/flutter_version.json
|
|
cue vet config/schema.cue -d '#Version' config/version.json
|
|
|
|
validate_generated_config:
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Setup mise tools
|
|
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
|
|
|
- name: Generate test files with CUE
|
|
run: |
|
|
./script/update_test.sh
|
|
|
|
- name: Check if there are any changes in the git working tree
|
|
run: |
|
|
git add -A
|
|
git diff --exit-code HEAD
|
|
|
|
build_docs:
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Setup mise tools
|
|
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
|
|
|
- name: Build documentation
|
|
working-directory: docs/src
|
|
run: |
|
|
pnpm install --frozen-lockfile
|
|
pnpm run build
|
|
|
|
# Upload generated docs so reviewers can inspect them. Commit-back is
|
|
# handled by update_docs.yml on push to main — pushing to fork PR
|
|
# branches is not possible with base-repo credentials.
|
|
- name: Upload built docs
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: docs-${{ github.event.pull_request.number || github.sha }}
|
|
path: |
|
|
readme.md
|
|
LICENSE.md
|
|
docs/contributing.md
|
|
docs/windows.md
|
|
retention-days: 14
|
|
|
|
test_gradle:
|
|
needs: setup
|
|
permissions:
|
|
# Allow to read packages to pull the container image from GitHub Container Registry
|
|
packages: read
|
|
runs-on: ubuntu-24.04
|
|
container:
|
|
image: ghcr.io/${{ github.repository_owner }}/flutter-android:${{ needs.setup.outputs.flutter_version }}
|
|
credentials:
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
|
|
- name: Read version.json
|
|
id: version-json
|
|
run: |
|
|
{
|
|
echo "content<<EOF"
|
|
cat ./config/version.json
|
|
echo "EOF"
|
|
} >> $GITHUB_OUTPUT
|
|
|
|
- name: Set environment variables from version.json
|
|
run: |
|
|
echo "FLUTTER_VERSION=${{ fromJson( steps.version-json.outputs.content ).flutter.version }}" >> $GITHUB_ENV
|
|
echo "FLUTTER_CHANNEL=${{ fromJson( steps.version-json.outputs.content ).flutter.channel }}" >> $GITHUB_ENV
|
|
|
|
- name: Setup Flutter
|
|
run: |
|
|
cd $FLUTTER_ROOT
|
|
git fetch origin ${{ env.FLUTTER_VERSION }}:${{ env.FLUTTER_VERSION }}
|
|
git switch --discard-changes ${{ env.FLUTTER_VERSION }}
|
|
|
|
- name: Create test application
|
|
run: |
|
|
flutter create test_app
|
|
|
|
- name: Update default Android platform versions in Flutter
|
|
working-directory: test_app/android
|
|
env:
|
|
BUILD_TOOLS_VERSION: ${{ fromJson(steps.version-json.outputs.content).android.buildTools.version }}
|
|
run: |
|
|
cat ../../script/updateAndroidVersions.gradle.kts >> app/build.gradle.kts
|
|
./gradlew --warning-mode all updateAndroidVersions
|
|
|
|
- name: Setup mise tools
|
|
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
|
|
|
- name: Validate version.json with CUE
|
|
run: cue vet config/schema.cue -d '#Version' config/version.json
|