The shellcheck step added a few hours ago reads `git ls-files '*.sh'`. That is four files. It does not read the inline `run:` blocks, and a good deal of this repo's bash lives there: the release verification in build.yml, the emulator setup and teardown in status_check.yml and api37-debug.yml. "shellcheck runs in CI" was true of the files and not of the blocks, and CLAUDE.md said so rather than pretending otherwise. actionlint closes that half. It parses each workflow and runs shellcheck over every `run:`, on top of its own checks for expression syntax, `needs:` references, matrix keys and action input names. Pinned by digest, for the reason shellcheck is pinned -- a new rule making untouched files fail is a red build whose diff cannot explain it -- and for a second reason of its own. actionlint's documented install is bash <(curl -s https://raw.githubusercontent.com/.../download-actionlint.bash) off a moving branch. Running that in a repository that pins every action by SHA would contradict its own supply-chain posture more than the linter is worth. That is why #70 was filed instead of bolted onto the shellcheck commit. It reported exactly one finding, and it is fixed here rather than suppressed: build.yml parsed `ls` to pick the release APK (SC2012). The glob was already in the line, so a bash array reads it without the pipe. Gradle's output names have no spaces today, which is the kind of assumption that holds right up until it does not. Proved it catches something, rather than trusting a green run: planting `if [ $UNQUOTED = bad ]` into a build.yml `run:` block produces shellcheck reported issue in this script: SC2086:info:4:6: Removed again afterwards. A linter that cannot be shown to catch a plant is not wired in, it is just running -- and SC2086 in a `run:` block is invisible to the .sh-file step, which is the whole argument for this commit. CLAUDE.md loses the "does not cover inline run: blocks" caveat, because it no longer does. Both linters verified clean at their pinned digests. Closes #70.
135 lines
5.8 KiB
YAML
135 lines
5.8 KiB
YAML
name: Build
|
|
|
|
# Pull requests are covered by status_check.yml, which runs the unit tests and the
|
|
# instrumented suite across API 33-37. This workflow keeps the post-merge and release
|
|
# duties and does not duplicate PR validation.
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
tags: ['v*']
|
|
|
|
# Actions are pinned to a commit rather than a tag, with the release in a trailing
|
|
# comment. A tag is mutable -- the owner can repoint it at new code -- so a tag
|
|
# reference amounts to running whatever that repository contains tomorrow. This matters
|
|
# more here than on pull requests: these jobs sign nothing today, but they do publish
|
|
# the artifacts people install.
|
|
env:
|
|
GRADLE_CACHE_PATHS: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
|
|
jobs:
|
|
test:
|
|
name: Unit tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: '25' # Matches the daemon JVM pinned in gradle/gradle-daemon-jvm.properties
|
|
|
|
# Gradle runs through the committed wrapper rather than a setup action. The
|
|
# wrapper verifies its own distribution against distributionSha256Sum, and
|
|
# caching is a handful of lines, so the action earned little here.
|
|
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ env.GRADLE_CACHE_PATHS }}
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
- name: Unit tests
|
|
run: ./gradlew :app:testDebugUnitTest
|
|
|
|
- name: Upload test report
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: unit-test-report
|
|
path: app/build/reports/tests/
|
|
|
|
release:
|
|
name: Release
|
|
needs: [test]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
permissions:
|
|
# Needed to create the release. Declared explicitly rather than relying on the
|
|
# repository default, so the token's reach is visible here.
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: '25'
|
|
|
|
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ env.GRADLE_CACHE_PATHS }}
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
# No -PabiFilters here: released artifacts must carry every ABI. That override
|
|
# exists only so emulator jobs skip libraries they cannot execute.
|
|
- name: Build release artifacts
|
|
run: ./gradlew :app:assembleRelease :app:bundleRelease
|
|
|
|
- name: Verify the released artifacts
|
|
run: |
|
|
# A glob, not `ls | head`: the glob is already here, and parsing ls is what
|
|
# SC2012 is about. Gradle's names have no spaces today, which is exactly the
|
|
# kind of assumption that holds until it does not.
|
|
apks=(app/build/outputs/apk/release/*.apk)
|
|
APK="${apks[0]}"
|
|
# A release that shipped one ABI, or lost 16 KB alignment, would install
|
|
# fine on a test device and fail for users or at Play submission. Both are
|
|
# cheap to check and expensive to discover later.
|
|
for abi in arm64-v8a x86_64; do
|
|
n=$(unzip -l "$APK" | grep -c "lib/$abi/.*\.so$" || true)
|
|
echo " $abi: $n shared libraries"
|
|
test "$n" -gt 0 || { echo "::error::release APK is missing $abi"; exit 1; }
|
|
done
|
|
unzip -q -o "$APK" 'lib/*' -d /tmp/relcheck
|
|
bad=0
|
|
for f in /tmp/relcheck/lib/*/*.so; do
|
|
align=$(readelf -lW "$f" | awk '$1=="LOAD"{print $NF}' | sort -u)
|
|
[ "$align" = "0x4000" ] || { echo "::error::$(basename "$f") is $align"; bad=1; }
|
|
done
|
|
test "$bad" -eq 0 || exit 1
|
|
echo " all libraries are 16 KB aligned"
|
|
|
|
# GPL-3.0 requires that complete corresponding source accompany the binary.
|
|
# FFmpeg's guidance says to host it on the same server as the binary; for a Play
|
|
# listing that is impossible, so it is attached to the GitHub release next to the
|
|
# APK and linked from both the store listing and the in-app About screen.
|
|
- name: Assemble corresponding source
|
|
run: |
|
|
mkdir -p release-source
|
|
cp -r tools/ffmpeg release-source/
|
|
cp bin/README.md release-source/PREBUILT.md
|
|
{
|
|
echo "FFmpeg corresponding source for ${GITHUB_REF_NAME}"
|
|
echo
|
|
echo "Upstream: https://github.com/arthenica/ffmpeg-kit-next"
|
|
echo "Tag: v8.1.1 (FFmpeg 8.1.2)"
|
|
echo
|
|
echo "tools/ffmpeg reproduces the binary shipped in this release."
|
|
echo "PREBUILT.md records its provenance, including the SHA-256 and the"
|
|
echo "configure line read back out of the shipped libavutil."
|
|
} > release-source/README.txt
|
|
tar czf ffmpeg-corresponding-source.tar.gz release-source
|
|
|
|
- uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
|
with:
|
|
files: |
|
|
app/build/outputs/apk/release/*.apk
|
|
app/build/outputs/bundle/release/*.aab
|
|
ffmpeg-corresponding-source.tar.gz
|
|
LICENSE
|
|
LICENSES/README.md
|