Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .github/workflows/docs_deploy.yml

This file was deleted.

62 changes: 62 additions & 0 deletions .github/workflows/on_cron.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Nightly build

on:
schedule:
- cron: "0 0 * * 2-6" # https://crontab.guru/#0_0_*_*_2-6
workflow_dispatch:
inputs:
version_name:
description: 'Version name (Android)'
required: true
type: string
default: '1.x.x-snapshot'

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

jobs:
generate_version:
runs-on: ubuntu-latest
outputs:
version_name: ${{ steps.version.outputs.version_name }}
steps:
- name: Generate version name with timestamp
id: version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION_NAME="${{ inputs.version_name }}"
echo "[DEBUG] Using manual input version name: $VERSION_NAME"
else
VERSION_NAME="nightly-${{ github.run_id }}"
echo "[DEBUG] Generated automatic version name: $VERSION_NAME"
fi
echo "version_name=$VERSION_NAME" >> $GITHUB_OUTPUT
nightly_build:
needs: generate_version
uses: futuredapp/.github/.github/workflows/[email protected]
with:
# `testReleaseUnitTest` covers androidApp module and all shared modules all at once. No need to call `testEnterpriseUnitTest` and `testReleaseUnitTest`.
ANDROID_TEST_GRADLE_TASK: testReleaseUnitTest
ANDROID_PACKAGE_GRADLE_TASK: packageEnterpriseUniversalApk
ANDROID_UPLOAD_GRADLE_TASK: appDistributionUploadEnterprise
ANDROID_VERSION_NAME: ${{ needs.generate_version.outputs.version_name }}
# TODO PROJECT-SETUP verify product flavor configuration
# Specifies API environment for KMP build.
# One of [dev|prod] as per configuration of Buildkonfig plugin in :shared:network:* Gradle module.
KMP_FLAVOR: 'dev'
KMP_SWIFT_PACKAGE_INTEGRATION: true
KMP_SWIFT_PACKAGE_PATH: 'iosApp/shared/KMP'
# TODO PROJECT-SETUP Verify app distribution groups
FIREBASE_APP_DISTRIBUTION_GROUPS: futured-devs, futured-qa
CHANGELOG_DEBUG: true
CHANGELOG_CHECKOUT_DEPTH: 100
CHANGELOG_FALLBACK_LOOKBACK: "2 weeks"
secrets:
FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT: ${{ secrets.APP_DISTRIBUTION_SERVICE_ACCOUNT }}
# TODO PROJECT-SETUP create random secure string in repository secrets, or leave out to disable configuration cache
GRADLE_CACHE_ENCRYPTION_KEY: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
IOS_MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
IOS_APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
IOS_APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
70 changes: 0 additions & 70 deletions .github/workflows/on_demand_deploy.yml

This file was deleted.

56 changes: 0 additions & 56 deletions .github/workflows/on_merge_pull_request.yml

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/on_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ concurrency:

jobs:
detect-changes:
uses: futuredapp/.github/.github/workflows/kmp-cloud-detect-changes.yml@main
uses: futuredapp/.github/.github/workflows/kmp-cloud-detect-changes.yml@2.0.0

check-android:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.androidFiles == 'true' }}
uses: futuredapp/.github/.github/workflows/android-cloud-check.yml@main
uses: futuredapp/.github/.github/workflows/android-cloud-check.yml@2.0.0
with:
LINT_GRADLE_TASKS: lintCheck
# `testReleaseUnitTest` covers androidApp module and all shared modules all at once. No need to call `testEnterpriseUnitTest` and `testReleaseUnitTest`.
Expand All @@ -22,7 +22,7 @@ jobs:
check-ios:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.iosFiles == 'true' }}
uses: futuredapp/.github/.github/workflows/ios-kmp-selfhosted-test.yml@main
uses: futuredapp/.github/.github/workflows/ios-kmp-selfhosted-test.yml@2.0.0
with:
kmp_swift_package_integration: true
kmp_swift_package_path: iosApp/shared/KMP
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/on_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
release_ios:
name: iOS Release
uses: futuredapp/.github/.github/workflows/ios-kmp-selfhosted-release.yml@main
uses: futuredapp/.github/.github/workflows/ios-kmp-selfhosted-release.yml@2.0.0
with:
kmp_swift_package_integration: true
kmp_swift_package_path: iosApp/shared/KMP
Expand All @@ -23,11 +23,10 @@ jobs:

