diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 73be8315a4..3e9c8efec9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,12 +1,18 @@ name: PR CI -on: +on: pull_request: branches: - master + paths-ignore: + - 'scripts/**' + - '**/*.md' push: branches: - master + paths-ignore: + - 'scripts/**' + - '**/*.md' jobs: build-linux-jdk8-fx: diff --git a/.github/workflows/scripts-android.yml b/.github/workflows/scripts-android.yml new file mode 100644 index 0000000000..b6f2ad8544 --- /dev/null +++ b/.github/workflows/scripts-android.yml @@ -0,0 +1,30 @@ +--- +name: Test Android build scripts + +'on': + pull_request: + paths: + - 'scripts/**' + - 'BUILDING.md' + push: + branches: + - master + paths: + - 'scripts/**' + - 'BUILDING.md' + +jobs: + build-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup workspace + run: ./scripts/setup-workspace.sh -q -DskipTests + - name: Build Android port + run: | + source tools/env.sh + echo "JAVA_HOME=$JAVA_HOME" + java -version + echo "JAVA_HOME_17=$JAVA_HOME_17" + "$JAVA_HOME_17/bin/java" -version + ./scripts/build-android-port.sh -q -DskipTests diff --git a/.github/workflows/scripts-ios.yml b/.github/workflows/scripts-ios.yml new file mode 100644 index 0000000000..ebc72c57a0 --- /dev/null +++ b/.github/workflows/scripts-ios.yml @@ -0,0 +1,28 @@ +--- +name: Test iOS build scripts + +'on': + pull_request: + paths: + - 'scripts/**' + - 'BUILDING.md' + push: + branches: + - master + paths: + - 'scripts/**' + - 'BUILDING.md' + +jobs: + build-ios: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Setup workspace + run: ./scripts/setup-workspace.sh -q -DskipTests + - name: Build iOS port + run: | + source tools/env.sh + echo "JAVA_HOME=$JAVA_HOME" + java -version + ./scripts/build-ios-port.sh -q -DskipTests diff --git a/.gitignore b/.gitignore index 651ab100ba..a78f5bd1de 100644 --- a/.gitignore +++ b/.gitignore @@ -69,4 +69,6 @@ target pom.xml.versionsBackup pom.xml.releaseBackup pom.xml.tag -!maven/**/*.zip \ No newline at end of file +!maven/**/*.zip +/tools/ +tools.sh diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000000..1e504c91fd --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,74 @@ +# Building Codename One + +This guide explains how to build Codename One from source using Maven. It provides reproducible steps to compile the core framework and its Android and iOS ports. + +## Prerequisites + +- **JDK 11** +- **JDK 17** for building the Android port +- **Apache Maven 3.6+** +- macOS with Xcode (required only for the iOS port) + +The helper scripts in the `scripts/` directory download these dependencies when they are not already installed. + +### Installing JDKs on Linux + +Download binaries from [Adoptium](https://adoptium.net): + +```bash +# JDK 11 (Linux x64; adjust `_x64_linux_` for your platform) +curl -L -o temurin11.tar.gz https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.28%2B6/OpenJDK11U-jdk_x64_linux_hotspot_11.0.28_6.tar.gz +tar xf temurin11.tar.gz +export JAVA_HOME=$PWD/jdk-11.0.28+6 + +# JDK 17 (Linux x64; adjust `_x64_linux_` for your platform) +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 +tar xf temurin17.tar.gz +export JAVA_HOME_17=$PWD/jdk-17.0.16+8 + +export PATH="$JAVA_HOME/bin:$PATH" +``` + +## Preparing the workspace + +Clone the repository and run the setup script to download JDK 11 and JDK 17, install Maven, build the core modules, and install the Maven archetypes. This step must be performed before building any ports. + +```bash +git clone https://github.com/codenameone/CodenameOne +cd CodenameOne +./scripts/setup-workspace.sh -DskipTests +source tools/env.sh +``` + +The script runs `mvn install` in `maven/`, installs `cn1-maven-archetypes`, and ensures `~/.codenameone/CodeNameOneBuildClient.jar` is installed by invoking the `cn1:install-codenameone` Maven goal. If that goal fails, the script copies the jar from `maven/CodeNameOneBuildClient.jar`. After the script finishes, `tools/env.sh` contains environment variables for the provisioned JDKs and Maven. Downloads are placed in a temporary directory outside the repository to keep the workspace clean. + +## Building the Android port + +The Android port uses JDK 17 for compilation while Maven runs with JDK 11. Javadoc generation is skipped to avoid known Maven report issues. Run the build script: + +```bash +./scripts/build-android-port.sh -DskipTests +``` + +Artifacts are placed in `maven/android/target`. + +## Building the iOS port + +The iOS port can only be built on macOS with Xcode installed. Run the iOS script: + +```bash +./scripts/build-ios-port.sh -DskipTests +``` + +Artifacts are produced in `maven/ios/target`. + +## Convenience scripts + +- `setup-workspace.sh` – installs Maven, downloads JDK 11 and JDK 17 to a temporary directory, builds the core modules, installs Maven archetypes, provisions the Codename One build client, and writes `tools/env.sh`. +- `build-android-port.sh` – builds the Android port using JDK 11 for Maven and JDK 17 for compilation. +- `build-ios-port.sh` – builds the iOS port on macOS with JDK 11. + +## Further reading + +- Blog post: +- Maven developers guide: diff --git a/scripts/build-android-port.sh b/scripts/build-android-port.sh new file mode 100755 index 0000000000..c12a88648c --- /dev/null +++ b/scripts/build-android-port.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Build Codename One Android port using JDK 11 for Maven and JDK 17 for compilation +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +if [ -f "$ROOT/tools/env.sh" ]; then + source "$ROOT/tools/env.sh" +else + ./scripts/setup-workspace.sh -q -DskipTests + source "$ROOT/tools/env.sh" +fi + +if ! "${JAVA_HOME:-}/bin/java" -version 2>&1 | grep -q '11\.0'; then + ./scripts/setup-workspace.sh -q -DskipTests + source "$ROOT/tools/env.sh" +fi +if ! "${JAVA_HOME:-}/bin/java" -version 2>&1 | grep -q '11\.0'; then + echo "Failed to provision JDK 11" >&2 + exit 1 +fi +if ! "${JAVA_HOME_17:-}/bin/java" -version 2>&1 | grep -q '17\.0'; then + ./scripts/setup-workspace.sh -q -DskipTests + source "$ROOT/tools/env.sh" +fi +if ! "${JAVA_HOME_17:-}/bin/java" -version 2>&1 | grep -q '17\.0'; then + echo "Failed to provision JDK 17" >&2 + exit 1 +fi + +export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH" +"$JAVA_HOME/bin/java" -version +"$JAVA_HOME_17/bin/java" -version +"$MAVEN_HOME/bin/mvn" -version + +BUILD_CLIENT="$HOME/.codenameone/CodeNameOneBuildClient.jar" +if [ ! -f "$BUILD_CLIENT" ]; then + if ! "$MAVEN_HOME/bin/mvn" -q -f maven/pom.xml cn1:install-codenameone "$@"; then + [ -f maven/CodeNameOneBuildClient.jar ] && cp maven/CodeNameOneBuildClient.jar "$BUILD_CLIENT" || true + fi +fi + +"$MAVEN_HOME/bin/mvn" -q -f maven/pom.xml -pl android -am -Dmaven.javadoc.skip=true -Djava.awt.headless=true clean install "$@" diff --git a/scripts/build-ios-port.sh b/scripts/build-ios-port.sh new file mode 100755 index 0000000000..ded6e43fa6 --- /dev/null +++ b/scripts/build-ios-port.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Build Codename One iOS port (macOS only) +set -euo pipefail +if [[ "$(uname)" != "Darwin" ]]; then + echo "The iOS port can only be built on macOS with Xcode installed." >&2 + exit 1 +fi +if ! command -v xcodebuild >/dev/null; then + echo "Xcode command-line tools not found." >&2 + exit 1 +fi +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" +if [ -f "$ROOT/tools/env.sh" ]; then + source "$ROOT/tools/env.sh" +else + ./scripts/setup-workspace.sh -q -DskipTests + source "$ROOT/tools/env.sh" +fi +if ! "${JAVA_HOME:-}/bin/java" -version 2>&1 | grep -q '11\.0'; then + ./scripts/setup-workspace.sh -q -DskipTests + source "$ROOT/tools/env.sh" +fi +if ! "${JAVA_HOME:-}/bin/java" -version 2>&1 | grep -q '11\.0'; then + echo "Failed to provision JDK 11" >&2 + exit 1 +fi +export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH" +"$JAVA_HOME/bin/java" -version +"$MAVEN_HOME/bin/mvn" -version + +BUILD_CLIENT="$HOME/.codenameone/CodeNameOneBuildClient.jar" +if [ ! -f "$BUILD_CLIENT" ]; then + if ! "$MAVEN_HOME/bin/mvn" -q -f maven/pom.xml cn1:install-codenameone "$@"; then + [ -f maven/CodeNameOneBuildClient.jar ] && cp maven/CodeNameOneBuildClient.jar "$BUILD_CLIENT" || true + fi +fi + +"$MAVEN_HOME/bin/mvn" -q -f maven/pom.xml -pl ios -am -Djava.awt.headless=true clean install "$@" diff --git a/scripts/setup-workspace.sh b/scripts/setup-workspace.sh new file mode 100755 index 0000000000..30779f0ec8 --- /dev/null +++ b/scripts/setup-workspace.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +### +# Prepare Codename One workspace by installing Maven, provisioning JDK 8 and JDK 17, +# building core modules, and installing Maven archetypes. +### +set -euo pipefail + +log() { + echo "[setup-workspace] $1" +} + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +ENV_DIR="$ROOT/tools" +mkdir -p "$ENV_DIR" + +# Place downloaded tools outside the repository so it isn't filled with binaries +DOWNLOAD_DIR="${TMPDIR:-/tmp}/codenameone-tools" +mkdir -p "$DOWNLOAD_DIR" + +JAVA_HOME="${JAVA_HOME:-}" +JAVA_HOME_17="${JAVA_HOME_17:-}" +MAVEN_HOME="${MAVEN_HOME:-}" + +log "Detecting host platform" +os_name=$(uname -s) +arch_name=$(uname -m) +case "$os_name" in + Linux) os="linux" ;; + Darwin) os="mac" ;; + *) echo "Unsupported OS: $os_name" >&2; exit 1 ;; +esac +case "$arch_name" in + x86_64|amd64) arch="x64" ;; + arm64|aarch64) arch="aarch64" ;; + *) echo "Unsupported architecture: $arch_name" >&2; exit 1 ;; +esac + +# Determine platform-specific JDK download URLs +arch_jdk8="$arch" +if [ "$os" = "mac" ] && [ "$arch" = "aarch64" ]; then + arch_jdk8="x64" +fi + +JDK8_URL="https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u462-b08/OpenJDK8U-jdk_${arch_jdk8}_${os}_hotspot_8u462b08.tar.gz" +JDK17_URL="https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.16%2B8/OpenJDK17U-jdk_${arch}_${os}_hotspot_17.0.16_8.tar.gz" +MAVEN_URL="https://archive.apache.org/dist/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz" + +install_jdk() { + local url="$1" dest_var="$2" + local tmp="$DOWNLOAD_DIR/jdk.tgz" + log "Downloading JDK from $url" + curl -fL "$url" -o "$tmp" + local top + top=$(tar -tzf "$tmp" 2>/dev/null | head -1 | cut -d/ -f1 || true) + tar -xzf "$tmp" -C "$DOWNLOAD_DIR" + rm "$tmp" + local home="$DOWNLOAD_DIR/$top" + if [ -d "$home/Contents/Home" ]; then + home="$home/Contents/Home" + fi + eval "$dest_var=\"$home\"" +} + +log "Ensuring JDK 8 is available" +if [ ! -x "${JAVA_HOME:-}/bin/java" ] || ! "${JAVA_HOME:-}/bin/java" -version 2>&1 | grep -q '8\.0'; then + log "Provisioning JDK 8 (this may take a while)..." + install_jdk "$JDK8_URL" JAVA_HOME +else + log "Using existing JDK 8 at $JAVA_HOME" +fi + +log "Ensuring JDK 17 is available" +if [ ! -x "${JAVA_HOME_17:-}/bin/java" ] || ! "${JAVA_HOME_17:-}/bin/java" -version 2>&1 | grep -q '17\.0'; then + log "Provisioning JDK 17 (this may take a while)..." + install_jdk "$JDK17_URL" JAVA_HOME_17 +else + log "Using existing JDK 17 at $JAVA_HOME_17" +fi + +log "Ensuring Maven is available" +if ! [ -x "${MAVEN_HOME:-}/bin/mvn" ]; then + tmp="$DOWNLOAD_DIR/maven.tgz" + log "Downloading Maven from $MAVEN_URL" + curl -fL "$MAVEN_URL" -o "$tmp" + tar -xzf "$tmp" -C "$DOWNLOAD_DIR" + rm "$tmp" + MAVEN_HOME="$DOWNLOAD_DIR/apache-maven-3.9.6" +else + log "Using existing Maven at $MAVEN_HOME" +fi + +log "Writing environment to $ENV_DIR/env.sh" +cat > "$ENV_DIR/env.sh" < arrayLength || arrayLength - offset < count) { + throw new ArrayIndexOutOfBoundsException(offset); + } + } + /** * Writes the specified byte {@code oneByte} to the OutputStream. Only the * low order byte of {@code oneByte} is written. diff --git a/vm/JavaAPI/src/java/text/SimpleDateFormat.java b/vm/JavaAPI/src/java/text/SimpleDateFormat.java index 46e9c519a4..49f9b69cca 100644 --- a/vm/JavaAPI/src/java/text/SimpleDateFormat.java +++ b/vm/JavaAPI/src/java/text/SimpleDateFormat.java @@ -315,7 +315,7 @@ String format(Date source, StringBuffer toAppendTo) { if (names == null) { toAppendTo.append(calendar.getTimeZone().getID()); } else { - toAppendTo.append(names[DateFormatSymbols.ZONE_SHORTNAME]); + toAppendTo.append(names[2 /*DateFormatSymbols.ZONE_SHORTNAME */]); } break; case TIMEZONE822_LETTER : @@ -398,7 +398,7 @@ String format(Date source, StringBuffer toAppendTo) { private String[] getTimeZoneDisplayNames(String id) { for (String zoneStrings[] : getDateFormatSymbols().getZoneStrings()) { - if (zoneStrings[DateFormatSymbols.ZONE_ID].equalsIgnoreCase(id)) { + if (zoneStrings[0 /*DateFormatSymbols.ZONE_ID*/].equalsIgnoreCase(id)) { return zoneStrings; } } @@ -926,7 +926,7 @@ int parseTimeZone(String source, int ofs) throws ParseException { for (String timezone[] : getDateFormatSymbols().getZoneStrings()) { for (String z : timezone) { if (z.equalsIgnoreCase(source)) { - TimeZone tz = TimeZone.getTimeZone(timezone[DateFormatSymbols.ZONE_ID]); + TimeZone tz = TimeZone.getTimeZone(timezone[0 /*DateFormatSymbols.ZONE_ID*/]); return -(tz.getRawOffset() / MILLIS_TO_MINUTES); } }