build: prepare locked native sources for F-Droid #95
Workflow file for this run
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
| name: PR Check | |
| on: | |
| pull_request: | |
| branches: [main] | |
| types: [opened, synchronize, reopened, edited, ready_for_review] | |
| concurrency: | |
| group: pr-check-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| PYTHONDONTWRITEBYTECODE: '1' | |
| JAVA_VERSION: '21' | |
| NODE_VERSION: '22' | |
| ANDROID_BUILD_TOOLS_VERSION: '35.0.0' | |
| ANDROID_NDK_VERSION: '25.1.8937393' | |
| ANDROID_CMAKE_VERSION: '3.22.1' | |
| MANUAL_DEPS_DIR: manual-deps | |
| jobs: | |
| candidate: | |
| name: Candidate checks | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 240 | |
| steps: | |
| - name: Checkout merge candidate | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| ref: ${{ github.sha }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| submodules: false | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Plan candidate checks | |
| id: plan | |
| env: | |
| CANDIDATE_SHA: ${{ github.sha }} | |
| EXPECTED_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| python3 ci/script/pr_check.py plan \ | |
| --candidate "$CANDIDATE_SHA" \ | |
| --expected-base "$EXPECTED_BASE_SHA" \ | |
| --expected-head "$EXPECTED_HEAD_SHA" \ | |
| --github-output "$GITHUB_OUTPUT" \ | |
| --step-summary "$GITHUB_STEP_SUMMARY" | |
| - name: Check gate implementation | |
| id: gate-tests | |
| continue-on-error: true | |
| run: python3 -B -m unittest discover -s ci/test -p 'test_*.py' | |
| - name: Check repository hygiene | |
| id: hygiene | |
| continue-on-error: true | |
| run: | | |
| python3 ci/script/check_repo_hygiene.py \ | |
| --base "${{ steps.plan.outputs.base_sha }}" \ | |
| --candidate "${{ steps.plan.outputs.candidate_sha }}" | |
| - name: Check Android lint baseline | |
| id: lint-baseline | |
| continue-on-error: true | |
| run: python3 ci/script/normalize_lint_baseline.py --check | |
| - name: Check Markdown links | |
| id: markdown | |
| continue-on-error: true | |
| run: | | |
| python3 ci/script/check_markdown_links.py \ | |
| --base "${{ steps.plan.outputs.base_sha }}" \ | |
| --candidate "${{ steps.plan.outputs.candidate_sha }}" | |
| - name: Check localization changes | |
| id: localization | |
| if: steps.plan.outputs.localization == 'true' | |
| continue-on-error: true | |
| run: | | |
| python3 ci/script/check_localizations.py \ | |
| --base "${{ steps.plan.outputs.base_sha }}" \ | |
| --candidate "${{ steps.plan.outputs.candidate_sha }}" | |
| - name: Check changed YAML and Actions workflows | |
| id: yaml | |
| if: steps.plan.outputs.yaml == 'true' | |
| continue-on-error: true | |
| env: | |
| BASE_SHA: ${{ steps.plan.outputs.base_sha }} | |
| CANDIDATE_SHA: ${{ steps.plan.outputs.candidate_sha }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| yaml_list="$RUNNER_TEMP/changed-yaml.list" | |
| git diff --name-only --no-renames --diff-filter=ACMRT -z \ | |
| "$BASE_SHA" "$CANDIDATE_SHA" -- '*.yml' '*.yaml' > "$yaml_list" | |
| mapfile -d '' -t yaml_files < "$yaml_list" | |
| workflow_list="$RUNNER_TEMP/changed-workflows.list" | |
| : > "$workflow_list" | |
| if (( ${#yaml_files[@]} == 0 )); then | |
| echo "No added or modified YAML files." | |
| exit 0 | |
| fi | |
| # AST parsing validates syntax without converting valid scalars into Ruby objects. | |
| ruby -rpsych -e \ | |
| 'ARGV.each { |path| Psych.parse_file(path); puts "YAML OK: #{path}" }' \ | |
| -- "${yaml_files[@]}" | |
| for path in "${yaml_files[@]}"; do | |
| case "$path" in | |
| .github/workflows/*.yml|.github/workflows/*.yaml) | |
| printf '%s\0' "$path" >> "$workflow_list" | |
| ;; | |
| esac | |
| done | |
| if [[ -s "$workflow_list" ]]; then | |
| mapfile -d '' -t workflow_files < "$workflow_list" | |
| actionlint_archive="$RUNNER_TEMP/actionlint.tar.gz" | |
| actionlint_dir="$RUNNER_TEMP/actionlint" | |
| curl --fail --location --silent --show-error \ | |
| "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz" \ | |
| --output "$actionlint_archive" | |
| echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 $actionlint_archive" \ | |
| | sha256sum --check --strict | |
| mkdir -p "$actionlint_dir" | |
| tar -xzf "$actionlint_archive" -C "$actionlint_dir" | |
| "$actionlint_dir/actionlint" "${workflow_files[@]}" | |
| fi | |
| - name: Require successful fast checks | |
| if: always() | |
| env: | |
| PLAN_OUTCOME: ${{ steps.plan.outcome }} | |
| GATE_TESTS_OUTCOME: ${{ steps.gate-tests.outcome }} | |
| HYGIENE_OUTCOME: ${{ steps.hygiene.outcome }} | |
| LINT_BASELINE_OUTCOME: ${{ steps.lint-baseline.outcome }} | |
| MARKDOWN_OUTCOME: ${{ steps.markdown.outcome }} | |
| LOCALIZATION_OUTCOME: ${{ steps.localization.outcome }} | |
| YAML_OUTCOME: ${{ steps.yaml.outcome }} | |
| LOCALIZATION_REQUIRED: ${{ steps.plan.outputs.localization }} | |
| YAML_REQUIRED: ${{ steps.plan.outputs.yaml }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| failures=0 | |
| check_stage() { | |
| local name="$1" | |
| local required="$2" | |
| local outcome="$3" | |
| printf '| <code>%s</code> | <code>%s</code> | <code>%s</code> |\n' \ | |
| "$name" "$required" "$outcome" >> "$GITHUB_STEP_SUMMARY" | |
| if [[ "$required" == "true" && "$outcome" != "success" ]]; then | |
| echo "Required fast check failed: $name ($outcome)" >&2 | |
| failures=$((failures + 1)) | |
| fi | |
| } | |
| { | |
| echo "## Fast check decision" | |
| echo | |
| echo '| Stage | Required | Outcome |' | |
| echo '| --- | --- | --- |' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| check_stage candidate true "$PLAN_OUTCOME" | |
| check_stage gate-tests true "$GATE_TESTS_OUTCOME" | |
| check_stage hygiene true "$HYGIENE_OUTCOME" | |
| check_stage lint-baseline true "$LINT_BASELINE_OUTCOME" | |
| check_stage markdown true "$MARKDOWN_OUTCOME" | |
| check_stage localization "$LOCALIZATION_REQUIRED" "$LOCALIZATION_OUTCOME" | |
| check_stage yaml "$YAML_REQUIRED" "$YAML_OUTCOME" | |
| if (( failures > 0 )); then | |
| exit 1 | |
| fi | |
| - name: Set up Node.js | |
| if: >- | |
| steps.plan.outputs.web == 'true' || | |
| steps.plan.outputs.toolpkg == 'true' || | |
| steps.plan.outputs.android_full == 'true' | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install JavaScript dependencies | |
| if: >- | |
| steps.plan.outputs.web == 'true' || | |
| steps.plan.outputs.toolpkg == 'true' || | |
| steps.plan.outputs.android_full == 'true' | |
| env: | |
| WEB_REQUIRED: ${{ steps.plan.outputs.web }} | |
| TOOLPKG_REQUIRED: ${{ steps.plan.outputs.toolpkg }} | |
| FULL_ANDROID_REQUIRED: ${{ steps.plan.outputs.android_full }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm install -g pnpm@10.34.5 | |
| if [[ "$TOOLPKG_REQUIRED" == "true" || "$FULL_ANDROID_REQUIRED" == "true" ]]; then | |
| npm ci --no-audit --no-fund | |
| npm --prefix examples/toolpkg_wasm_demo ci --no-audit --no-fund | |
| fi | |
| if [[ "$WEB_REQUIRED" == "true" || "$FULL_ANDROID_REQUIRED" == "true" ]]; then | |
| npm --prefix web-chat ci --no-audit --no-fund | |
| fi | |
| - name: Check WebChat | |
| if: steps.plan.outputs.web == 'true' || steps.plan.outputs.android_full == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm --prefix web-chat run typecheck | |
| npm run build:webchat | |
| - name: Check bundled ToolPkg packages | |
| if: steps.plan.outputs.toolpkg == 'true' || steps.plan.outputs.android_full == 'true' | |
| env: | |
| TOOLPKG_REQUIRED: ${{ steps.plan.outputs.toolpkg }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm run build:examples:github | |
| git diff --exit-code -- examples/github.js | |
| npm --prefix examples/toolpkg_wasm_demo run pack:toolpkg | |
| test -s examples/toolpkg_wasm_demo/main.js | |
| test -s examples/toolpkg_wasm_demo/modules/core.wasm | |
| test -s examples/toolpkg_wasm_demo/dist/toolpkg_wasm_demo.toolpkg | |
| if [[ "$TOOLPKG_REQUIRED" == "true" ]]; then | |
| python3 tools/example_packages/sync_example_packages.py --mode test --no-hot-reload | |
| fi | |
| python3 tools/example_packages/sync_example_packages.py --no-hot-reload | |
| - name: Set up JDK | |
| if: steps.plan.outputs.android_jvm == 'true' || steps.plan.outputs.android_full == 'true' | |
| uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Set up Android SDK | |
| if: >- | |
| steps.plan.outputs.android_resources == 'true' || | |
| steps.plan.outputs.android_jvm == 'true' || | |
| steps.plan.outputs.android_full == 'true' | |
| uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4 | |
| - name: Install required Android toolchain | |
| if: >- | |
| steps.plan.outputs.android_resources == 'true' || | |
| steps.plan.outputs.android_jvm == 'true' || | |
| steps.plan.outputs.android_full == 'true' | |
| env: | |
| JVM_REQUIRED: ${{ steps.plan.outputs.android_jvm }} | |
| FULL_REQUIRED: ${{ steps.plan.outputs.android_full }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| packages=("build-tools;$ANDROID_BUILD_TOOLS_VERSION") | |
| if [[ "$JVM_REQUIRED" == "true" || "$FULL_REQUIRED" == "true" ]]; then | |
| packages+=("platform-tools" "platforms;android-34" "platforms;android-36") | |
| fi | |
| if [[ "$FULL_REQUIRED" == "true" ]]; then | |
| packages+=("ndk;$ANDROID_NDK_VERSION" "cmake;$ANDROID_CMAKE_VERSION") | |
| fi | |
| sdkmanager --install "${packages[@]}" | |
| - name: Compile changed Android resources | |
| if: steps.plan.outputs.android_resources == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| output="$RUNNER_TEMP/compiled-app-resources.zip" | |
| "$ANDROID_HOME/build-tools/$ANDROID_BUILD_TOOLS_VERSION/aapt2" compile \ | |
| --dir app/src/main/res \ | |
| -o "$output" | |
| test -s "$output" | |
| - name: Initialize terminal submodule | |
| if: steps.plan.outputs.android_jvm == 'true' || steps.plan.outputs.android_full == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git submodule sync -- terminal | |
| git submodule update --init --recursive --depth 1 terminal | |
| git submodule status -- terminal | |
| - name: Configure secret-free Android build | |
| if: steps.plan.outputs.android_jvm == 'true' || steps.plan.outputs.android_full == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| echo "sdk.dir=$ANDROID_HOME" > local.properties | |
| - name: Restore JVM dependency cache | |
| id: cache-jvm-deps | |
| if: steps.plan.outputs.android_jvm == 'true' | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ env.MANUAL_DEPS_DIR }} | |
| key: android-jvm-deps-${{ runner.os }}-${{ hashFiles('ci/script/download_android_dependencies.sh', 'ci/script/prepare_android_dependencies.py') }} | |
| - name: Download JVM dependencies | |
| if: steps.plan.outputs.android_jvm == 'true' && steps.cache-jvm-deps.outputs.cache-hit != 'true' | |
| run: bash ci/script/download_android_dependencies.sh jvm "$MANUAL_DEPS_DIR" | |
| - name: Restore full Android dependency cache | |
| id: cache-full-deps | |
| if: steps.plan.outputs.android_full == 'true' | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ env.MANUAL_DEPS_DIR }} | |
| key: android-full-deps-${{ runner.os }}-${{ hashFiles('ci/script/download_android_dependencies.sh', 'ci/script/prepare_android_dependencies.py') }} | |
| - name: Restore generated STT asset cache | |
| if: steps.plan.outputs.android_full == 'true' | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: app/build/generated/stt-model-assets | |
| key: stt-model-assets-${{ runner.os }}-${{ hashFiles('app/config/stt-model-assets.properties') }} | |
| - name: Download full Android dependencies | |
| if: steps.plan.outputs.android_full == 'true' && steps.cache-full-deps.outputs.cache-hit != 'true' | |
| run: bash ci/script/download_android_dependencies.sh full "$MANUAL_DEPS_DIR" | |
| - name: Prepare JVM dependencies | |
| if: steps.plan.outputs.android_jvm == 'true' | |
| run: | | |
| python3 ci/script/prepare_android_dependencies.py \ | |
| --profile jvm \ | |
| --archives "$MANUAL_DEPS_DIR" \ | |
| --repository "$GITHUB_WORKSPACE" | |
| - name: Prepare full Android dependencies | |
| if: steps.plan.outputs.android_full == 'true' | |
| run: | | |
| python3 ci/script/prepare_android_dependencies.py \ | |
| --profile full \ | |
| --archives "$MANUAL_DEPS_DIR" \ | |
| --repository "$GITHUB_WORKSPACE" | |
| - name: Restore native ripgrep cache | |
| if: steps.plan.outputs.android_full == 'true' | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| tools/native_ripgrep/target | |
| key: native-ripgrep-arm64-${{ runner.os }}-${{ hashFiles('tools/native_ripgrep/Cargo.toml', 'tools/native_ripgrep/Cargo.lock', 'tools/native_ripgrep/rust-toolchain.toml', 'tools/native_ripgrep/build_android.sh') }} | |
| - name: Build native ripgrep from locked Rust source | |
| if: steps.plan.outputs.android_full == 'true' | |
| shell: bash | |
| env: | |
| ANDROID_NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/${{ env.ANDROID_NDK_VERSION }} | |
| run: bash tools/native_ripgrep/build_android.sh | |
| - name: Run Android JVM checks | |
| if: steps.plan.outputs.android_jvm == 'true' | |
| env: | |
| INSTRUMENTATION_REQUIRED: ${{ steps.plan.outputs.android_instrumentation }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| chmod +x ./gradlew | |
| tasks=(":app:testDebugUnitTest" ":app:lintDebug") | |
| if [[ "$INSTRUMENTATION_REQUIRED" == "true" ]]; then | |
| tasks+=(":app:compileDebugAndroidTestKotlin" ":app:compileDebugAndroidTestJavaWithJavac") | |
| fi | |
| ./gradlew "${tasks[@]}" --stacktrace --no-build-cache --no-daemon | |
| - name: Run full Android checks | |
| if: steps.plan.outputs.android_full == 'true' | |
| env: | |
| INSTRUMENTATION_REQUIRED: ${{ steps.plan.outputs.android_instrumentation }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| chmod +x ./gradlew | |
| tasks=("assembleDebug" ":app:testDebugUnitTest" ":app:lintDebug") | |
| if [[ "$INSTRUMENTATION_REQUIRED" == "true" ]]; then | |
| tasks+=(":app:compileDebugAndroidTestKotlin" ":app:compileDebugAndroidTestJavaWithJavac") | |
| fi | |
| ./gradlew "${tasks[@]}" --stacktrace --no-build-cache --no-daemon |