Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Create workflow to build sample app with latest SDK release #457

Merged
merged 6 commits into from
Oct 30, 2024
Merged
9 changes: 9 additions & 0 deletions .github/workflows/build-sample-app-for-sdk-release.yml
mahmoud-elmorabea marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Build sample app for SDK release
on:
workflow_dispatch:
jobs:
build-sample-apps:
uses: ./.github/workflows/reusable_build_sample_apps.yml
with:
use_latest_sdk_version: true
secrets: inherit
138 changes: 34 additions & 104 deletions .github/workflows/build-sample-apps.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: Build sample apps
name: Build sample apps

on:
on:
pull_request: # build sample apps for every commit pushed to an open pull request (including drafts)
push:
branches: [main, feature/*]
push:
branches: [ main, feature/* ]
release: # build sample apps for every git tag created. These are known as "stable" builds that are suitable for people outside the mobile team.
types: [published]
types: [ published ]

concurrency: # cancel previous workflow run if one exists.
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -20,105 +20,35 @@ jobs:
outputs:
comment-id: ${{ steps.create-comment.outputs.comment-id }}
steps:
- name: Find Comment
uses: peter-evans/find-comment@v3
id: existing-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: <!-- sample app builds -->
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v4
id: create-comment
with:
comment-id: ${{ steps.existing-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
<!-- sample app builds -->
# Sample app builds 📱

Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.

---
${{ steps.build.outputs.build-log }}
edit-mode: replace # replace the existing comment with new content since we are creating new builds
- name: Find Comment
uses: peter-evans/find-comment@v3
id: existing-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: <!-- sample app builds -->

- name: Create or update comment
uses: peter-evans/create-or-update-comment@v4
id: create-comment
with:
comment-id: ${{ steps.existing-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
<!-- sample app builds -->
# Sample app builds 📱
Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.
---
${{ steps.build.outputs.build-log }}
edit-mode: replace # replace the existing comment with new content since we are creating new builds

build-sample-apps:
if: ${{ always() }} # do not skip running this step if update-pr-comment does not run
needs: [update-pr-comment] # wait for PR comment to be created saying new builds are being made.
permissions:
pull-requests: write # comment on pull request with build information
strategy:
fail-fast: false # if one sample app fails to build, let the other sample apps continue to build and not cancel them.
matrix: # Use a matrix allowing us to build multiple apps in parallel. Just add an entry to the matrix and it will build!
sample-app:
# List all sample apps you want to have compiled.
# List item is name of directory inside of "samples" directory for the corresponding app to compile.
- "java_layout"
- "kotlin_compose"
include: # Add additional variables to each sample app build: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
- sample-app: "java_layout"
cio-cdpapikey-secret-key: "CUSTOMERIO_JAVA_WORKSPACE_CDP_API_KEY"
cio-siteid-secret-key: "CUSTOMERIO_JAVA_WORKSPACE_SITE_ID"
- sample-app: "kotlin_compose"
cio-cdpapikey-secret-key: "CUSTOMERIO_KOTLIN_WORKSPACE_CDP_API_KEY"
cio-siteid-secret-key: "CUSTOMERIO_KOTLIN_WORKSPACE_SITE_ID"

runs-on: ubuntu-latest
name: Building app...${{ matrix.sample-app }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-android

# CLI to replace strings in files. The CLI recommends using `cargo install` which is slow. This Action is fast because it downloads pre-built binaries.
# If using sd on macos, "brew install" works great. for Linux, this is the recommended way.
- name: Install sd CLI to use later in the workflow
uses: kenji-miyake/setup-sd@v2

- name: Install tools from Gemfile (ruby language) used for building our apps with
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true # cache tools to make builds faster in future

- name: Setup local.properties file for sample app
run: |
touch "samples/local.properties"
echo "cdpApiKey=${{ secrets[matrix.cio-cdpapikey-secret-key] }}" >> "samples/local.properties"
echo "siteId=${{ secrets[matrix.cio-siteid-secret-key] }}" >> "samples/local.properties"

- name: Dump GitHub Action metadata because Fastlane uses it. Viewing it here helps debug JSON parsing code in Firebase.
run: cat $GITHUB_EVENT_PATH

- name: Deploy development build via Fastlane
uses: maierj/[email protected]
with:
lane: 'android build'
subdirectory: "samples/${{ matrix.sample-app }}"
env:
ANDROID_SIGNING_ALIAS: ${{ secrets.ANDROID_SIGNING_ALIAS }}
ANDROID_SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
ANDROID_SIGNING_STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }}
FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64: ${{ secrets.FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64 }}

- name: Update sample builds PR comment with build information
if: ${{ github.event_name == 'pull_request' }}
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ needs.update-pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
# the variables APP_BUILD_NUMBER, APP_VERSION_STRING are created when fastlane runs "build".
body: |
* ${{ matrix.sample-app }}: `${{ env.APP_VERSION_STRING }} (${{ env.APP_BUILD_NUMBER }})`
edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds.
if: ${{ always() }} # do not skip running this step if update-pr-comment does not run
needs: [ update-pr-comment ] # wait for PR comment to be created saying new builds are being made.
uses: ./.github/workflows/reusable_build_sample_apps.yml
with:
use_latest_sdk_version: false
secrets: inherit

- name: Update sample builds PR comment with build failure message
if: ${{ failure() }}
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ needs.update-pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
* ${{ matrix.sample-app }}: Build failed. See [CI job logs](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}) to determine the issue and try re-building.
edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds.
104 changes: 104 additions & 0 deletions .github/workflows/reusable_build_sample_apps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Reusable build sample apps workflow

on:
workflow_call:
mahmoud-elmorabea marked this conversation as resolved.
Show resolved Hide resolved
inputs:
use_latest_sdk_version:
description: "Whether this workflow should build sample apps with latest SDK version or source code"
type: boolean
required: false
default: false

jobs:
build_sample_apps:
runs-on: ubuntu-latest
strategy:
fail-fast: false # if one sample app fails to build, let the other sample apps continue to build and not cancel them.
matrix: # Use a matrix allowing us to build multiple apps in parallel. Just add an entry to the matrix and it will build!
sample-app:
# List all sample apps you want to have compiled.
# List item is name of directory inside of "samples" directory for the corresponding app to compile.
- "java_layout"
- "kotlin_compose"
include: # Add additional variables to each sample app build: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
- sample-app: "java_layout"
cio-cdpapikey-secret-key: "CUSTOMERIO_JAVA_WORKSPACE_CDP_API_KEY"
cio-siteid-secret-key: "CUSTOMERIO_JAVA_WORKSPACE_SITE_ID"
- sample-app: "kotlin_compose"
cio-cdpapikey-secret-key: "CUSTOMERIO_KOTLIN_WORKSPACE_CDP_API_KEY"
cio-siteid-secret-key: "CUSTOMERIO_KOTLIN_WORKSPACE_SITE_ID"

name: Building app...${{ matrix.sample-app }}
permissions:
pull-requests: write # comment on pull request with build information
steps:
- name: Check out code with conditional fetch-depth
uses: actions/checkout@v4
with:
fetch-depth: 0 # Workaround for bug https://github.com/actions/checkout/issues/1471

- name: Get latest SDK version
if: ${{ inputs.use_latest_sdk_version == true }}
id: latest-sdk-version-step
run: |
latest_tag=$(git describe --tags --abbrev=0)
echo "LATEST_TAG=$latest_tag" >> "$GITHUB_OUTPUT"

- uses: ./.github/actions/setup-android

# CLI to replace strings in files. The CLI recommends using `cargo install` which is slow. This Action is fast because it downloads pre-built binaries.
# If using sd on macos, "brew install" works great. for Linux, this is the recommended way.
- name: Install sd CLI to use later in the workflow
uses: kenji-miyake/setup-sd@v2

- name: Install tools from Gemfile (ruby language) used for building our apps with
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true # cache tools to make builds faster in future

- name: Setup local.properties file for sample app
run: |
touch "samples/local.properties"
echo "cdpApiKey=${{ secrets[matrix.cio-cdpapikey-secret-key] }}" >> "samples/local.properties"
echo "siteId=${{ secrets[matrix.cio-siteid-secret-key] }}" >> "samples/local.properties"
if [ "${{ inputs.use_latest_sdk_version == true }}" ]; then
echo "sdkVersion=${{ steps.latest-sdk-version-step.outputs.LATEST_TAG }}" >> "samples/local.properties"
fi

- name: Dump GitHub Action metadata because Fastlane uses it. Viewing it here helps debug JSON parsing code in Firebase.
run: cat $GITHUB_EVENT_PATH

- name: Deploy build via Fastlane
if: ${{ ! (inputs.use_latest_sdk_version == true && matrix.sample-app == 'kotlin_compose') }}
uses: maierj/[email protected]
with:
lane: ${{ inputs.use_latest_sdk_version == true && 'android build_sample_app_for_sdk_release' || 'android build' }}
subdirectory: "samples/${{ matrix.sample-app }}"
options: ${{ inputs.use_latest_sdk_version == true && format('{{"sdk_version":"{0}"}}', steps.latest-sdk-version-step.outputs.LATEST_TAG) || '' }}
env:
ANDROID_SIGNING_ALIAS: ${{ secrets.ANDROID_SIGNING_ALIAS }}
ANDROID_SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
ANDROID_SIGNING_STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }}
FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64: ${{ secrets.FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64 }}

- name: Update sample builds PR comment with build information
if: ${{ github.event_name == 'pull_request' }}
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ needs.update-pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
# the variables APP_BUILD_NUMBER, APP_VERSION_STRING are created when fastlane runs "build".
body: |
* ${{ matrix.sample-app }}: `${{ env.APP_VERSION_STRING }} (${{ env.APP_BUILD_NUMBER }})`
edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds.

- name: Update sample builds PR comment with build failure message
if: ${{ failure() }}
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ needs.update-pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
* ${{ matrix.sample-app }}: Build failed. See [CI job logs](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}) to determine the issue and try re-building.
edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds.
76 changes: 54 additions & 22 deletions samples/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,22 @@ import_from_git(
path: "Apps/fastlane/Fastfile"
)

platform :android do
lane :build do |values|
# Get the name of the Gradle module for the sample app. We need this later when building it.
# The name of the gradle module is the name of the directory where the sample app source code is hosted in `samples/`.
path_to_root_directory_sample_app = File.expand_path('../', Dir.pwd) # We are currently in `samples/N/fastlane/` and we need the directory name `samples/N``
name_of_sample_app_module = File.basename(path_to_root_directory_sample_app) # just get the name of the directory I am in now. `samples/N`
private_lane :build_sample_app do |options|
name_of_sample_app_module = options[:sample_app_name]

# Apps use `build.gradle` or `build.kts`. This wildcard lookup is used to get the absolute path to one of these files present
path_to_app_gradle_file = File.expand_path(Dir.glob("../build.*").first, Dir.pwd)
# Apps use `build.gradle` or `build.kts`. This wildcard lookup is used to get the absolute path to one of these files present
path_to_app_gradle_file = File.expand_path(Dir.glob("../build.*").first, Dir.pwd)

new_app_version = get_new_app_version()
new_app_version = options[:app_version]
new_build_number = get_new_build_version()
build_notes = get_build_notes()
test_groups = get_build_test_groups()
app_package_name = CredentialsManager::AppfileConfig.try_fetch_value(:package_name) # get package_name from Appfile
test_groups = options[:firebase_test_groups]
app_package_name = options[:app_package_name]

UI.important(find_firebase_app_id(app_identifier: app_package_name))

# Modify the source code with the new app version and build number before we compile the iOS app. This is a good idea to do to make installing builds on a test device easier.
# The iOS OS might give errors when trying to install a build of an app if the app is already installed on the device. Having unique build number or app version can avoid those errors.
# Modify the source code with the new app version and build number before we compile the Android app. This is a good idea to do to make installing builds on a test device easier.
# The Android OS might give errors when trying to install a build of an app if the app is already installed on the device. Having unique build number or app version can avoid those errors.
android_set_version_name(
version_name: new_app_version,
gradle_file: path_to_app_gradle_file
Expand All @@ -34,19 +30,21 @@ platform :android do
gradle_file: path_to_app_gradle_file
)

UI.important("Updating the SDK's source code version to non-production version. This allows the sample apps to show the SDK version at runtime for app user to better understand the version of the SDK they are running.")
sh("../../../scripts/update-version.sh \"#{new_app_version}.#{new_build_number}\"")
if !options.key?(:sdk_version)
UI.important("Updating the SDK's source code version to non-production version. This allows the sample apps to show the SDK version at runtime for app user to better understand the version of the SDK they are running.")
sh("../../../scripts/update-version.sh \"#{new_app_version}.#{new_build_number}\"")
end

build_android_app(
task: ":samples:#{name_of_sample_app_module}:assemble",
build_type: 'Release',
gradle_path: '../../gradlew' # path to root directory of the SDK. Where gradlew is located
gradle_path: '../../gradlew' # path to root directory of the SDK. Where gradlew is located
)

# function 'setup_google_bucket_access' is a re-usable function inside of apple-code-signing Fastfile that we imported.
# This allows you to create a temporary file from a GitHub secret for added convenience.
# When uploading the build to Firebase App Distribution, the CI server needs to authenticate with Firebase. This is done with a
# Google Cloud Service Account json creds file. The base64 encoded value of this service account file is stored as this secret.
# function 'setup_google_bucket_access' is a re-usable function inside of apple-code-signing Fastfile that we imported.
# This allows you to create a temporary file from a GitHub secret for added convenience.
# When uploading the build to Firebase App Distribution, the CI server needs to authenticate with Firebase. This is done with a
# Google Cloud Service Account json creds file. The base64 encoded value of this service account file is stored as this secret.
service_credentials_file_path = setup_google_bucket_access(
environment_variable_key: "FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64"
)
Expand All @@ -57,5 +55,39 @@ platform :android do
groups: test_groups,
release_notes: build_notes
)
end
end
end

platform :android do
lane :build do |values|
# Get the name of the Gradle module for the sample app. We need this later when building it.
# The name of the gradle module is the name of the directory where the sample app source code is hosted in `samples/`.
path_to_root_directory_sample_app = File.expand_path('../', Dir.pwd) # We are currently in `samples/N/fastlane/` and we need the directory name `samples/N``
name_of_sample_app_module = File.basename(path_to_root_directory_sample_app) # just get the name of the directory I am in now. `samples/N`

new_app_version = get_new_app_version()
test_groups = get_build_test_groups()
app_package_name = CredentialsManager::AppfileConfig.try_fetch_value(:package_name) # get package_name from Appfile

build_sample_app(
sample_app_name: name_of_sample_app_module,
app_version: new_app_version,
firebase_test_groups: test_groups,
app_package_name: app_package_name
)
end

lane :build_sample_app_for_sdk_release do |values|
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted for creating a new lane since the existing one is already fairly complicated. Also if we decide to move to a single app, we can just deprecate the older lane.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm unsure about this because again, both lanes have 70% of the same code, apart from some hardcoded values in this lane. I don't mind separate lane, but then could we probably move the shared code in methods that both lanes can utilize?

Also, could you point out the complicated parts in the other lane so we can see how we can make them less complicated in future?

for example, something like this, ensure, that when you are testing a feature branch we don't show the version of the SDK in user agent, but rather the branch its being tested on.

    UI.important("Updating the SDK's source code version to non-production version. This allows the sample apps to show the SDK version at runtime for app user to better understand the version of the SDK they are running.")
    sh("../../../scripts/update-version.sh \"#{new_app_version}.#{new_build_number}\"")

# Apps use `build.gradle` or `build.kts`. This wildcard lookup is used to get the absolute path to one of these files present
path_to_app_gradle_file = File.expand_path(Dir.glob("../build.*").first, Dir.pwd)

new_app_version = values[:sdk_version]

build_sample_app(
sample_app_name: 'java_layout',
app_version: new_app_version,
firebase_test_groups: 'public, stable-builds',
app_package_name: 'io.customer.android.sample.java_layout',
sdk_version: values[:sdk_version]
)
end
end
Loading