Skip to content
Closed
35 changes: 34 additions & 1 deletion .github/workflows/scripts-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ name: Test Android build scripts

jobs:
build-android:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Setup workspace
Expand All @@ -23,3 +23,36 @@ jobs:
run: ./scripts/build-android-port.sh -q -DskipTests
- name: Build Hello Codename One Android app
run: ./scripts/build-android-app.sh -q -DskipTests
- name: Add Android instrumentation test
run: ./scripts/add-android-instrumentation-test.sh
- name: Run Android instrumentation tests on emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 35
target: google_apis
arch: arm64-v8a
force-avd-creation: true
disable-animations: true
emulator-options: >-
-no-window -no-boot-anim -gpu swiftshader_indirect
-no-snapshot -camera-back none -camera-front none
-accel off
script: ./scripts/run-android-instrumentation-tests.sh
- name: Export Android emulator screenshot path
if: always()
run: |
set -euo pipefail
echo "ANDROID_SCREENSHOT=" >> "$GITHUB_ENV"
if [ -f scripts/.android-build-info ]; then
# shellcheck disable=SC1090
source scripts/.android-build-info
if [ -n "${SCREENSHOT_PATH:-}" ] && [ -f "$SCREENSHOT_PATH" ]; then
echo "ANDROID_SCREENSHOT=$SCREENSHOT_PATH" >> "$GITHUB_ENV"
fi
fi
- name: Upload Android emulator screenshot
if: ${{ always() && env.ANDROID_SCREENSHOT != '' }}
uses: actions/upload-artifact@v4
with:
name: android-emulator-screenshot
path: ${{ env.ANDROID_SCREENSHOT }}
99 changes: 99 additions & 0 deletions scripts/add-android-instrumentation-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# Inject an instrumentation test into the generated Codename One Android Gradle project.
set -euo pipefail

log() { echo "[add-android-instrumentation-test] $1"; }

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$REPO_ROOT"

TMPDIR="${TMPDIR:-/tmp}"
TMPDIR="${TMPDIR%/}"

TOOLS_ENV_DIR="$TMPDIR/codenameone-tools/tools"
ENV_FILE="$TOOLS_ENV_DIR/env.sh"
if [ -f "$ENV_FILE" ]; then
# shellcheck disable=SC1090
source "$ENV_FILE"
else
log "Workspace tools not provisioned. Run scripts/setup-workspace.sh first." >&2
exit 1
fi

if [ -z "${JAVA17_HOME:-}" ] || [ ! -x "$JAVA17_HOME/bin/java" ]; then
log "JAVA17_HOME is not configured. Run scripts/setup-workspace.sh to provision Java 17." >&2
exit 1
fi

export JAVA_HOME="$JAVA17_HOME"
export PATH="$JAVA17_HOME/bin:$PATH"

BUILD_INFO_FILE="$SCRIPT_DIR/.android-build-info"
if [ ! -f "$BUILD_INFO_FILE" ]; then
log "Android build metadata not found at $BUILD_INFO_FILE. Run scripts/build-android-app.sh first." >&2
exit 1
fi

# shellcheck disable=SC1090
source "$BUILD_INFO_FILE"

required_vars=(GRADLE_PROJECT_DIR PACKAGE_NAME ARTIFACT_ID WORK_DIR)
for var in "${required_vars[@]}"; do
if [ -z "${!var:-}" ]; then
log "Required build metadata '$var' is missing. Regenerate the Android project." >&2
exit 1
fi
done

ANDROID_APP_MODULE_DIR="$GRADLE_PROJECT_DIR/app"
if [ ! -d "$ANDROID_APP_MODULE_DIR" ]; then
log "Android app module directory not found at $ANDROID_APP_MODULE_DIR" >&2
exit 1
fi

PACKAGE_PATH="${PACKAGE_NAME//.//}"
ANDROID_TEST_DIR="$ANDROID_APP_MODULE_DIR/src/androidTest/java/$PACKAGE_PATH"
mkdir -p "$ANDROID_TEST_DIR"

TEMPLATE_FILE="$SCRIPT_DIR/templates/AndroidPackageInstrumentationTest.java.tmpl"
if [ ! -f "$TEMPLATE_FILE" ]; then
log "Instrumentation test template not found at $TEMPLATE_FILE" >&2
exit 1
fi

TEST_FILE="$ANDROID_TEST_DIR/PackageNameInstrumentationTest.java"
sed -e "s|@PACKAGE@|$PACKAGE_NAME|g" "$TEMPLATE_FILE" > "$TEST_FILE"
log "Wrote instrumentation test to $TEST_FILE"

GRADLE_APP_BUILD_FILE="$ANDROID_APP_MODULE_DIR/build.gradle"
if [ ! -f "$GRADLE_APP_BUILD_FILE" ]; then
log "Gradle build file not found at $GRADLE_APP_BUILD_FILE" >&2
exit 1
fi

