Skip to content

Commit 6aa19eb

Browse files
authored
Add Android app build step to CI (#3949)
1 parent 0fce39a commit 6aa19eb

File tree

10 files changed

+497
-42
lines changed

10 files changed

+497
-42
lines changed

.github/workflows/scripts-android.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ jobs:
2121
run: ./scripts/setup-workspace.sh -q -DskipTests
2222
- name: Build Android port
2323
run: ./scripts/build-android-port.sh -q -DskipTests
24+
- name: Build Hello Codename One Android app
25+
run: ./scripts/build-android-app.sh -q -DskipTests

.github/workflows/scripts-ios.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
1-
---
21
name: Test iOS build scripts
32

4-
'on':
3+
on:
54
pull_request:
65
paths:
76
- 'scripts/**'
87
- 'BUILDING.md'
98
push:
10-
branches:
11-
- master
9+
branches: [ master ]
1210
paths-ignore:
1311
- '**/*.md'
1412

1513
jobs:
1614
build-ios:
17-
runs-on: macos-latest
15+
runs-on: macos-15 # pinning macos-15 avoids surprises during the cutover window
16+
timeout-minutes: 25 # hard-stop the job after 25 minutes
17+
concurrency: # ensure only one mac build runs at once
18+
group: mac-ci
19+
cancel-in-progress: false # queue new ones instead of canceling in-flight
20+
1821
steps:
1922
- uses: actions/checkout@v4
23+
2024
- name: Setup workspace
2125
run: ./scripts/setup-workspace.sh -q -DskipTests
26+
# per-step timeout
27+
timeout-minutes: 15
28+
2229
- name: Build iOS port
2330
run: ./scripts/build-ios-port.sh -q -DskipTests
31+
timeout-minutes: 15

BUILDING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export JAVA_HOME=$PWD/jdk-8.0.28+6
2424
# JDK 17 (Linux x64; adjust `_x64_linux_` for your platform)
2525
curl -L -o temurin17.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.16%2B8/OpenJDK17U-jdk_x64_linux_hotspot_17.0.16_8.tar.gz
2626
tar xf temurin17.tar.gz
27-
export JAVA_HOME_17=$PWD/jdk-17.0.16+8
27+
export JAVA17_HOME=$PWD/jdk-17.0.16+8
2828

2929
export PATH="$JAVA_HOME/bin:$PATH"
3030
```
@@ -54,7 +54,7 @@ Note that it's important that `$CN1_BINARIES` points at the locally cloned [cn1-
5454
5555
## Building the Android port
5656
57-
The Android port uses JDK 8 as well. However, it needs a `JAVA_HOME_17` environment variable that points at JDK 17.
57+
The Android port uses JDK 8 as well. However, it needs a `JAVA17_HOME` environment variable that points at JDK 17.
5858
5959
```bash
6060
./scripts/build-android-port.sh -DskipTests

CodenameOne/src/com/codename1/ui/Form.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public class Form extends Container {
179179
public Form() {
180180
this(new FlowLayout());
181181
}
182-
182+
183183
/**
184184
* Constructor that accepts a layout
185185
*

maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateAppProjectMojo.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ private void generateProject() throws MojoExecutionException{
8181

8282
request.setGoals( Collections.singletonList( "archetype:generate" ) );
8383

84+
request.setBatchMode(true);
85+
8486
String[] propsArr = {
8587
"interactiveMode=false",
8688
"archetypeArtifactId=cn1app-archetype",

scripts/build-android-app.sh

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
#!/usr/bin/env bash
2+
# Build a sample "Hello Codename One" Android application using the locally-built Codename One Android port
3+
set -euo pipefail
4+
5+
ba_log() { echo "[build-android-app] $1"; }
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9+
cd "$REPO_ROOT"
10+
11+
TMPDIR="${TMPDIR:-/tmp}"; TMPDIR="${TMPDIR%/}"
12+
DOWNLOAD_DIR="${TMPDIR%/}/codenameone-tools"
13+
ENV_DIR="$DOWNLOAD_DIR/tools"
14+
EXTRA_MVN_ARGS=("$@")
15+
16+
ENV_FILE="$ENV_DIR/env.sh"
17+
ba_log "Loading workspace environment from $ENV_FILE"
18+
if [ -f "$ENV_FILE" ]; then
19+
ba_log "Workspace environment file metadata"
20+
ls -l "$ENV_FILE" | while IFS= read -r line; do ba_log "$line"; done
21+
ba_log "Workspace environment file contents"
22+
sed 's/^/[build-android-app] ENV: /' "$ENV_FILE"
23+
# shellcheck disable=SC1090
24+
source "$ENV_FILE"
25+
ba_log "Loaded environment: JAVA_HOME=${JAVA_HOME:-<unset>} JAVA17_HOME=${JAVA17_HOME:-<unset>} MAVEN_HOME=${MAVEN_HOME:-<unset>}"
26+
else
27+
ba_log "Workspace tools not found. Run scripts/setup-workspace.sh before this script." >&2
28+
exit 1
29+
fi
30+
31+
# --- Tool validations ---
32+
if [ -z "${JAVA_HOME:-}" ] || [ ! -x "$JAVA_HOME/bin/java" ]; then
33+
ba_log "JAVA_HOME validation failed. Current value: ${JAVA_HOME:-<unset>}" >&2
34+
if [ -n "${JAVA_HOME:-}" ]; then
35+
ba_log "Contents of JAVA_HOME directory"
36+
if [ -d "$JAVA_HOME" ]; then ls -l "$JAVA_HOME" | while IFS= read -r line; do ba_log "$line"; done; else ba_log "JAVA_HOME directory does not exist"; fi
37+
fi
38+
ba_log "JAVA_HOME is not set correctly. Please run scripts/setup-workspace.sh first." >&2
39+
exit 1
40+
fi
41+
if [ -z "${JAVA17_HOME:-}" ] || [ ! -x "$JAVA17_HOME/bin/java" ]; then
42+
ba_log "JAVA17_HOME validation failed. Current value: ${JAVA17_HOME:-<unset>}" >&2
43+
if [ -n "${JAVA17_HOME:-}" ]; then
44+
ba_log "Contents of JAVA17_HOME directory"
45+
if [ -d "$JAVA17_HOME" ]; then ls -l "$JAVA17_HOME" | while IFS= read -r line; do ba_log "$line"; done; else ba_log "JAVA17_HOME directory does not exist"; fi
46+
fi
47+
ba_log "JAVA17_HOME is not set correctly. Please run scripts/setup-workspace.sh first." >&2
48+
exit 1
49+
fi
50+
if [ -z "${MAVEN_HOME:-}" ] || [ ! -x "$MAVEN_HOME/bin/mvn" ]; then
51+
ba_log "MAVEN_HOME validation failed. Current value: ${MAVEN_HOME:-<unset>}" >&2
52+
if [ -n "${MAVEN_HOME:-}" ]; then
53+
ba_log "Contents of MAVEN_HOME directory"
54+
if [ -d "$MAVEN_HOME" ]; then ls -l "$MAVEN_HOME" | while IFS= read -r line; do ba_log "$line"; done; else ba_log "MAVEN_HOME directory does not exist"; fi
55+
fi
56+
ba_log "Maven is not available. Please run scripts/setup-workspace.sh first." >&2
57+
exit 1
58+
fi
59+
60+
ba_log "Using JAVA_HOME at $JAVA_HOME"
61+
ba_log "Using JAVA17_HOME at $JAVA17_HOME"
62+
ba_log "Using Maven installation at $MAVEN_HOME"
63+
export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH"
64+
65+
ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}"
66+
if [ -z "$ANDROID_SDK_ROOT" ]; then
67+
if [ -d "/usr/local/lib/android/sdk" ]; then ANDROID_SDK_ROOT="/usr/local/lib/android/sdk"
68+
elif [ -d "$HOME/Android/Sdk" ]; then ANDROID_SDK_ROOT="$HOME/Android/Sdk"; fi
69+
fi
70+
if [ -z "$ANDROID_SDK_ROOT" ] || [ ! -d "$ANDROID_SDK_ROOT" ]; then
71+
ba_log "Android SDK not found. Set ANDROID_SDK_ROOT or ANDROID_HOME to a valid installation." >&2
72+
exit 1
73+
fi
74+
export ANDROID_SDK_ROOT ANDROID_HOME="$ANDROID_SDK_ROOT"
75+
ba_log "Using Android SDK at $ANDROID_SDK_ROOT"
76+
77+
CN1_VERSION=$(awk -F'[<>]' '/<version>/{print $3; exit}' maven/pom.xml)
78+
ba_log "Detected Codename One version $CN1_VERSION"
79+
80+
WORK_DIR="$TMPDIR/cn1-hello-android"
81+
rm -rf "$WORK_DIR"; mkdir -p "$WORK_DIR"
82+
83+
GROUP_ID="com.codenameone.examples"
84+
ARTIFACT_ID="hello-codenameone"
85+
MAIN_NAME="HelloCodenameOne"
86+
87+
SOURCE_PROJECT="$REPO_ROOT/Samples/SampleProjectTemplate"
88+
if [ ! -d "$SOURCE_PROJECT" ]; then
89+
ba_log "Source project template not found at $SOURCE_PROJECT" >&2
90+
exit 1
91+
fi
92+
ba_log "Using source project template at $SOURCE_PROJECT"
93+
94+
LOCAL_MAVEN_REPO="${LOCAL_MAVEN_REPO:-$HOME/.m2/repository}"
95+
ba_log "Using local Maven repository at $LOCAL_MAVEN_REPO"
96+
mkdir -p "$LOCAL_MAVEN_REPO"
97+
MAVEN_CMD=(
98+
"$MAVEN_HOME/bin/mvn" -B -ntp
99+
-Dmaven.repo.local="$LOCAL_MAVEN_REPO"
100+
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
101+
)
102+
103+
# --- Generate app skeleton ---
104+
ba_log "Generating Codename One application skeleton via codenameone-maven-plugin"
105+
(
106+
cd "$WORK_DIR"
107+
xvfb-run -a "${MAVEN_CMD[@]}" -q \
108+
com.codenameone:codenameone-maven-plugin:7.0.204:generate-app-project \
109+
-DgroupId="$GROUP_ID" \
110+
-DartifactId="$ARTIFACT_ID" \
111+
-Dversion=1.0-SNAPSHOT \
112+
-DsourceProject="$SOURCE_PROJECT" \
113+
-Dcn1Version="7.0.204" \
114+
"${EXTRA_MVN_ARGS[@]}"
115+
)
116+
117+
APP_DIR="$WORK_DIR/$ARTIFACT_ID"
118+
119+
# --- Namespace-aware CN1 normalization (xmlstarlet) ---
120+
ROOT_POM="$APP_DIR/pom.xml"
121+
NS="mvn=http://maven.apache.org/POM/4.0.0"
122+
123+
if ! command -v xmlstarlet >/dev/null 2>&1; then
124+
sudo apt-get update -y && sudo apt-get install -y xmlstarlet
125+
fi
126+
127+
# Helper to run xmlstarlet with Maven namespace
128+
x() { xmlstarlet ed -L -N "$NS" "$@"; }
129+
q() { xmlstarlet sel -N "$NS" "$@"; }
130+
131+
# 1) Ensure <properties><codenameone.version> exists/updated (root pom)
132+
if [ "$(q -t -v 'count(/mvn:project/mvn:properties)' "$ROOT_POM" 2>/dev/null || echo 0)" = "0" ]; then
133+
x -s "/mvn:project" -t elem -n properties -v "" "$ROOT_POM"
134+
fi
135+
if [ "$(q -t -v 'count(/mvn:project/mvn:properties/mvn:codenameone.version)' "$ROOT_POM" 2>/dev/null || echo 0)" = "0" ]; then
136+
x -s "/mvn:project/mvn:properties" -t elem -n codenameone.version -v "$CN1_VERSION" "$ROOT_POM"
137+
else
138+
x -u "/mvn:project/mvn:properties/mvn:codenameone.version" -v "$CN1_VERSION" "$ROOT_POM"
139+
fi
140+
141+
# 2) Parent must be a LITERAL version (no property allowed)
142+
while IFS= read -r -d '' P; do
143+
x -u "/mvn:project[mvn:parent/mvn:groupId='com.codenameone' and mvn:parent/mvn:artifactId='codenameone-maven-parent']/mvn:parent/mvn:version" -v "$CN1_VERSION" "$P" || true
144+
done < <(find "$APP_DIR" -type f -name pom.xml -print0)
145+
146+
# 3) Point com.codenameone deps/plugins to ${codenameone.version}
147+
while IFS= read -r -d '' P; do
148+
# Dependencies
149+
x -u "/mvn:project//mvn:dependencies/mvn:dependency[starts-with(mvn:groupId,'com.codenameone')]/mvn:version" -v '${codenameone.version}' "$P" 2>/dev/null || true
150+
# Plugins (regular)
151+
x -u "/mvn:project//mvn:build/mvn:plugins/mvn:plugin[starts-with(mvn:groupId,'com.codenameone')]/mvn:version" -v '${codenameone.version}' "$P" 2>/dev/null || true
152+
# Plugins (pluginManagement)
153+
x -u "/mvn:project//mvn:build/mvn:pluginManagement/mvn:plugins/mvn:plugin[starts-with(mvn:groupId,'com.codenameone')]/mvn:version" -v '${codenameone.version}' "$P" 2>/dev/null || true
154+
done < <(find "$APP_DIR" -type f -name pom.xml -print0)
155+
156+
# 4) Ensure common Maven plugins have a version (Maven requires it even if parent not yet resolved)
157+
declare -A PIN=(
158+
[org.apache.maven.plugins:maven-compiler-plugin]=3.11.0
159+
[org.apache.maven.plugins:maven-resources-plugin]=3.3.1
160+
[org.apache.maven.plugins:maven-surefire-plugin]=3.2.5
161+
[org.apache.maven.plugins:maven-failsafe-plugin]=3.2.5
162+
[org.apache.maven.plugins:maven-jar-plugin]=3.3.0
163+
[org.apache.maven.plugins:maven-clean-plugin]=3.3.2
164+
[org.apache.maven.plugins:maven-deploy-plugin]=3.1.2
165+
[org.apache.maven.plugins:maven-install-plugin]=3.1.2
166+
[org.apache.maven.plugins:maven-assembly-plugin]=3.6.0
167+
[org.apache.maven.plugins:maven-site-plugin]=4.0.0-M15
168+
[com.codenameone:codenameone-maven-plugin]='${codenameone.version}'
169+
)
170+
171+
add_version_if_missing() {
172+
local pom="$1" g="$2" a="$3" v="$4"
173+
# build/plugins
174+
if [ "$(q -t -v "count(/mvn:project/mvn:build/mvn:plugins/mvn:plugin[mvn:groupId='$g' and mvn:artifactId='$a']/mvn:version)" "$pom" 2>/dev/null || echo 0)" = "0" ] &&
175+
[ "$(q -t -v "count(/mvn:project/mvn:build/mvn:plugins/mvn:plugin[mvn:groupId='$g' and mvn:artifactId='$a'])" "$pom" 2>/dev/null || echo 0)" != "0" ]; then
176+
x -s "/mvn:project/mvn:build/mvn:plugins/mvn:plugin[mvn:groupId='$g' and mvn:artifactId='$a']" -t elem -n version -v "$v" "$pom" || true
177+
fi
178+
# pluginManagement/plugins
179+
if [ "$(q -t -v "count(/mvn:project/mvn:build/mvn:pluginManagement/mvn:plugins/mvn:plugin[mvn:groupId='$g' and mvn:artifactId='$a']/mvn:version)" "$pom" 2>/dev/null || echo 0)" = "0" ] &&
180+
[ "$(q -t -v "count(/mvn:project/mvn:build/mvn:pluginManagement/mvn:plugins/mvn:plugin[mvn:groupId='$g' and mvn:artifactId='$a'])" "$pom" 2>/dev/null || echo 0)" != "0" ]; then
181+
x -s "/mvn:project/mvn:build/mvn:pluginManagement/mvn:plugins/mvn:plugin[mvn:groupId='$g' and mvn:artifactId='$a']" -t elem -n version -v "$v" "$pom" || true
182+
fi
183+
}
184+
185+
while IFS= read -r -d '' P; do
186+
for ga in "${!PIN[@]}"; do
187+
add_version_if_missing "$P" "${ga%%:*}" "${ga##*:}" "${PIN[$ga]}"
188+
done
189+
done < <(find "$APP_DIR" -type f -name pom.xml -print0)
190+
191+
# 5) Build with the property set so any lingering refs resolve to the local snapshot
192+
EXTRA_MVN_ARGS+=("-Dcodenameone.version=${CN1_VERSION}")
193+
194+
# (Optional) quick non-fatal checks
195+
xmlstarlet sel -N "$NS" -t -v "/mvn:project/mvn:properties/mvn:codenameone.version" -n "$ROOT_POM" || true
196+
xmlstarlet sel -N "$NS" -t -c "/mvn:project/mvn:build/mvn:plugins" -n "$ROOT_POM" | head -n 60 || true
197+
198+
199+
200+
[ -d "$APP_DIR" ] || { ba_log "Failed to create Codename One application project" >&2; exit 1; }
201+
[ -f "$APP_DIR/build.sh" ] && chmod +x "$APP_DIR/build.sh"
202+
203+
SETTINGS_FILE="$APP_DIR/common/codenameone_settings.properties"
204+
[ -f "$SETTINGS_FILE" ] || { ba_log "codenameone_settings.properties not found at $SETTINGS_FILE" >&2; exit 1; }
205+
206+
# --- Read settings ---
207+
read_prop() { grep -E "^$1=" "$SETTINGS_FILE" | head -n1 | cut -d'=' -f2- | sed 's/^[[:space:]]*//'; }
208+
209+
PACKAGE_NAME="$(read_prop 'codename1.packageName' || true)"
210+
CURRENT_MAIN_NAME="$(read_prop 'codename1.mainName' || true)"
211+
212+
if [ -z "$PACKAGE_NAME" ]; then
213+
PACKAGE_NAME="$GROUP_ID"
214+
ba_log "Package name not found in settings. Falling back to groupId $PACKAGE_NAME"
215+
fi
216+
if [ -z "$CURRENT_MAIN_NAME" ]; then
217+
CURRENT_MAIN_NAME="$MAIN_NAME"
218+
ba_log "Main class name not found in settings. Falling back to target $CURRENT_MAIN_NAME"
219+
fi
220+
221+
# --- Generate Java from external template ---
222+
PACKAGE_PATH="${PACKAGE_NAME//.//}"
223+
JAVA_DIR="$APP_DIR/common/src/main/java/${PACKAGE_PATH}"
224+
mkdir -p "$JAVA_DIR"
225+
MAIN_FILE="$JAVA_DIR/${MAIN_NAME}.java"
226+
227+
TEMPLATE="$SCRIPT_DIR/templates/HelloCodenameOne.java.tmpl"
228+
if [ ! -f "$TEMPLATE" ]; then
229+
ba_log "Template not found: $TEMPLATE" >&2
230+
exit 1
231+
fi
232+
233+
sed -e "s|@PACKAGE@|$PACKAGE_NAME|g" \
234+
-e "s|@MAIN_NAME@|$MAIN_NAME|g" \
235+
"$TEMPLATE" > "$MAIN_FILE"
236+
237+
# --- Ensure codename1.mainName is set ---
238+
ba_log "Setting codename1.mainName to $MAIN_NAME"
239+
if grep -q '^codename1.mainName=' "$SETTINGS_FILE"; then
240+
# GNU sed in CI: in-place edit without backup
241+
sed -E -i 's|^codename1\.mainName=.*$|codename1.mainName='"$MAIN_NAME"'|' "$SETTINGS_FILE"
242+
else
243+
printf '\ncodename1.mainName=%s\n' "$MAIN_NAME" >> "$SETTINGS_FILE"
244+
fi
245+
# Ensure trailing newline
246+
tail -c1 "$SETTINGS_FILE" | read -r _ || echo >> "$SETTINGS_FILE"
247+
248+
# --- Normalize Codename One versions (use Maven Versions Plugin) ---
249+
ba_log "Normalizing Codename One Maven coordinates to $CN1_VERSION"
250+
251+
# --- Build Android gradle project ---
252+
ba_log "Building Android gradle project using Codename One port"
253+
xvfb-run -a "${MAVEN_CMD[@]}" -q -f "$APP_DIR/pom.xml" package \
254+
-DskipTests \
255+
-Dcodename1.platform=android \
256+
-Dcodename1.buildTarget=android-source \
257+
-Dopen=false \
258+
"${EXTRA_MVN_ARGS[@]}"
259+
260+
GRADLE_PROJECT_DIR=$(find "$APP_DIR/android/target" -maxdepth 2 -type d -name "*-android-source" | head -n 1 || true)
261+
if [ -z "$GRADLE_PROJECT_DIR" ]; then
262+
ba_log "Failed to locate generated Android project" >&2
263+
ba_log "Contents of $APP_DIR/android/target:" >&2
264+
ls -R "$APP_DIR/android/target" >&2 || ba_log "Unable to list $APP_DIR/android/target" >&2
265+
exit 1
266+
fi
267+
268+
ba_log "Invoking Gradle build in $GRADLE_PROJECT_DIR"
269+
chmod +x "$GRADLE_PROJECT_DIR/gradlew"
270+
ORIGINAL_JAVA_HOME="$JAVA_HOME"
271+
export JAVA_HOME="$JAVA17_HOME"
272+
(
273+
cd "$GRADLE_PROJECT_DIR"
274+
if command -v sdkmanager >/dev/null 2>&1; then
275+
yes | sdkmanager --licenses >/dev/null 2>&1 || true
276+
elif [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
277+
yes | "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" --licenses >/dev/null 2>&1 || true
278+
fi
279+
./gradlew --no-daemon assembleDebug
280+
)
281+
export JAVA_HOME="$ORIGINAL_JAVA_HOME"
282+
283+
APK_PATH=$(find "$GRADLE_PROJECT_DIR" -path "*/outputs/apk/debug/*.apk" | head -n 1 || true)
284+
[ -n "$APK_PATH" ] || { ba_log "Gradle build completed but no APK was found" >&2; exit 1; }
285+
ba_log "Successfully built Android APK at $APK_PATH"

0 commit comments

Comments
 (0)