7 Commits
test5 ... main

Author SHA1 Message Date
Hermes
c84181ea18 ci: route Android builds through BuildTool
All checks were successful
Android build / build (push) Successful in 8m23s
Android build / build (release) Successful in 16m1s
2026-09-01 17:16:44 -05:00
Hermes
0ae7ac86d9 ci: prefer github token for release asset upload
All checks were successful
Android build / build (push) Successful in 7m8s
Android build / build (release) Successful in 8m28s
2026-08-31 18:22:00 -05:00
Hermes
61b8c3d18f ci: broaden release asset upload token fallback
All checks were successful
Android build / build (push) Successful in 8m8s
2026-08-31 16:12:12 -05:00
Hermes
87c77dc1fa ci: bump APK versionCode and timestamp AAB names
Some checks failed
Android build / build (push) Successful in 11m31s
Android build / build (release) Failing after 8m2s
2026-08-31 15:05:45 -05:00
Hermes
e53a33c7b5 fix: repair AGP upgrade file corruption
All checks were successful
Android build / build (push) Successful in 9m9s
2026-08-31 13:49:22 -05:00
Hermes
4bbcead398 build: upgrade AGP for Android 16 support
Some checks failed
Android build / build (push) Has been cancelled
2026-08-31 13:30:46 -05:00
Hermes
dcdf69d02a build: target Android 16 / API 36
All checks were successful
Android build / build (push) Successful in 12m16s
2026-08-31 12:17:46 -05:00
6 changed files with 89 additions and 97 deletions

View File