release_android:
name: Android Release
uses: futuredapp/.github/.github/workflows/android-cloud-release-googlePlay.yml@main
uses: futuredapp/.github/.github/workflows/android-cloud-release-googlePlay.yml@2.0.0
with:
VERSION_NAME: ${{ github.event.release.tag_name }}
BUNDLE_GRADLE_TASK: bundleRelease
SIGNING_KEYSTORE_PATH: androidApp/keystore/release.keystore
# TODO PROJECT-SETUP This has to be applicationId
GOOGLE_PLAY_APPLICATION_ID: app.futured.kmptemplate.android
GOOGLE_PLAY_WHATSNEW_DIRECTORY: androidApp/whatsnew
Expand Down
10 changes: 5 additions & 5 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ android {
applicationId = ProjectSettings.Android.ApplicationId
minSdk = ProjectSettings.Android.MinSdkVersion
targetSdk = ProjectSettings.Android.TargetSdkVersion
versionCode = ProjectSettings.Android.VersionCode
versionName = ProjectSettings.Android.VersionName
versionCode = System.getenv("ANDROID_BUILD_NUMBER")?.toIntOrNull() ?: 1
versionName = System.getenv("ANDROID_VERSION_NAME") ?: "1.x.x-local"
}

buildFeatures {
Expand All @@ -48,9 +48,9 @@ android {
}
create(ProjectSettings.Android.BuildTypes.Release) {
storeFile = file("keystore/todo_your_release_keystore.keystore")
storePassword = ProjectSettings.Android.Signing.Release.StorePassword
keyAlias = ProjectSettings.Android.Signing.Release.KeyAlias
keyPassword = ProjectSettings.Android.Signing.Release.KeyPassword
storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD").orEmpty()
keyAlias = System.getenv("ANDROID_KEY_ALIAS").orEmpty()
keyPassword = System.getenv("ANDROID_KEY_PASSWORD").orEmpty()
Comment on lines +51 to +53
Copy link

@coderabbitai coderabbitai bot Oct 7, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail‑fast on missing signing env and support SIGNING_ fallback to match CI.*

Empty strings defer errors to signing time. Resolve early and align with workflow secrets.

-            storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD").orEmpty()
-            keyAlias = System.getenv("ANDROID_KEY_ALIAS").orEmpty()
-            keyPassword = System.getenv("ANDROID_KEY_PASSWORD").orEmpty()
+            val storePwd = System.getenv("ANDROID_KEYSTORE_PASSWORD")
+                ?: System.getenv("SIGNING_KEYSTORE_PASSWORD")
+                ?: error("Missing ANDROID_KEYSTORE_PASSWORD/SIGNING_KEYSTORE_PASSWORD")
+            val alias = System.getenv("ANDROID_KEY_ALIAS")
+                ?: System.getenv("SIGNING_KEY_ALIAS")
+                ?: error("Missing ANDROID_KEY_ALIAS/SIGNING_KEY_ALIAS")
+            val keyPwd = System.getenv("ANDROID_KEY_PASSWORD")
+                ?: System.getenv("SIGNING_KEY_PASSWORD")
+                ?: error("Missing ANDROID_KEY_PASSWORD/SIGNING_KEY_PASSWORD")
+            storePassword = storePwd
+            keyAlias = alias
+            keyPassword = keyPwd

Optionally drive the keystore path from env to avoid committing it:

-            storeFile = file("keystore/todo_your_release_keystore.keystore")
+            val ksPath = System.getenv("ANDROID_KEYSTORE_PATH") ?: "keystore/release.keystore"
+            storeFile = file(ksPath)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD").orEmpty()
keyAlias = System.getenv("ANDROID_KEY_ALIAS").orEmpty()
keyPassword = System.getenv("ANDROID_KEY_PASSWORD").orEmpty()
val storePwd = System.getenv("ANDROID_KEYSTORE_PASSWORD")
?: System.getenv("SIGNING_KEYSTORE_PASSWORD")
?: error("Missing ANDROID_KEYSTORE_PASSWORD/SIGNING_KEYSTORE_PASSWORD")
val alias = System.getenv("ANDROID_KEY_ALIAS")
?: System.getenv("SIGNING_KEY_ALIAS")
?: error("Missing ANDROID_KEY_ALIAS/SIGNING_KEY_ALIAS")
val keyPwd = System.getenv("ANDROID_KEY_PASSWORD")
?: System.getenv("SIGNING_KEY_PASSWORD")
?: error("Missing ANDROID_KEY_PASSWORD/SIGNING_KEY_PASSWORD")
storePassword = storePwd
keyAlias = alias
keyPassword = keyPwd

Copy link
Member Author

Choose a reason for hiding this comment

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

Falling back to error if null causes crash during Gradle Sync = during configuration phase the entire script is evaluated, so we need to provide meaningful default value even if not building release type.

Copy link

Choose a reason for hiding this comment

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

@matejsemancik, you're absolutely right—I overlooked the Gradle configuration phase behavior. Since the entire script is evaluated during sync regardless of the build type, using error() would indeed break the developer experience for anyone running a simple Gradle sync or building debug variants without release signing credentials.

The current .orEmpty() approach is the appropriate solution here. It allows configuration to succeed while deferring validation to the actual signing phase, where missing credentials will naturally fail if someone attempts a release build. This strikes the right balance between validation and usability.

Thanks for the clarification!


✏️ Learnings added
Learnt from: matejsemancik
PR: futuredapp/kmp-futured-template#131
File: androidApp/build.gradle.kts:51-53
Timestamp: 2025-10-07T09:54:12.559Z
Learning: In Gradle build scripts, using `error()` for missing environment variables in signing configurations causes crashes during Gradle Sync because the entire script is evaluated during the configuration phase, even when not building that specific variant. Use `.orEmpty()` or provide default values instead to allow configuration to succeed, with validation deferred to actual build/signing time.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member Author

Choose a reason for hiding this comment

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

No problem, rookie mistake.

Copy link

Choose a reason for hiding this comment

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

@matejsemancik, appreciate the understanding! Always learning. 😊


🧠 Learnings used
Learnt from: matejsemancik
PR: futuredapp/kmp-futured-template#131
File: androidApp/build.gradle.kts:51-53
Timestamp: 2025-10-07T09:54:12.559Z
Learning: In Gradle build scripts, using `error()` for missing environment variables in signing configurations causes crashes during Gradle Sync because the entire script is evaluated during the configuration phase, even when not building that specific variant. Use `.orEmpty()` or provide default values instead to allow configuration to succeed, with validation deferred to actual build/signing time.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ object ProjectSettings {

const val ApplicationId = "app.futured.kmptemplate.android"

val VersionCode = System.getenv("ANDROID_BUILD_NUMBER")?.toInt() ?: 1
val VersionName = System.getenv("ANDROID_VERSION_NAME") ?: "1.0.0"

val JavaCompatibility = JavaVersion.VERSION_17
const val KotlinJvmTargetNum = "17"

Expand All @@ -37,12 +34,6 @@ object ProjectSettings {
val KeyAlias = "androiddebugkey"
val KeyPassword = "android"
}

object Release {
val StorePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD").orEmpty()
val KeyAlias = System.getenv("ANDROID_KEY_ALIAS").orEmpty()
val KeyPassword = System.getenv("ANDROID_KEY_PASSWORD").orEmpty()
}
}
}

Expand Down
Loading