Repository navigation
Resolve shared demo file references in Android/iOS artifact workflows #2168
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: Build All Android Demos | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - master | |
| paths: | |
| - 'demos/android/**' | |
| pull_request: | |
| branches: | |
| - master | |
| paths: | |
| - 'demos/**' | |
| - '.github/workflows/build-android-demos.yml' | |
| jobs: | |
| generate-matrix: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| sparse-checkout: demos/android | |
| fetch-depth: 0 | |
| - name: Generate matrix | |
| id: set-matrix | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| changed_files="$(git diff --name-only "${{ github.event.pull_request.base.sha }}" HEAD -- 'demos/android/**')" | |
| echo "Changed files:" | |
| echo "$changed_files" | |
| demos="$(echo "$changed_files" \ | |
| | grep -oE '^demos/android/[^/]+/MASTG-DEMO-[^/]+' \ | |
| | sort -u || true)" | |
| # Drop demos whose directory no longer exists (e.g. removed in the target PR), | |
| # otherwise the build job fails on the missing directory. | |
| existing_demos="" | |
| while IFS= read -r demo; do | |
| [ -n "$demo" ] || continue | |
| if [ -d "$demo" ]; then | |
| existing_demos+="$demo"$'\n' | |
| else | |
| echo "Skipping removed demo: $demo" | |
| fi | |
| done <<< "$demos" | |
| demos="$existing_demos" | |
| else | |
| demos="$(find demos/android -mindepth 2 -maxdepth 2 -type d -name 'MASTG-DEMO-*' | sort)" | |
| fi | |
| matrix="$(printf '%s\n' "$demos" \ | |
| | jq -R 'select(length > 0)' \ | |
| | jq -sc '{demo: .}')" | |
| echo "matrix=$matrix" >> "$GITHUB_OUTPUT" | |
| echo "Print matrix: $matrix" | |
| - name: Print matrix | |
| run: echo '${{ steps.set-matrix.outputs.matrix }}' | |
| build-base-app: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| outputs: | |
| mastestapp_hash: ${{ steps.get-mastestapp-hash.outputs.mastestapp_hash }} | |
| steps: | |
| - name: Get last commit hash of mas-app-android | |
| id: get-mastestapp-hash | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| hash="$(git ls-remote https://github.com/cpholguera/mas-app-android.git HEAD | awk '{print $1}')" | |
| echo "mastestapp_hash=$hash" >> "$GITHUB_OUTPUT" | |
| echo "MASTESTAPP_HASH=$hash" >> "$GITHUB_ENV" | |
| - name: Check if already cached | |
| id: cache-check | |
| uses: actions/cache/restore@v5 | |
| with: | |
| lookup-only: true | |
| path: mas-app-android/ | |
| key: base-app-${{ steps.get-mastestapp-hash.outputs.mastestapp_hash }} | |
| - name: Set up JDK 17 | |
| if: steps.cache-check.outputs.cache-hit != 'true' | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| if: steps.cache-check.outputs.cache-hit != 'true' | |
| uses: gradle/actions/setup-gradle@v6 | |
| with: | |
| cache-read-only: false | |
| - name: Accept Android SDK licenses | |
| if: steps.cache-check.outputs.cache-hit != 'true' | |
| run: yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses | |
| - name: Clone mas-app-android repository | |
| if: steps.cache-check.outputs.cache-hit != 'true' | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: cpholguera/mas-app-android | |
| path: mas-app-android | |
| ref: ${{ steps.get-mastestapp-hash.outputs.mastestapp_hash }} | |
| - name: Build base app | |
| if: steps.cache-check.outputs.cache-hit != 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cd mas-app-android | |
| grep -q 'org.gradle.caching=true' gradle.properties \ | |
| || echo -en "\norg.gradle.caching=true\norg.gradle.configuration-cache=true\n" >> gradle.properties | |
| ./gradlew assembleDebug --stacktrace | |
| echo "Build succeeded" | |
| - name: Saving cache | |
| if: steps.cache-check.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: mas-app-android/ | |
| key: base-app-${{ steps.get-mastestapp-hash.outputs.mastestapp_hash }} | |
| build: | |
| needs: | |
| - generate-matrix | |
| - build-base-app | |
| if: ${{ needs.generate-matrix.outputs.matrix != '{"demo":[]}' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} | |
| max-parallel: 3 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| sparse-checkout: demos/android | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v6 | |
| with: | |
| cache-read-only: true | |
| - name: Accept Android SDK licenses | |
| run: yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses | |
| - name: Restore base app cache | |
| id: cache-base-app | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: mas-app-android/ | |
| key: base-app-${{ needs.build-base-app.outputs.mastestapp_hash }} | |
| fail-on-cache-miss: true | |
| - name: Replace files and build APK | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| demo="${{ matrix.demo }}" | |
| [ -d "$demo" ] || { | |
| echo "Demo directory not found: $demo" | |
| exit 1 | |
| } | |
| echo "Processing $demo" | |
| mapfile -t referenced_demo_dirs < <( | |
| find "$demo" -maxdepth 1 -type f -name 'MASTG-DEMO-*.md' -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' demo_md; do | |
| grep -oE '\{\{[^}]+\}\}' "$demo_md" \ | |
| | sed -E 's/^\{\{\s*|\s*\}\}$//g' \ | |
| | tr '#' '\n' \ | |
| | sed -E 's/^\s+|\s+$//g' \ | |
| | grep -E '^\.\./MASTG-DEMO-[^/]+/.+' \ | |
| | while IFS= read -r shared_path; do | |
| dirname "$(realpath -m "$demo/$shared_path")" | |
| done | |
| done \ | |
| | sort -u | |
| ) | |
| resolve_source_file() { | |
| local relative_file="$1" | |
| local source_file="$demo/$relative_file" | |
| if [ -f "$source_file" ]; then | |
| echo "$source_file" | |
| return 0 | |
| fi | |
| for shared_dir in "${referenced_demo_dirs[@]}"; do | |
| source_file="$shared_dir/$relative_file" | |
| if [ -f "$source_file" ]; then | |
| echo "$source_file" | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| copy_if_exists() { | |
| local relative_file="$1" | |
| local target_file="$2" | |
| local label="$3" | |
| local source_file="" | |
| if source_file="$(resolve_source_file "$relative_file")"; then | |
| cp -f "$source_file" "$target_file" | |
| echo "Copied $label for $demo" | |
| else | |
| echo "No $label found for $demo" | |
| fi | |
| } | |
| copy_if_exists MastgTest.kt mas-app-android/app/src/main/java/org/owasp/mastestapp/MastgTest.kt MastgTest.kt | |
| copy_if_exists MainActivity.kt mas-app-android/app/src/main/java/org/owasp/mastestapp/MainActivity.kt MainActivity.kt | |
| copy_if_exists MastgTestWebView.kt mas-app-android/app/src/main/java/org/owasp/mastestapp/MastgTestWebView.kt MastgTestWebView.kt | |
| copy_if_exists AndroidManifest.xml mas-app-android/app/src/main/AndroidManifest.xml AndroidManifest.xml | |
| copy_if_exists filepaths.xml mas-app-android/app/src/main/res/xml/filepaths.xml filepaths.xml | |
| copy_if_exists network_security_config.xml mas-app-android/app/src/main/res/xml/network_security_config.xml network_security_config.xml | |
| copy_if_exists backup_rules.xml mas-app-android/app/src/main/res/xml/backup_rules.xml backup_rules.xml | |
| copy_if_exists data_extraction_rules.xml mas-app-android/app/src/main/res/xml/data_extraction_rules.xml data_extraction_rules.xml | |
| copy_if_exists proguard-rules.pro mas-app-android/app/proguard-rules.pro proguard-rules.pro | |
| if find "$demo" -maxdepth 1 -name "*.proto" -print -quit 2>/dev/null | grep -q .; then | |
| mkdir -p mas-app-android/app/src/main/proto | |
| find "$demo" -maxdepth 1 -name "*.proto" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' proto_file; do | |
| cp -f "$proto_file" mas-app-android/app/src/main/proto/ | |
| echo "Copied $(basename "$proto_file") for $demo" | |
| done | |
| else | |
| echo "No .proto files found for $demo" | |
| fi | |
| if [ -f "$demo/CMakeLists.txt" ]; then | |
| mkdir -p mas-app-android/app/src/main/cpp | |
| cp -f "$demo/CMakeLists.txt" mas-app-android/app/src/main/cpp/CMakeLists.txt | |
| echo "Copied CMakeLists.txt for $demo" | |
| find "$demo" -maxdepth 1 -name "*.cpp" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' cpp_file; do | |
| cp -f "$cpp_file" mas-app-android/app/src/main/cpp/ | |
| echo "Copied $(basename "$cpp_file") for $demo" | |
| done | |
| else | |
| echo "No CMakeLists.txt found for $demo" | |
| fi | |
| insert_block() { | |
| local kind="$1" | |
| local upper="${kind^^}" | |
| local file="$demo/build.gradle.kts.$kind" | |
| local target="mas-app-android/app/build.gradle.kts" | |
| if [ -f "$file" ]; then | |
| sed -i '/\/\/ ADD_'"$upper"'_HERE/{ | |
| r '"$file"' | |
| d | |
| }' "$target" | |
| echo "Replaced $kind in build.gradle.kts for $demo" | |
| else | |
| echo "No build.gradle.kts.$kind found for $demo, skipping $kind replacement" | |
| fi | |
| } | |
| insert_block plugins | |
| insert_block sections | |
| insert_block android | |
| insert_block libs | |
| insert_block build | |
| echo "Building APK for $demo" | |
| cd mas-app-android | |
| grep -q 'org.gradle.caching=true' gradle.properties \ | |
| || echo -en "\norg.gradle.caching=true\norg.gradle.configuration-cache=true\n" >> gradle.properties | |
| ./gradlew assembleDebug --stacktrace | |
| cd .. | |
| echo "Build succeeded for $demo" | |
| apk_filename="$(basename "$demo").apk" | |
| mv mas-app-android/app/build/outputs/apk/debug/app-debug.apk "$apk_filename" | |
| echo "APK for $demo moved to $apk_filename" | |
| echo "APK_NAME=$apk_filename" >> "$GITHUB_ENV" | |
| - name: Upload APK | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ env.APK_NAME }} | |
| path: ${{ env.APK_NAME }} | |
| if-no-files-found: error |