python3 - "$GRADLE_APP_BUILD_FILE" <<'PY'
import re
import sys
from pathlib import Path

build_file = Path(sys.argv[1])
contents = build_file.read_text()

missing_lines = []
if "androidx.test.ext:junit" not in contents:
missing_lines.append("androidTestImplementation 'androidx.test.ext:junit:1.1.5'")
if "androidx.test.espresso:espresso-core" not in contents:
missing_lines.append("androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'")

if missing_lines:
match = re.search(r'dependencies\s*\{', contents)
if not match:
raise SystemExit("dependencies block not found in app build.gradle")
insertion = ''.join(f"\n {line}" for line in missing_lines)
idx = match.end()
contents = contents[:idx] + insertion + contents[idx:]
build_file.write_text(contents)
PY
log "Ensured Android test dependencies are declared in $GRADLE_APP_BUILD_FILE"

38 changes: 36 additions & 2 deletions scripts/build-android-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ TMPDIR="${TMPDIR:-/tmp}"; TMPDIR="${TMPDIR%/}"
DOWNLOAD_DIR="${TMPDIR%/}/codenameone-tools"
ENV_DIR="$DOWNLOAD_DIR/tools"
EXTRA_MVN_ARGS=("$@")
HOST_OS="$(uname -s)"
HOST_ARCH="$(uname -m)"

detect_local_repo() {
local repo="${LOCAL_MAVEN_REPO:-}"
for arg in "${EXTRA_MVN_ARGS[@]}"; do
case "$arg" in
-Dmaven.repo.local=*)
repo="${arg#*=}"
;;
esac
done
if [ -z "$repo" ]; then
repo="$HOME/.m2/repository"
fi
printf '%s' "${repo%/}"
}

ENV_FILE="$ENV_DIR/env.sh"
ba_log "Loading workspace environment from $ENV_FILE"
Expand Down Expand Up @@ -62,6 +79,11 @@ ba_log "Using JAVA17_HOME at $JAVA17_HOME"
ba_log "Using Maven installation at $MAVEN_HOME"
export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH"

if [ "$HOST_OS" = "Linux" ] && { [ "$HOST_ARCH" = "arm64" ] || [ "$HOST_ARCH" = "aarch64" ]; }; then
ba_log "Ensuring codenameone-cef stub artifact is installed for Linux ARM builds"
MAVEN_BIN="$MAVEN_HOME/bin/mvn" LOCAL_MAVEN_REPO="$LOCAL_MAVEN_REPO" ./scripts/ensure-cef-stub.sh
fi

ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}"
if [ -z "$ANDROID_SDK_ROOT" ]; then
if [ -d "/usr/local/lib/android/sdk" ]; then ANDROID_SDK_ROOT="/usr/local/lib/android/sdk"
Expand Down Expand Up @@ -91,7 +113,7 @@ if [ ! -d "$SOURCE_PROJECT" ]; then
fi
ba_log "Using source project template at $SOURCE_PROJECT"

LOCAL_MAVEN_REPO="${LOCAL_MAVEN_REPO:-$HOME/.m2/repository}"
LOCAL_MAVEN_REPO="$(detect_local_repo)"
ba_log "Using local Maven repository at $LOCAL_MAVEN_REPO"
mkdir -p "$LOCAL_MAVEN_REPO"
MAVEN_CMD=(
Expand Down Expand Up @@ -282,4 +304,16 @@ export JAVA_HOME="$ORIGINAL_JAVA_HOME"

APK_PATH=$(find "$GRADLE_PROJECT_DIR" -path "*/outputs/apk/debug/*.apk" | head -n 1 || true)
[ -n "$APK_PATH" ] || { ba_log "Gradle build completed but no APK was found" >&2; exit 1; }
ba_log "Successfully built Android APK at $APK_PATH"
ba_log "Successfully built Android APK at $APK_PATH"

BUILD_INFO_FILE="$SCRIPT_DIR/.android-build-info"
ba_log "Recording Android build metadata for downstream stages at $BUILD_INFO_FILE"
{
printf "APP_DIR=%q\n" "$APP_DIR"
printf "GRADLE_PROJECT_DIR=%q\n" "$GRADLE_PROJECT_DIR"
printf "PACKAGE_NAME=%q\n" "$PACKAGE_NAME"
printf "ARTIFACT_ID=%q\n" "$ARTIFACT_ID"
printf "WORK_DIR=%q\n" "$WORK_DIR"
printf "APK_PATH=%q\n" "$APK_PATH"
} > "$BUILD_INFO_FILE"

26 changes: 26 additions & 0 deletions scripts/build-android-port.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ log "The DOWNLOAD_DIR is ${DOWNLOAD_DIR}"
ENV_DIR="$DOWNLOAD_DIR/tools"
ENV_FILE="$ENV_DIR/env.sh"

host_os="$(uname -s)"
host_arch="$(uname -m)"

detect_local_repo() {
local repo="${LOCAL_MAVEN_REPO:-}"
for arg in "$@"; do
case "$arg" in
-Dmaven.repo.local=*)
repo="${arg#*=}"
;;
esac
done
if [ -z "$repo" ]; then
repo="$HOME/.m2/repository"
fi
printf '%s' "${repo%/}"
}