@@ -1,7 +1,7 @@
name: Android build name: Android build
permissions: permissions:
contents: read contents: write
releases: write releases: write
on: on:
@@ -29,110 +29,56 @@ jobs:
distribution: temurin distribution: temurin
java-version: '17' java-version: '17'
- name: Install Android SDK command-line tools and packages - name: Run project build tooling for debug APK
if: github.event_name != 'release'
shell: bash shell: bash
env:
CI_VERSION_CODE: ${{ github.run_number }}
run: | run: |
set -euxo pipefail set -euxo pipefail
export ANDROID_SDK_ROOT="$HOME/.android/sdk" chmod +x ./BuildTool.sh
export ANDROID_HOME="$ANDROID_SDK_ROOT" ./BuildTool.sh --no-commit --apk-only
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
if [ ! -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
tools_url="$(curl -fsSL https://developer.android.com/studio | grep -o 'https://dl.google.com/android/repository/commandlinetools-linux-[0-9][0-9]*_latest.zip' | head -n 1)"
test -n "$tools_url"
curl -fsSL -o /tmp/commandlinetools.zip "$tools_url"
rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest" "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools"
unzip -q /tmp/commandlinetools.zip -d "$ANDROID_SDK_ROOT/cmdline-tools"
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
fi
export PATH="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH"
set +o pipefail
yes | sdkmanager --licenses >/dev/null
set -o pipefail
sdkmanager \
"platform-tools" \
"platforms;android-35" \
"build-tools;35.0.0"
- name: Prepare optional release signing - name: Run project build tooling for release AAB
if: github.event_name == 'release' if: github.event_name == 'release'
continue-on-error: true shell: bash
env: env:
CI_VERSION_CODE: ${{ github.run_number }}
CI_ARTIFACT_TIMESTAMP: ${{ github.run_id }}
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
shell: bash
run: |
set -euo pipefail
missing=()
for key in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do
if [ -z "${!key:-}" ]; then
missing+=("$key")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
printf -v missing_csv '%s, ' "${missing[@]}"
missing_csv="${missing_csv%, }"
echo "::error::Release signing secrets missing: ${missing_csv}. Continuing with unsigned/default-signed release AAB."
exit 1
fi
mkdir -p .ci-secrets
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > .ci-secrets/release-keystore.jks
cat > keystore.properties <<EOF
storeFile=.ci-secrets/release-keystore.jks
storePassword=$ANDROID_KEYSTORE_PASSWORD
keyAlias=$ANDROID_KEY_ALIAS
keyPassword=$ANDROID_KEY_PASSWORD
EOF
- name: Make Gradle wrapper executable
run: chmod +x gradlew
- name: Write Android SDK location
shell: bash
run: | run: |
set -euxo pipefail set -euxo pipefail
printf 'sdk.dir=%s/.android/sdk\n' "$HOME" > local.properties chmod +x ./BuildTool.sh
./BuildTool.sh --no-commit --with-aab
- name: Build debug APK
if: github.event_name != 'release'
shell: bash
run: |
set -euxo pipefail
export ANDROID_SDK_ROOT="$HOME/.android/sdk"
export ANDROID_HOME="$ANDROID_SDK_ROOT"
./gradlew --no-daemon clean assembleDebug
- name: Build release AAB
if: github.event_name == 'release'
shell: bash
run: |
set -euxo pipefail
export ANDROID_SDK_ROOT="$HOME/.android/sdk"
export ANDROID_HOME="$ANDROID_SDK_ROOT"
./gradlew --no-daemon bundleRelease
- name: Upload debug APK - name: Upload debug APK
if: github.event_name != 'release' if: github.event_name != 'release'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: debug-apk name: debug-apk
path: app/build/outputs/apk/debug/*.apk path: dist/*.apk
if-no-files-found: error if-no-files-found: error
- name: Attach release AAB to Gitea release - name: Attach release AAB to Gitea release
if: github.event_name == 'release' if: github.event_name == 'release'
env: env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
shell: bash shell: bash
run: | run: |
set -euxo pipefail set -euxo pipefail
token="${GITHUB_TOKEN:-${GITEA_TOKEN:-}}"
if [ -z "$token" ]; then
echo "::error::No release upload token is available from secrets.GITEA_TOKEN or github.token"
exit 1
fi
release_id="$(python3 -c 'import json, os; print(json.load(open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8"))["release"]["id"])')" release_id="$(python3 -c 'import json, os; print(json.load(open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8"))["release"]["id"])')"
aab="$(find app/build/outputs/bundle/release -name '*.aab' | head -n 1)" aab="$(find dist -name '*.aab' | head -n 1)"
curl --fail-with-body \ curl --fail-with-body \
--request POST \ --request POST \
--header "Authorization: token ${GITEA_TOKEN}" \ --header "Authorization: token ${token}" \
--form "attachment=@${aab}" \ --form "attachment=@${aab}" \
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets?name=$(basename "$aab")" "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets?name=$(basename "$aab")"

View File

@@ -3,8 +3,8 @@ set -Eeuo pipefail
APP_NAME="e-SUN" APP_NAME="e-SUN"
MODULE="app" MODULE="app"
API_LEVEL="35" API_LEVEL="36"
BUILD_TOOLS_VERSION="35.0.0" BUILD_TOOLS_VERSION="36.0.0"
DIST_DIR="dist" DIST_DIR="dist"
COMMIT_MSG_FILE="commit-message.txt" COMMIT_MSG_FILE="commit-message.txt"
@@ -47,6 +47,9 @@ for arg in "$@"; do
esac esac
done done
CI_VERSION_CODE="${CI_VERSION_CODE:-}"
CI_ARTIFACT_TIMESTAMP="${CI_ARTIFACT_TIMESTAMP:-}"
need_cmd() { need_cmd() {
command -v "$1" >/dev/null 2>&1 || { command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2 echo "Missing required command: $1" >&2
@@ -111,6 +114,35 @@ gradle_no_daemon() {
./gradlew --no-daemon -Dorg.gradle.daemon=false "$@" ./gradlew --no-daemon -Dorg.gradle.daemon=false "$@"
} }
prepare_optional_release_signing() {
local required=(ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD)
local missing=()
local key
for key in "${required[@]}"; do
if [[ -z "${!key:-}" ]]; then
missing+=("$key")
fi
done
if [[ ${#missing[@]} -eq ${#required[@]} ]]; then
return
fi
if [[ ${#missing[@]} -gt 0 ]]; then
printf 'Release signing secrets missing: %s\n' "${missing[*]}" >&2
exit 1
fi
mkdir -p .ci-secrets
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > .ci-secrets/release-keystore.jks
cat > keystore.properties <<EOF
storeFile=.ci-secrets/release-keystore.jks
storePassword=$ANDROID_KEYSTORE_PASSWORD
keyAlias=$ANDROID_KEY_ALIAS
keyPassword=$ANDROID_KEY_PASSWORD
EOF
}
ensure_gitignore_entries() { ensure_gitignore_entries() {
touch .gitignore touch .gitignore
for entry in \ for entry in \
@@ -134,13 +166,14 @@ increment_versions() {
exit 1 exit 1
fi fi
python3 - "$gradle_file" <<'PY' python3 - "$gradle_file" "$CI_VERSION_CODE" <<'PY'
import re import re
import sys import sys
from pathlib import Path from pathlib import Path
path = Path(sys.argv[1]) path = Path(sys.argv[1])
text = path.read_text() text = path.read_text()
ci_version_code = sys.argv[2].strip()
m_code = re.search(r'\bversionCode\s*=\s*(\d+)', text) m_code = re.search(r'\bversionCode\s*=\s*(\d+)', text)
m_name = re.search(r'\bversionName\s*=\s*"(\d+(?:\.\d+)*)"', text) m_name = re.search(r'\bversionName\s*=\s*"(\d+(?:\.\d+)*)"', text)
@@ -151,7 +184,7 @@ if not m_name:
raise SystemExit('versionName = "<number>" not found (must be numeric, e.g. "1" or "1.0")') raise SystemExit('versionName = "<number>" not found (must be numeric, e.g. "1" or "1.0")')
old_code = int(m_code.group(1)) old_code = int(m_code.group(1))
new_code = old_code + 1 new_code = int(ci_version_code) if ci_version_code else old_code + 1
old_name = m_name.group(1) old_name = m_name.group(1)
parts = old_name.split('.') parts = old_name.split('.')
@@ -159,7 +192,8 @@ parts[-1] = str(int(parts[-1]) + 1)
new_name = '.'.join(parts) new_name = '.'.join(parts)
text = re.sub(r'(\bversionCode\s*=\s*)\d+', r'\g<1>%d' % new_code, text, count=1) text = re.sub(r'(\bversionCode\s*=\s*)\d+', r'\g<1>%d' % new_code, text, count=1)
text = re.sub(r'(\bversionName\s*=\s*)"\d+(?:\.\d+)*"', r'\g<1>"%s"' % new_name, text, count=1) if not ci_version_code:
text = re.sub(r'(\bversionName\s*=\s*)"\d+(?:\.\d+)*"', r'\g<1>"%s"' % new_name, text, count=1)
path.write_text(text) path.write_text(text)
print(new_code) print(new_code)
@@ -193,8 +227,14 @@ copy_artifacts() {
echo "Release AAB not found." >&2 echo "Release AAB not found." >&2
exit 1 exit 1
fi fi
cp "$release_aab" "$DIST_DIR/${APP_NAME}-v${version_code}-release.aab" local release_name
echo "Created: $DIST_DIR/${APP_NAME}-v${version_code}-release.aab" if [[ -n "$CI_ARTIFACT_TIMESTAMP" ]]; then
release_name="${APP_NAME}-${CI_ARTIFACT_TIMESTAMP}-release.aab"
else
release_name="${APP_NAME}-v${version_code}-release.aab"
fi
cp "$release_aab" "$DIST_DIR/$release_name"
echo "Created: $DIST_DIR/$release_name"
fi fi
} }
@@ -233,6 +273,10 @@ main() {
exit 0 exit 0
fi fi
if [[ "$WITH_AAB" == true ]]; then
prepare_optional_release_signing
fi
local version_code local version_code
version_code="$(increment_versions)" version_code="$(increment_versions)"

View File

@@ -16,22 +16,24 @@ Native Kotlin + Jetpack Compose app (API 35) that provides a light-therapy style
- Unit test coverage for Kelvin conversion output range - Unit test coverage for Kelvin conversion output range
## BuildTool.sh ## BuildTool.sh
The project intentionally runs Gradle with daemon disabled (`--no-daemon` and `org.gradle.daemon=false`). The project intentionally runs builds through `./BuildTool.sh` so local development and CI use the same build path.
### Options ### Common commands
```bash ```bash
./BuildTool.sh --setup-only ./BuildTool.sh --setup-only
./BuildTool.sh --debug ./BuildTool.sh --no-commit --apk-only
./BuildTool.sh --release ./BuildTool.sh --no-commit --with-aab
./BuildTool.sh --all
``` ```
### Behavior ### Behavior
- `--setup-only`: prepares wrapper/dependencies without building release artifacts. - `--setup-only`: prepares the local Android SDK/tooling and exits.
- `--debug`: increments version code/name, builds debug APK, renames output to include app name and version code. - `--apk-only`: builds the debug APK.
- `--release`: increments version code/name, builds release AAB, renames output similarly. - `--with-aab`: also builds the release AAB.
- `--all`: runs debug + release flow in one run. - `--no-commit`: skips the post-build git commit step.
- After successful build tasks, script commits all changes with message from `commit-message.txt` (fallback: `No Details`) and then clears that file. - Local builds increment `versionCode` and `versionName`.
- CI can provide `CI_VERSION_CODE` to force the build version code and `CI_ARTIFACT_TIMESTAMP` to control the release AAB filename.
- Artifacts are copied into `dist/`.
- Gitea Actions uses this same script instead of duplicating build logic in YAML.
## Upload Key (Release Signing) ## Upload Key (Release Signing)
For this first increment, release uses default debug signing unless custom signing is added. For this first increment, release uses default debug signing unless custom signing is added.

View File

@@ -20,12 +20,12 @@ val hasReleaseSigning = listOf(
android { android {
namespace = "solutions.tretter.esunandroid" namespace = "solutions.tretter.esunandroid"
compileSdk = 35 compileSdk = 36
defaultConfig { defaultConfig {
applicationId = "solutions.tretter.esunandroid" applicationId = "solutions.tretter.esunandroid"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 36
versionCode = 19 versionCode = 19
versionName = "19" versionName = "19"

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id("com.android.application") version "8.7.2" apply false id("com.android.application") version "8.10.0" apply false
id("org.jetbrains.kotlin.android") version "2.0.21" apply false id("org.jetbrains.kotlin.android") version "2.0.21" apply false
// Required for Kotlin 2.0+ when Compose is enabled // Required for Kotlin 2.0+ when Compose is enabled
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists