diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 48a4d43..f664c16 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -24,3 +24,84 @@ jobs: wget https://github.com/codenameone/CodenameOne/raw/refs/heads/master/maven/CodeNameOneBuildClient.jar -O ~/.codenameone/CodeNameOneBuildClient.jar - name: Build with Maven run: mvn install + + android-emulator: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Java 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Install Android SDK + uses: android-actions/setup-android@v3 + with: + packages: |- + platform-tools + emulator + system-images;android-33;google_apis;x86_64 + + - name: Enable KVM + run: | + sudo apt-get update + sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils + sudo usermod -aG kvm $USER + sudo udevadm control --reload-rules && sudo udevadm trigger + + - name: Build app with Maven Central artifacts + run: | + # Use local BTDemo directory + APP_DIR=$PWD/BTDemo \ + CODENAMEONE_VERSION=7.0.26 \ + BUILD_TARGET=android-source \ + ./scripts/ci/build-thirdparty-app.sh android + + - name: Build APK from Source + run: | + # The generated project should be in scripts/ci/.thirdparty-app/app/target + # We search for it + + # Find the android project directory (contains build.gradle) + # We look inside the work dir used by the script + ANDROID_PROJECT_DIR=$(find scripts/ci/.thirdparty-app/app/target -name "build.gradle" | grep "android" | xargs dirname) + + if [ -z "$ANDROID_PROJECT_DIR" ]; then + echo "Could not find generated Android project" + find scripts/ci/.thirdparty-app/app/target + exit 1 + fi + + echo "Found Android project at $ANDROID_PROJECT_DIR" + cd "$ANDROID_PROJECT_DIR" + + # Ensure gradlew is executable + chmod +x gradlew + + # Build APK + ./gradlew assembleDebug + + - name: Boot emulator and run smoke test + env: + ANDROID_SDK_ROOT: ${{ env.ANDROID_SDK_ROOT }} + run: | + ./scripts/ci/start-android-emulator.sh + + # Find the generated APK + # It should be in the android project dir/app/build/outputs/apk/debug/ + # But we are relative to root now + + APK_PATH=$(find scripts/ci/.thirdparty-app/app/target -name "*.apk" | head -n 1) + + if [ -z "$APK_PATH" ]; then + echo "Could not find built APK" + find scripts/ci/.thirdparty-app/app/target + exit 1 + fi + + echo "Installing APK from $APK_PATH" + adb install -r "$APK_PATH" + + # Start the app + adb shell monkey -p com.codename1.btle 1 diff --git a/BTDemo/CodeNameOneBuildClient.jar b/BTDemo/CodeNameOneBuildClient.jar deleted file mode 100644 index 54b8e97..0000000 Binary files a/BTDemo/CodeNameOneBuildClient.jar and /dev/null differ diff --git a/BTDemo/codenameone_settings.properties b/BTDemo/codenameone_settings.properties new file mode 100644 index 0000000..6816a8b --- /dev/null +++ b/BTDemo/codenameone_settings.properties @@ -0,0 +1,51 @@ +# +#Thu Dec 25 09:28:09 UTC 2025 +codename1.android.keystore= +codename1.android.keystoreAlias= +codename1.android.keystorePassword= +codename1.arg.android.debug=false +codename1.arg.android.release=true +codename1.arg.android.xpermissions= +codename1.arg.ios.add_libs=;CoreBluetooth.framework; +codename1.arg.ios.application_exits=false +codename1.arg.ios.background_modes=,bluetooth-central,bluetooth-peripheral +codename1.arg.ios.dsym=false +codename1.arg.ios.includePush=false +codename1.arg.ios.interface_orientation=UIInterfaceOrientationPortrait\:UIInterfaceOrientationPortraitUpsideDown\:UIInterfaceOrientationLandscapeLeft\:UIInterfaceOrientationLandscapeRight +codename1.arg.ios.newStorageLocation=true +codename1.arg.ios.plistInject=NSBluetoothPeripheralUsageDescription${foobarfoo} +codename1.arg.ios.pods=,Cordova,Cordova ~> 6.1 +codename1.arg.ios.pods.platform=,11.0 +codename1.arg.ios.prerendered_icon=false +codename1.arg.ios.project_type=ios +codename1.arg.ios.statusbar_hidden=false +codename1.arg.ios.testFlight=false +codename1.arg.j2me.nativeThemeConst=0 +codename1.arg.java.version=8 +codename1.arg.rim.obfuscation=false +codename1.arg.win.ver=8 +codename1.description= +codename1.displayName=BTDemo +codename1.icon=icon.png +codename1.ios.appid=Q5GHSKAL2F.com.codename1.btle +codename1.ios.certificate= +codename1.ios.certificatePassword= +codename1.ios.debug.certificate= +codename1.ios.debug.certificatePassword= +codename1.ios.debug.provision= +codename1.ios.provision= +codename1.ios.release.certificate= +codename1.ios.release.certificatePassword= +codename1.ios.release.provision= +codename1.j2me.nativeTheme=nbproject/nativej2me.res +codename1.languageLevel=5 +codename1.mainName=BTDemo +codename1.packageName=com.codename1.btle +codename1.rim.certificatePassword= +codename1.rim.signtoolCsk= +codename1.rim.signtoolDb= +codename1.secondaryTitle=CodenameOne_Template +codename1.vendor=CodenameOne +codename1.version=1.0 +foobarfoo=This is a description of what we are going to do +libVersion=111 diff --git a/BTDemo/dist/BTDemo.jar b/BTDemo/dist/BTDemo.jar deleted file mode 100644 index 11b38cb..0000000 Binary files a/BTDemo/dist/BTDemo.jar and /dev/null differ diff --git a/BTDemo/dist/README.TXT b/BTDemo/dist/README.TXT deleted file mode 100644 index a7a14d3..0000000 --- a/BTDemo/dist/README.TXT +++ /dev/null @@ -1,32 +0,0 @@ -======================== -BUILD OUTPUT DESCRIPTION -======================== - -When you build an Java application project that has a main class, the IDE -automatically copies all of the JAR -files on the projects classpath to your projects dist/lib folder. The IDE -also adds each of the JAR files to the Class-Path element in the application -JAR files manifest file (MANIFEST.MF). - -To run the project from the command line, go to the dist folder and -type the following: - -java -jar "BTDemo.jar" - -To distribute this project, zip up the dist folder (including the lib folder) -and distribute the ZIP file. - -Notes: - -* If two JAR files on the project classpath have the same name, only the first -JAR file is copied to the lib folder. -* Only JAR files are copied to the lib folder. -If the classpath contains other types of files or folders, these files (folders) -are not copied. -* If a library on the projects classpath also has a Class-Path element -specified in the manifest,the content of the Class-Path element has to be on -the projects runtime path. -* To set a main class in a standard Java project, right-click the project node -in the Projects window and choose Properties. Then click Run and enter the -class name in the Main Class field. Alternatively, you can manually type the -class name in the manifest Main-Class element. diff --git a/BTDemo/dist/lib/CLDC11.jar b/BTDemo/dist/lib/CLDC11.jar deleted file mode 100644 index e7cdab1..0000000 Binary files a/BTDemo/dist/lib/CLDC11.jar and /dev/null differ diff --git a/BTDemo/dist/lib/CodenameOne.jar b/BTDemo/dist/lib/CodenameOne.jar deleted file mode 100644 index 43a9f4a..0000000 Binary files a/BTDemo/dist/lib/CodenameOne.jar and /dev/null differ diff --git a/BTDemo/dist/lib/CodenameOne_SRC.zip b/BTDemo/dist/lib/CodenameOne_SRC.zip deleted file mode 100644 index 0e964f7..0000000 Binary files a/BTDemo/dist/lib/CodenameOne_SRC.zip and /dev/null differ diff --git a/BTDemo/dist/lib/JavaSE.jar b/BTDemo/dist/lib/JavaSE.jar deleted file mode 100644 index 261c47e..0000000 Binary files a/BTDemo/dist/lib/JavaSE.jar and /dev/null differ diff --git a/scripts/ci/build-thirdparty-app.sh b/scripts/ci/build-thirdparty-app.sh new file mode 100755 index 0000000..81a53d7 --- /dev/null +++ b/scripts/ci/build-thirdparty-app.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# Simple helper to build a Codename One Maven app using published artifacts +# from Maven Central. Useful for CI jobs that want to validate the toolchain +# without building Codename One from source. +set -euo pipefail + +function usage() { + cat <<'USAGE' +Usage: build-thirdparty-app.sh + +Options are provided via environment variables: + APP_DIR Path to an existing Codename One Maven project. If set, + the script builds this directory directly. + APP_REPO Git URL for a Codename One Maven project to clone. + Ignored when APP_DIR is set. + APP_REF Optional git ref (branch, tag, or commit) to check out + after cloning APP_REPO. + WORK_DIR Temporary workspace for cloned/copied sources. Default: + /scripts/ci/.thirdparty-app + CODENAMEONE_VERSION Codename One runtime version to request from Maven + Central. Defaults to LATEST. + CODENAMEONE_PLUGIN_VERSION + Codename One Maven plugin version. Defaults to + CODENAMEONE_VERSION. + BUILD_TARGET Overrides the codename1.buildTarget value passed to Maven. + Defaults to android-device for android or ios-source for + ios. + +Examples: + # Build a local Maven app for Android + APP_DIR=/path/to/app ./scripts/ci/build-thirdparty-app.sh android + + # Build a remote project for iOS from a specific tag + APP_REPO=https://github.com/example/my-cn1-app \ + APP_REF=v1.2.3 \ + CODENAMEONE_VERSION=8.0.0 \ + ./scripts/ci/build-thirdparty-app.sh ios + + # Use the bundled hello-codenameone sample as a fallback + ./scripts/ci/build-thirdparty-app.sh android +USAGE +} + +if [[ ${1:-} == "--help" || ${1:-} == "-h" ]]; then + usage + exit 0 +fi + +TARGET=${1:-android} +if [[ "$TARGET" != "android" && "$TARGET" != "ios" ]]; then + echo "Unsupported target '$TARGET'. Expected 'android' or 'ios'." >&2 + usage + exit 1 +fi + +CODENAMEONE_VERSION=${CODENAMEONE_VERSION:-LATEST} +CODENAMEONE_PLUGIN_VERSION=${CODENAMEONE_PLUGIN_VERSION:-$CODENAMEONE_VERSION} +WORK_DIR=${WORK_DIR:-"$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)/ci/.thirdparty-app"} +APP_WORK_DIR="$WORK_DIR/app" + +function info() { + echo "[build-thirdparty] $*" +} + +function prepare_workspace() { + rm -rf "$WORK_DIR" + mkdir -p "$WORK_DIR" +} + +function copy_local_app() { + local source_dir=$1 + info "Using local app at $source_dir" + cp -R "$source_dir" "$APP_WORK_DIR" +} + +function clone_app() { + local repo_url=$1 + info "Cloning $repo_url" + git clone --depth 1 "$repo_url" "$APP_WORK_DIR" + if [[ -n ${APP_REF:-} ]]; then + pushd "$APP_WORK_DIR" >/dev/null + git fetch origin "$APP_REF" --depth 1 + git checkout "$APP_REF" + popd >/dev/null + fi +} + +function use_bundled_sample() { + local root_dir + root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd) + local sample_dir="$root_dir/scripts/hellocodenameone" + info "Falling back to bundled sample at $sample_dir" + cp -R "$sample_dir" "$APP_WORK_DIR" +} + +function prepare_app() { + prepare_workspace + if [[ -n ${APP_DIR:-} ]]; then + copy_local_app "$APP_DIR" + return + fi + if [[ -n ${APP_REPO:-} ]]; then + clone_app "$APP_REPO" + return + fi + use_bundled_sample +} + +function resolve_maven() { + local mvnw="$APP_WORK_DIR/mvnw" + if [[ -x "$mvnw" ]]; then + echo "$mvnw" + else + echo "mvn" + fi +} + +function build_target() { + local mvn_cmd + mvn_cmd=$(resolve_maven) + local build_target + case "$TARGET" in + android) + build_target=${BUILD_TARGET:-android-device} + ;; + ios) + build_target=${BUILD_TARGET:-ios-source} + ;; + esac + + pushd "$APP_WORK_DIR" >/dev/null + info "Building $TARGET with Codename One $CODENAMEONE_VERSION" + "$mvn_cmd" \ + -B \ + -U \ + -DskipTests \ + -Dcn1.version="$CODENAMEONE_VERSION" \ + -Dcn1.plugin.version="$CODENAMEONE_PLUGIN_VERSION" \ + -Dcodename1.platform="$TARGET" \ + -Dcodename1.buildTarget="$build_target" \ + package + popd >/dev/null +} + +prepare_app +build_target +info "Build complete. Output available under $APP_WORK_DIR" diff --git a/scripts/ci/start-android-emulator.sh b/scripts/ci/start-android-emulator.sh new file mode 100755 index 0000000..6acb35e --- /dev/null +++ b/scripts/ci/start-android-emulator.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# Helper to provision and boot an Android emulator suitable for CI. +# It installs the requested system image, creates an AVD, boots it +# headlessly, and waits for boot completion. +set -euo pipefail + +function usage() { + cat <<'USAGE' +Usage: start-android-emulator.sh [--help] + +Environment variables: + ANDROID_SDK_ROOT Path to the Android SDK (required). + AVD_NAME Name for the emulator AVD. Default: cn1-ci-api33. + AVD_PACKAGE System image to install. Default: + system-images;android-33;google_apis;x86_64 + AVD_DEVICE Device profile to use. Default: pixel_5 + EMULATOR_PORT Optional TCP port for the emulator console. Default: 5554 + ADB_SERVER_PORT Optional TCP port for the adb server. Default: 5037 + EMULATOR_FLAGS Extra flags passed directly to the emulator binary. + +The script ensures the emulator is booted and ready for adb commands. +It is optimized for CI runners (no-window, KVM, swiftshader GPU). +USAGE +} + +if [[ ${1:-} == "--help" || ${1:-} == "-h" ]]; then + usage + exit 0 +fi + +if [[ -z ${ANDROID_SDK_ROOT:-} ]]; then + echo "ANDROID_SDK_ROOT must be set" >&2 + exit 1 +fi + +AVD_NAME=${AVD_NAME:-cn1-ci-api33} +AVD_PACKAGE=${AVD_PACKAGE:-system-images;android-33;google_apis;x86_64} +AVD_DEVICE=${AVD_DEVICE:-pixel_5} +EMULATOR_PORT=${EMULATOR_PORT:-5554} +ADB_SERVER_PORT=${ADB_SERVER_PORT:-5037} + +PATH="$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH" + +function info() { + echo "[android-emulator] $*" +} + +function ensure_sdk_tools() { + if ! command -v sdkmanager >/dev/null; then + echo "sdkmanager not found in ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >&2 + exit 1 + fi + yes | sdkmanager --licenses >/dev/null + sdkmanager --install "platform-tools" "emulator" "$AVD_PACKAGE" +} + +function create_avd() { + if avdmanager list avd | grep -q "Name: $AVD_NAME"; then + info "AVD $AVD_NAME already exists" + return + fi + echo "no" | avdmanager create avd \ + --name "$AVD_NAME" \ + --package "$AVD_PACKAGE" \ + --device "$AVD_DEVICE" \ + --force +} + +function start_emulator() { + local emulator_bin + emulator_bin=$(command -v emulator) + if [[ ! -x $emulator_bin ]]; then + echo "emulator binary not found" >&2 + exit 1 + fi + info "Starting emulator $AVD_NAME on port $EMULATOR_PORT" + # Avoid stale instances + if pgrep -f "-avd $AVD_NAME" >/dev/null; then + pkill -9 -f "-avd $AVD_NAME" + fi + + # Launch headless emulator + "${emulator_bin}" -avd "$AVD_NAME" \ + -port "$EMULATOR_PORT" \ + -no-window \ + -no-audio \ + -no-boot-anim \ + -gpu swiftshader_indirect \ + -accel on \ + -camera-back none \ + -camera-front none \ + -verbose \ + ${EMULATOR_FLAGS:-} \ + >"$HOME/.android/avd/$AVD_NAME/emulator.log" 2>&1 & +} + +function wait_for_boot() { + export ANDROID_ADB_SERVER_PORT="$ADB_SERVER_PORT" + adb start-server >/dev/null + adb wait-for-device + info "Waiting for boot completion" + local booted="0" + local attempts=0 + until [[ "$booted" == "1" ]]; do + booted=$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') + sleep 3 + ((attempts++)) + if (( attempts > 120 )); then + info "Emulator failed to boot" + tail -n 200 "$HOME/.android/avd/$AVD_NAME/emulator.log" || true + exit 1 + fi + done + info "Emulator booted" + adb shell settings put global window_animation_scale 0 >/dev/null 2>&1 || true + adb shell settings put global transition_animation_scale 0 >/dev/null 2>&1 || true + adb shell settings put global animator_duration_scale 0 >/dev/null 2>&1 || true +} + +info "Using SDK at $ANDROID_SDK_ROOT" +ensure_sdk_tools +create_avd +start_emulator +wait_for_boot +info "Emulator $AVD_NAME is ready for use"