LOCAL_REPO_PATH="$(detect_local_repo "$@")"
mkdir -p "$LOCAL_REPO_PATH"

load_environment() {
if [ ! -f "$ENV_FILE" ]; then
return 1
Expand Down Expand Up @@ -113,6 +134,11 @@ export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH"
"$JAVA17_HOME/bin/java" -version
"$MAVEN_HOME/bin/mvn" -version

if [ "$host_os" = "Linux" ] && { [ "$host_arch" = "arm64" ] || [ "$host_arch" = "aarch64" ]; }; then
log "Ensuring codenameone-cef stub artifact is installed for Linux ARM builds"
MAVEN_BIN="$MAVEN_HOME/bin/mvn" LOCAL_MAVEN_REPO="$LOCAL_REPO_PATH" ./scripts/ensure-cef-stub.sh
fi

run_maven() {
xvfb-run -a "$MAVEN_HOME/bin/mvn" "$@"
}
Expand Down
78 changes: 78 additions & 0 deletions scripts/ensure-cef-stub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Ensure the codenameone-cef artifact is available in the local Maven repository on
# platforms where the published binaries are unavailable (e.g., Linux ARM).
set -euo pipefail
[ "${DEBUG:-0}" = "1" ] && set -x

log() {
echo "[ensure-cef-stub] $1"
}

os_name="$(uname -s)"
arch_name="$(uname -m)"
if [ "$os_name" != "Linux" ] || { [ "$arch_name" != "arm64" ] && [ "$arch_name" != "aarch64" ]; }; then
log "Host ${os_name}-${arch_name} does not require a CEF stub; skipping"
exit 0
fi

MAVEN_BIN="${MAVEN_BIN:-}"
if [ -z "$MAVEN_BIN" ] && [ -n "${MAVEN_HOME:-}" ] && [ -x "$MAVEN_HOME/bin/mvn" ]; then
MAVEN_BIN="$MAVEN_HOME/bin/mvn"
fi
if [ -z "$MAVEN_BIN" ] && command -v mvn >/dev/null 2>&1; then
MAVEN_BIN="$(command -v mvn)"
fi
if [ -z "$MAVEN_BIN" ] || [ ! -x "$MAVEN_BIN" ]; then
log "Maven executable not found. Set MAVEN_HOME, MAVEN_BIN, or ensure mvn is on PATH." >&2
exit 1
fi

LOCAL_MAVEN_REPO="${LOCAL_MAVEN_REPO:-$HOME/.m2/repository}"
LOCAL_MAVEN_REPO="${LOCAL_MAVEN_REPO%/}"
if [ -z "$LOCAL_MAVEN_REPO" ]; then
log "LOCAL_MAVEN_REPO is empty" >&2
exit 1
fi
mkdir -p "$LOCAL_MAVEN_REPO"

CEF_VERSION="84.4.1-M3"
CEF_COORD="com.codenameone:codenameone-cef:$CEF_VERSION"
CEF_REPO_PATH="$LOCAL_MAVEN_REPO/com/codenameone/codenameone-cef/$CEF_VERSION"
CEF_POM="$CEF_REPO_PATH/codenameone-cef-$CEF_VERSION.pom"

if [ -s "$CEF_POM" ]; then
log "codenameone-cef $CEF_VERSION already present in $LOCAL_MAVEN_REPO"
exit 0
fi

log "Installing stub codenameone-cef $CEF_VERSION artifact into $LOCAL_MAVEN_REPO"
mkdir -p "$CEF_REPO_PATH"
stub_pom="$(mktemp "${TMPDIR:-/tmp}/codenameone-cef-stub.XXXXXX.pom")"
trap 'rm -f "$stub_pom"' EXIT

cat > "$stub_pom" <<'POM'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.codenameone</groupId>
<artifactId>codenameone-cef</artifactId>
<version>84.4.1-M3</version>
<packaging>pom</packaging>
<name>codenameone-cef stub</name>
<description>Stub artifact to satisfy codenameone-cef dependency on platforms where CEF binaries are unavailable.</description>
</project>
POM

"$MAVEN_BIN" -B -ntp org.apache.maven.plugins:maven-install-plugin:3.1.2:install-file \
-Dfile="$stub_pom" \
-DgroupId=com.codenameone \
-DartifactId=codenameone-cef \
-Dversion="$CEF_VERSION" \
-Dpackaging=pom \
-DgeneratePom=false \
-DcreateChecksum=true \
-DlocalRepositoryPath="$LOCAL_MAVEN_REPO"

log "Stub $CEF_COORD installed"
Loading
Loading