diff --git a/.github/workflows/fw_icu4c_ci.yml b/.github/workflows/fw_icu4c_ci.yml
index 07042587af15..1c948f4d1712 100644
--- a/.github/workflows/fw_icu4c_ci.yml
+++ b/.github/workflows/fw_icu4c_ci.yml
@@ -98,7 +98,7 @@ jobs:
- name: Publish nuget packages
if: github.event_name == 'push'
working-directory: icu4c
- run: nuget push **/*.nupkg -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{ secrets.ICU_NUGET_API_KEY }}
+ run: nuget push **/*.nupkg -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{ secrets.SILLSDEV_PUBLISH_NUGET_ORG }}
# Run ICU4C tests with SIL mod and stub data
@@ -125,6 +125,79 @@ jobs:
echo 'Running ICU4C cintltst'
make -j2 check
+ android-build:
+ needs: set-version-number
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v7
+ with:
+ fetch-depth: 1
+ sparse-checkout-cone-mode: false
+ sparse-checkout: |
+ icu4c/packaging
+ icu4c/source
+ icu4c/nugetpackage/assets/android
+ icu4c/LICENSE
+
+ - name: Set up Android SDK command-line tools
+ uses: android-actions/setup-android@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v6
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Install Android NDK
+ run: |
+ sdkmanager --install 'ndk;27.0.12077973'
+ echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/27.0.12077973" >> "$GITHUB_ENV"
+
+ - name: Cache Android ICU build output
+ uses: actions/cache@v6
+ with:
+ path: icu4c/out/android-icu
+ key: android-icu-${{ runner.os }}-${{ hashFiles('icu4c/packaging/build-android.sh', 'icu4c/packaging/pack-android-nuget.sh', 'icu4c/source/common/unicode/uvernum.h', 'icu4c/source/configure') }}
+
+ - name: Build Android ICU libraries
+ run: |
+ set -xeo pipefail
+ bash icu4c/packaging/build-android.sh --arch=x86_64,arm64-v8a,armeabi-v7a
+
+ - name: Verify Android ICU output contract
+ run: |
+ set -xeuo pipefail
+ major=$(grep -oP '^#define U_ICU_VERSION_MAJOR_NUM \K[0-9]+' icu4c/source/common/unicode/uvernum.h)
+ test -f "icu4c/out/android-icu/icudt${major}l.dat"
+ for abi in x86_64 arm64-v8a armeabi-v7a; do
+ for library in libc++_shared.so libicuuc.so libicui18n.so libicudata.so; do
+ test -f "icu4c/out/android-icu/$abi/$library"
+ done
+ done
+
+ - name: Pack Android NuGet package
+ run: |
+ set -xeuo pipefail
+ bash icu4c/packaging/pack-android-nuget.sh \
+ --version="${{ needs.set-version-number.outputs.version }}" \
+ --output=icu4c/nugetpackage
+
+ - name: Upload Android NuGet package
+ uses: actions/upload-artifact@v7
+ with:
+ name: android-nuget
+ path: icu4c/nugetpackage/Icu4c.Android.Fw.Lib.*.nupkg
+ if-no-files-found: error
+
+ - name: Publish Android NuGet package
+ if: github.event_name == 'push'
+ run: |
+ set -xeuo pipefail
+ dotnet nuget push icu4c/nugetpackage/Icu4c.Android.Fw.Lib.*.nupkg \
+ --source 'https://api.nuget.org/v3/index.json' \
+ --api-key '${{ secrets.SILLSDEV_PUBLISH_NUGET_ORG }}' \
+ --skip-duplicate
+
debian-packaging:
needs: set-version-number
runs-on: ubuntu-latest
diff --git a/README.md b/README.md
index d46ccc544eda..a57965ccb3ec 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,14 @@ Build | Status
------|-------
GitHub Actions | [](https://github.com/sillsdev/icu/actions/workflows/fw_icu4c_ci.yml)
+### NuGet packages (`fw` branch)
+
+Package | Version
+--------|--------
+[Icu4c.Win.Fw.Lib](https://www.nuget.org/packages/Icu4c.Win.Fw.Lib/) | [](https://www.nuget.org/packages/Icu4c.Win.Fw.Lib/)
+[Icu4c.Win.Fw.Bin](https://www.nuget.org/packages/Icu4c.Win.Fw.Bin/) | [](https://www.nuget.org/packages/Icu4c.Win.Fw.Bin/)
+[Icu4c.Android.Fw.Lib](https://www.nuget.org/packages/Icu4c.Android.Fw.Lib/) | [](https://www.nuget.org/packages/Icu4c.Android.Fw.Lib/)
+
# ICU Project info
The ICU project is under the stewardship of [The Unicode Consortium](https://www.unicode.org).
@@ -30,6 +38,7 @@ The ICU project is under the stewardship of [The Unicode Consortium](https://www
#### Subdirectories that we modify and package
- [`icu4c/`](./icu4c/) [ICU for C/C++](./icu4c/readme.html)
+ - [Android native build guide](./icu4c/packaging/README.android.md)
#### Subdirectories that we don't care about
- [`icu4j/`](./icu4j/) [ICU for Java](./icu4j/readme.html)
- [`tools/`](./tools/) Tools
diff --git a/icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.props b/icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.props
new file mode 100644
index 000000000000..e875b61c3dda
--- /dev/null
+++ b/icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.props
@@ -0,0 +1,10 @@
+
+
+
+ 70
+ $(MSBuildThisFileDirectory)
+
+ $(IcuFwAndroidPackageDir)native/
+ $(IcuFwAndroidPackageDir)assets/
+
+
diff --git a/icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.targets b/icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.targets
new file mode 100644
index 000000000000..271502e09218
--- /dev/null
+++ b/icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.targets
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+ %(Filename)%(Extension)
+
+
+
diff --git a/icu4c/nugetpackage/assets/android/icu-android-fw-lib.nuspec b/icu4c/nugetpackage/assets/android/icu-android-fw-lib.nuspec
new file mode 100644
index 000000000000..7da058aaa6ed
--- /dev/null
+++ b/icu4c/nugetpackage/assets/android/icu-android-fw-lib.nuspec
@@ -0,0 +1,31 @@
+
+
+
+ Icu4c.Android.Fw.Lib
+ $version$
+ SIL International
+ Copyright (c) 2016-2026 SIL International
+ LICENSE
+ https://github.com/sillsdev/icu
+ false
+ native android maui
+ ICU - International Components for Unicode (Android)
+ ICU - International Components for Unicode
+
+This package contains the FieldWorks version of ICU4C shared libraries for Android
+(x86_64, arm64-v8a, and armeabi-v7a), including libc++_shared.so and the ICU data
+file for MAUI / .NET Android apps.
+
+The major version number of the nuget package corresponds to the ICU release.
+
+ en-US
+
+$version$ - FieldWorks ICU Android natives (x86_64, arm64-v8a, armeabi-v7a) plus icudt*l.dat
+
+
+
+
+
+
+
+
diff --git a/icu4c/packaging/README.android.md b/icu4c/packaging/README.android.md
new file mode 100644
index 000000000000..f25d202ba78f
--- /dev/null
+++ b/icu4c/packaging/README.android.md
@@ -0,0 +1,137 @@
+# Android ICU4C build
+
+`build-android.sh` cross-compiles the checked-out, SIL-modified ICU4C source in
+`../source` for Android. It does not download, unpack, or substitute an
+upstream ICU release. The current `fw` checkout determines the ICU version and
+therefore the generated data file name.
+
+The build creates host ICU tools first, then uses them with the Android NDK
+toolchain to cross-compile each requested ABI. It validates native-library
+output only; it does not build or run an Android application, emulator, or
+device test.
+
+CI packs the build into the `Icu4c.Android.Fw.Lib` NuGet package for MAUI /
+.NET Android consumers.
+
+## Prerequisites
+
+On Linux or macOS install a POSIX shell environment with `bash`, `make`, `sed`,
+`find`, `sort`, a C/C++ host compiler, and the Android NDK. Android API 21 is
+the default minimum API. The CI configuration uses NDK `27.0.12077973`.
+
+Set `ANDROID_NDK_HOME` to the NDK installation. Alternatively, set
+`ANDROID_HOME` to the Android SDK root and the script selects the latest
+installed `ndk/` directory.
+
+```bash
+export ANDROID_NDK_HOME=/opt/android-ndk-r27
+bash ./icu4c/packaging/build-android.sh --arch=x86_64,arm64-v8a,armeabi-v7a
+```
+
+On Windows, install Git for Windows and an Android NDK installed for Windows,
+then run the script from Git Bash:
+
+```bash
+export ANDROID_NDK_HOME='C:\Android\Sdk\ndk\27.0.12077973'
+bash ./icu4c/packaging/build-android.sh --arch=x86_64,arm64-v8a,armeabi-v7a
+```
+
+Git Bash and WSL need different NDKs: a Windows NDK cannot be reused from WSL,
+and a Linux NDK cannot be used by Git Bash. Under WSL, run the same
+`bash ./icu4c/packaging/build-android.sh` command with an NDK installed for
+Linux inside WSL.
+
+## Controls and cleanup
+
+```bash
+# Default fast validation ABI
+bash ./icu4c/packaging/build-android.sh
+
+# Select an API level and all supported ABIs
+bash ./icu4c/packaging/build-android.sh --api=21 --arch=x86_64,arm64-v8a,armeabi-v7a
+
+# Delete one ABI's build and installed output before rebuilding it
+bash ./icu4c/packaging/build-android.sh --clean-arch=x86_64 --arch=x86_64
+
+# Delete all generated host tools, cross-build trees, data, and libraries
+bash ./icu4c/packaging/build-android.sh --clean
+```
+
+Supported ABI values are `x86_64`, `arm64-v8a`, and `armeabi-v7a`. Use `--help`
+for the full command reference. The generated `icu4c/out/` directory is already
+ignored by this repository.
+
+## Output layout
+
+For the checked-out ICU `70.1`, the output layout is:
+
+```text
+icu4c/out/android-icu/
+ icudt70l.dat
+ x86_64/
+ libc++_shared.so
+ libicuuc.so
+ libicui18n.so
+ libicudata.so
+ arm64-v8a/
+ libc++_shared.so
+ libicuuc.so
+ libicui18n.so
+ libicudata.so
+ armeabi-v7a/
+ libc++_shared.so
+ libicuuc.so
+ libicui18n.so
+ libicudata.so
+```
+
+Cross builds use `--with-data-packaging=archive`, so `libicudata.so` is the
+**stub** data library. Locale data lives in `icudt*l.dat`. Consumers (for
+example `AndroidIcuBootstrap` in icu-dotnet) must load that file via
+`udata_setCommonData` (or equivalent) before using ICU.
+
+Shared libraries are linked with **unversioned** SONAMEs (`libicuuc.so`, and so
+on) so Android APK packaging of those file names alone satisfies `DT_NEEDED`.
+The library major and data-file name are derived from the local ICU source, so
+they change automatically when this branch advances to a different ICU major.
+
+## NuGet package (MAUI / .NET Android)
+
+CI packs unversioned ABI libraries plus `icudt*l.dat` into
+[`Icu4c.Android.Fw.Lib`](https://www.nuget.org/packages/Icu4c.Android.Fw.Lib).
+The major version of the package matches the ICU release (same scheme as
+`Icu4c.Win.Fw.Lib`).
+
+```xml
+
+
+
+```
+
+The package’s MSBuild targets add `AndroidNativeLibrary` entries for
+`x86_64`, `arm64-v8a`, and `armeabi-v7a` and embed `icudt70l.dat` as an
+`AndroidAsset`.
+Consumers do not need manual `AndroidNativeLibrary` / `AndroidAsset`
+ItemGroups. The props file also stamps `IcuFwAndroidMajorVersion` for
+consumers that need the ICU major (for example aligning
+`AndroidIcuBootstrap`).
+
+Pack a local `.nupkg` after a successful Android build:
+
+```bash
+bash ./icu4c/packaging/pack-android-nuget.sh --version=70.1.0
+```
+
+Keep the consumer’s ICU major (for example `AndroidIcuBootstrap` in
+icu-dotnet) aligned with the referenced package major.
+
+## Troubleshooting
+
+- If the script reports a missing NDK, set `ANDROID_NDK_HOME` explicitly.
+- If it reports a mismatched NDK toolchain, install the NDK for the host that
+ executes the script: Linux for Linux/WSL, Windows for Git Bash, or macOS for
+ macOS.
+- Use `--clean` after changing host compilers, NDK versions, or ICU source
+ configuration to ensure host tools and cross-build output are recreated.
+- ICU operations that need locale data will fail until the app loads
+ `icudt*l.dat` into ICU (stub `libicudata.so` alone is not enough).
diff --git a/icu4c/packaging/build-android.sh b/icu4c/packaging/build-android.sh
new file mode 100644
index 000000000000..68f9da700325
--- /dev/null
+++ b/icu4c/packaging/build-android.sh
@@ -0,0 +1,276 @@
+#!/usr/bin/env bash
+# Copyright (c) 2026 SIL Global
+# Cross-compile this checkout's ICU4C sources for Android APK packaging.
+# Adapted from https://github.com/patrickgold/icu4c-android (Apache 2.0).
+
+set -euo pipefail
+
+SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
+ICU4C_DIR="$(realpath "$SCRIPT_DIR/..")"
+ICU_SRC_DIR="$ICU4C_DIR/source"
+OUTPUT_DIR="$ICU4C_DIR/out/android-icu"
+HOST_BUILD_DIR="$OUTPUT_DIR/build/host"
+ARCHS="${ARCHS:-x86_64}"
+API_LEVEL="${API_LEVEL:-21}"
+
+usage() {
+ cat < is used
+ ARCHS Default ABI list when --arch is omitted
+ API_LEVEL Default API level when --api is omitted
+EOF
+}
+
+log() { echo "[build-android] $*"; }
+die() { echo "[build-android] ERROR: $*" >&2; exit 1; }
+
+host_platform() {
+ case "$(uname -s)" in
+ Linux) echo Linux ;;
+ Darwin) echo MacOSX ;;
+ MINGW*|MSYS*|CYGWIN*) echo MinGW ;;
+ *) die "Unsupported host OS: $(uname -s)" ;;
+ esac
+}
+
+detect_ndk_host_tag() {
+ case "$(uname -s)" in
+ Linux) echo "linux-$(uname -m)" ;;
+ Darwin) echo "darwin-$(uname -m)" ;;
+ MINGW*|MSYS*|CYGWIN*) echo windows-x86_64 ;;
+ *) die "Unsupported host OS for NDK toolchain: $(uname -s)" ;;
+ esac
+}
+
+resolve_ndk_toolchain_tag() {
+ local ndk="$1" preferred host_os
+ preferred="$(detect_ndk_host_tag)"
+ if [[ -d "$ndk/toolchains/llvm/prebuilt/$preferred" ]]; then
+ echo "$preferred"
+ return
+ fi
+ host_os="$(uname -s)"
+ if [[ "$host_os" == Linux && -d "$ndk/toolchains/llvm/prebuilt/windows-x86_64" ]]; then
+ die "NDK at $ndk has the Windows toolchain. WSL/Linux requires a Linux NDK installation; set ANDROID_NDK_HOME to it."
+ fi
+ if [[ "$host_os" =~ ^(MINGW|MSYS|CYGWIN) && -d "$ndk/toolchains/llvm/prebuilt/linux-x86_64" ]]; then
+ die "NDK at $ndk has the Linux toolchain. Git Bash requires a Windows NDK installation."
+ fi
+ die "NDK toolchain not found under $ndk/toolchains/llvm/prebuilt/ (expected $preferred)"
+}
+
+resolve_ndk() {
+ if [[ -n "${ANDROID_NDK_HOME:-}" && -d "$ANDROID_NDK_HOME" ]]; then
+ echo "$ANDROID_NDK_HOME"
+ return
+ fi
+ if [[ -n "${ANDROID_HOME:-}" && -d "$ANDROID_HOME/ndk" ]]; then
+ local latest
+ latest="$(ls -d "$ANDROID_HOME/ndk/"* 2>/dev/null | sort -V | tail -1)"
+ [[ -n "$latest" ]] && echo "$latest" && return
+ fi
+ die "Android NDK not found. Set ANDROID_NDK_HOME or ANDROID_HOME."
+}
+
+# Clang/configure triple for the ABI.
+arch_to_target() {
+ case "$1" in
+ x86_64) echo x86_64-linux-android ;;
+ arm64-v8a) echo aarch64-linux-android ;;
+ armeabi-v7a) echo armv7a-linux-androideabi ;;
+ *) die "Unsupported ABI '$1'. Supported ABIs: x86_64, arm64-v8a, armeabi-v7a" ;;
+ esac
+}
+
+# NDK sysroot usr/lib/ name (differs from the clang triple for 32-bit ARM).
+arch_to_sysroot_lib() {
+ case "$1" in
+ x86_64) echo x86_64-linux-android ;;
+ arm64-v8a) echo aarch64-linux-android ;;
+ armeabi-v7a) echo arm-linux-androideabi ;;
+ *) die "Unsupported ABI '$1'. Supported ABIs: x86_64, arm64-v8a, armeabi-v7a" ;;
+ esac
+}
+
+icu_major() {
+ sed -n 's/^#define U_ICU_VERSION_MAJOR_NUM \([0-9][0-9]*\)$/\1/p' \
+ "$ICU_SRC_DIR/common/unicode/uvernum.h" | head -1
+}
+
+parallel_jobs() {
+ nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4
+}
+
+# Refresh config.status in the current build dir when reusing a configured
+# tree (e.g. restored from a CI cache). git checkout rewrites the mtimes of
+# configure and uvernum.h to "now", making them newer than the cached
+# config.status; ICU's Makefile then treats config.status as stale (see the
+# `config.status:` rule in source/Makefile.in) and aborts. Touching it keeps
+# the existing configuration and lets the incremental build proceed.
+refresh_config_status() {
+ [[ -f config.status ]] && touch config.status
+}
+
+copy_ndk_cpp_shared() {
+ local ndk="$1" host_tag="$2" sysroot_lib="$3" install_dir="$4"
+ local cxx_lib="$ndk/toolchains/llvm/prebuilt/$host_tag/sysroot/usr/lib/$sysroot_lib/libc++_shared.so"
+ [[ -f "$cxx_lib" ]] || die "NDK libc++ not found: $cxx_lib"
+ cp -f "$cxx_lib" "$install_dir/"
+}
+
+# Copy versioned ICU shared objects to the unversioned lib*.so names Android
+# packages. SONAMEs are already unversioned (see build_android_arch make override).
+install_android_apk_libs() {
+ local install_dir="$1" lib src
+ for lib in icuuc icui18n icudata; do
+ src="$(find "$install_dir" -maxdepth 1 -type f -name "lib${lib}.so.*" -print 2>/dev/null | sort -V | tail -1 || true)"
+ [[ -n "$src" ]] || die "Missing lib${lib} in $install_dir"
+ cp -f "$src" "$install_dir/lib${lib}.so"
+ done
+}
+
+build_host_icu() {
+ local host_plat
+ mkdir -p "$HOST_BUILD_DIR"
+ pushd "$HOST_BUILD_DIR" >/dev/null
+ if [[ ! -f config/icucross.mk ]]; then
+ host_plat="$(host_platform)"
+ log "Configuring host ICU tools from $ICU_SRC_DIR"
+ "$ICU_SRC_DIR/runConfigureICU" "$host_plat" \
+ --enable-static=no --enable-shared=yes --enable-tests=no \
+ --enable-samples=no --enable-extras=no --enable-draft=yes \
+ --prefix="$HOST_BUILD_DIR/icu_build"
+ else
+ log "Host ICU already configured; rebuilding incrementally"
+ refresh_config_status
+ fi
+ make -j"$(parallel_jobs)"
+ [[ -f config/icucross.mk ]] || die "Host build did not produce config/icucross.mk"
+ popd >/dev/null
+}
+
+install_icu_data_file() {
+ # Declare separately: bash unsets all names in one `local` before assigning.
+ local major="$1"
+ local dat_name="icudt${major}l.dat"
+ local dat_src
+ dat_src="$(find "$HOST_BUILD_DIR/data/out" -type f -name "$dat_name" -print -quit 2>/dev/null || true)"
+ [[ -n "$dat_src" ]] || die "Missing generated ICU data file $dat_name under $HOST_BUILD_DIR/data/out"
+ cp -f "$dat_src" "$OUTPUT_DIR/$dat_name"
+}
+
+build_android_arch() {
+ local abi="$1" ndk="$2" host_tag="$3"
+ local target sysroot_lib build_dir install_dir toolchain
+ target="$(arch_to_target "$abi")"
+ sysroot_lib="$(arch_to_sysroot_lib "$abi")"
+ build_dir="$OUTPUT_DIR/build/android/$abi"
+ install_dir="$OUTPUT_DIR/$abi"
+ toolchain="$ndk/toolchains/llvm/prebuilt/$host_tag"
+ mkdir -p "$build_dir" "$install_dir"
+ log "Cross-compiling ICU for $abi ($target, API $API_LEVEL)"
+ pushd "$build_dir" >/dev/null
+ export PATH="$toolchain/bin:$PATH"
+ export CC="$toolchain/bin/${target}${API_LEVEL}-clang"
+ export CXX="$toolchain/bin/${target}${API_LEVEL}-clang++"
+ export AR="$toolchain/bin/llvm-ar"
+ export RANLIB="$toolchain/bin/llvm-ranlib"
+ export LDFLAGS="-Wl,--gc-sections -Wl,-z,max-page-size=16384"
+ if [[ ! -f config.status ]] || ! grep -qE '^PKGDATA_MODE=common$' icudefs.mk 2>/dev/null; then
+ # archive packaging => PKGDATA_MODE=common (stub libicudata + .dat)
+ rm -f config.status
+ "$ICU_SRC_DIR/configure" \
+ --with-cross-build="$HOST_BUILD_DIR" --host="$target" \
+ --enable-static=no --enable-shared=yes --enable-tests=no \
+ --enable-samples=no --enable-extras=no --enable-draft=yes \
+ --with-data-packaging=archive
+ else
+ refresh_config_status
+ fi
+ # By default ICU gives its shared libraries versioned SONAMEs (e.g.
+ # libicuuc.so.70) and records those versioned names in each library's
+ # dependency list. Android only packages the unversioned lib*.so files
+ # into the APK, so override the SONAME to the unversioned name; otherwise
+ # the loader looks for libicuuc.so.70 and fails.
+ make -j"$(parallel_jobs)" \
+ 'LD_SONAME=-Wl,-soname -Wl,$(notdir $(SO_TARGET))'
+
+ rm -f "$install_dir"/libicu*.so*
+ # With archive packaging, libicudata is the stub (data lives in icudt*l.dat).
+ # Prefer stubdata/ explicitly; also copy other ICU libs from lib/.
+ cp -f "$build_dir/lib"/libicuuc.so* "$install_dir/" 2>/dev/null || die "Missing libicuuc in $build_dir/lib"
+ cp -f "$build_dir/lib"/libicui18n.so* "$install_dir/" 2>/dev/null || die "Missing libicui18n in $build_dir/lib"
+ if [[ -d "$build_dir/stubdata" ]]; then
+ cp -f "$build_dir/stubdata"/libicudata.so* "$install_dir/" \
+ || die "Missing stub libicudata in $build_dir/stubdata"
+ else
+ die "Missing stubdata directory in $build_dir"
+ fi
+ copy_ndk_cpp_shared "$ndk" "$host_tag" "$sysroot_lib" "$install_dir"
+ install_android_apk_libs "$install_dir"
+ popd >/dev/null
+}
+
+CLEAN=no
+CLEAN_ARCHS=""
+for arg in "$@"; do
+ case "$arg" in
+ --help|-h) usage; exit 0 ;;
+ --clean) CLEAN=yes ;;
+ --clean-arch=*) CLEAN_ARCHS="${arg#*=}" ;;
+ --arch=*) ARCHS="${arg#*=}" ;;
+ --api=*) API_LEVEL="${arg#*=}" ;;
+ *) die "Unknown option: $arg" ;;
+ esac
+done
+
+[[ "$API_LEVEL" =~ ^[0-9]+$ ]] || die "API level must be a positive integer"
+[[ -f "$ICU_SRC_DIR/configure" ]] || die "Local ICU source is missing: $ICU_SRC_DIR"
+MAJOR="$(icu_major)"
+[[ -n "$MAJOR" ]] || die "Could not determine ICU major version from uvernum.h"
+
+if [[ "$CLEAN" == yes ]]; then
+ rm -rf "$OUTPUT_DIR"
+ log "Cleaned $OUTPUT_DIR"
+ exit 0
+fi
+if [[ -n "$CLEAN_ARCHS" ]]; then
+ IFS=',' read -ra CLEAN_ARCH_LIST <<< "$CLEAN_ARCHS"
+ for abi in "${CLEAN_ARCH_LIST[@]}"; do
+ abi="$(echo "$abi" | xargs)"; [[ -z "$abi" ]] && continue
+ arch_to_target "$abi" >/dev/null
+ rm -rf "$OUTPUT_DIR/build/android/$abi" "$OUTPUT_DIR/$abi"
+ log "Cleaned $abi"
+ done
+fi
+
+for cmd in make sed find sort; do command -v "$cmd" >/dev/null || die "Required command not found: $cmd"; done
+NDK="$(resolve_ndk)"
+HOST_TAG="$(resolve_ndk_toolchain_tag "$NDK")"
+log "Using local ICU $MAJOR.x sources, NDK $NDK, ABIs $ARCHS"
+build_host_icu
+install_icu_data_file "$MAJOR"
+IFS=',' read -ra ARCH_LIST <<< "$ARCHS"
+for abi in "${ARCH_LIST[@]}"; do
+ abi="$(echo "$abi" | xargs)"; [[ -z "$abi" ]] && continue
+ build_android_arch "$abi" "$NDK" "$HOST_TAG"
+done
+log "Done. APK-ready libraries are in $OUTPUT_DIR/{abi}/"
diff --git a/icu4c/packaging/pack-android-nuget.sh b/icu4c/packaging/pack-android-nuget.sh
new file mode 100644
index 000000000000..1be3f77003b4
--- /dev/null
+++ b/icu4c/packaging/pack-android-nuget.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env bash
+# Copyright (c) 2026 SIL Global
+# Stage Android ICU build output and pack Icu4c.Android.Fw.Lib.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ICU4C_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+INPUT_DIR="${INPUT_DIR:-$ICU4C_DIR/out/android-icu}"
+ASSETS_DIR="$ICU4C_DIR/nugetpackage/assets/android"
+OUTPUT_DIR="${OUTPUT_DIR:-$ICU4C_DIR/nugetpackage}"
+PKG_VERSION=""
+ARCHS="${ARCHS:-x86_64,arm64-v8a,armeabi-v7a}"
+
+usage() {
+ cat <&2; exit 1; }
+
+for arg in "$@"; do
+ case "$arg" in
+ --help|-h) usage; exit 0 ;;
+ --version=*) PKG_VERSION="${arg#*=}" ;;
+ --input=*) INPUT_DIR="${arg#*=}" ;;
+ --output=*) OUTPUT_DIR="${arg#*=}" ;;
+ --arch=*) ARCHS="${arg#*=}" ;;
+ *) die "Unknown option: $arg" ;;
+ esac
+done
+
+[[ -n "$PKG_VERSION" ]] || die "Missing --version"
+[[ -d "$INPUT_DIR" ]] || die "Input directory not found: $INPUT_DIR"
+[[ -f "$ASSETS_DIR/build/Icu4c.Android.Fw.Lib.targets" ]] || die "Missing targets under $ASSETS_DIR/build"
+[[ -f "$ICU4C_DIR/LICENSE" ]] || die "Missing LICENSE at $ICU4C_DIR/LICENSE"
+
+command -v dotnet >/dev/null || die "dotnet SDK is required to pack the NuGet package"
+command -v unzip >/dev/null || die "unzip is required to verify the NuGet package layout"
+
+STAGE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/icu-android-nuget.XXXXXX")"
+cleanup() { rm -rf "$STAGE_DIR"; }
+trap cleanup EXIT
+
+mkdir -p "$STAGE_DIR/build/native" "$STAGE_DIR/build/assets"
+cp -f "$ICU4C_DIR/LICENSE" "$STAGE_DIR/"
+cp -f "$ASSETS_DIR/build/Icu4c.Android.Fw.Lib.props" "$STAGE_DIR/build/"
+cp -f "$ASSETS_DIR/build/Icu4c.Android.Fw.Lib.targets" "$STAGE_DIR/build/"
+
+dat_files=("$INPUT_DIR"/icudt*l.dat)
+[[ -f "${dat_files[0]}" ]] || die "Missing icudt*l.dat under $INPUT_DIR"
+cp -f "${dat_files[@]}" "$STAGE_DIR/build/assets/"
+
+dat_base="$(basename "${dat_files[0]}")"
+[[ "$dat_base" =~ ^icudt([0-9]+)l\.dat$ ]] || die "Unexpected data file name: $dat_base"
+ICU_MAJOR="${BASH_REMATCH[1]}"
+sed -i.bak "s/[0-9][0-9]*<\/IcuFwAndroidMajorVersion>/${ICU_MAJOR}<\/IcuFwAndroidMajorVersion>/" \
+ "$STAGE_DIR/build/Icu4c.Android.Fw.Lib.props"
+rm -f "$STAGE_DIR/build/Icu4c.Android.Fw.Lib.props.bak"
+grep -q ">${ICU_MAJOR}" "$STAGE_DIR/build/Icu4c.Android.Fw.Lib.props" \
+ || die "Failed to stamp IcuFwAndroidMajorVersion=${ICU_MAJOR} into props"
+
+IFS=',' read -ra ARCH_LIST <<< "$ARCHS"
+for abi in "${ARCH_LIST[@]}"; do
+ abi="$(echo "$abi" | xargs)"
+ [[ -z "$abi" ]] && continue
+ src="$INPUT_DIR/$abi"
+ [[ -d "$src" ]] || die "Missing ABI directory: $src"
+ dest="$STAGE_DIR/build/native/$abi"
+ mkdir -p "$dest"
+ for library in libc++_shared.so libicuuc.so libicui18n.so libicudata.so; do
+ [[ -f "$src/$library" ]] || die "Missing $src/$library"
+ cp -f "$src/$library" "$dest/"
+ done
+done
+
+# Emit explicit PackagePath entries per folder. A single build/** with
+# PackagePath=build/%(RecursiveDir) still dropped build/assets in CI.
+pack_items="$(mktemp "${TMPDIR:-/tmp}/icu-android-nuget-items.XXXXXX")"
+{
+ echo ' '
+ echo ' '
+ echo ' '
+ echo ' '
+ for abi in "${ARCH_LIST[@]}"; do
+ abi="$(echo "$abi" | xargs)"
+ [[ -z "$abi" ]] && continue
+ echo " "
+ done
+} > "$pack_items"
+
+cat > "$STAGE_DIR/Icu4c.Android.Fw.Lib.csproj" <
+
+ netstandard2.0
+ true
+ false
+ true
+ Icu4c.Android.Fw.Lib
+ $PKG_VERSION
+ ICU - International Components for Unicode (Android)
+ SIL International
+ Copyright (c) 2016-2026 SIL International
+ https://github.com/sillsdev/icu
+ LICENSE
+ native;android;maui
+ FieldWorks ICU4C shared libraries for Android (x86_64, arm64-v8a, armeabi-v7a) for MAUI / .NET Android apps. The major version number corresponds to the ICU release.
+ $PKG_VERSION - FieldWorks ICU $ICU_MAJOR Android natives (x86_64, arm64-v8a, armeabi-v7a) plus icudt${ICU_MAJOR}l.dat
+ false
+
+
+$(cat "$pack_items")
+
+
+EOF
+rm -f "$pack_items"
+
+mkdir -p "$OUTPUT_DIR"
+log "Packing Icu4c.Android.Fw.Lib $PKG_VERSION from $INPUT_DIR"
+dotnet pack "$STAGE_DIR/Icu4c.Android.Fw.Lib.csproj" \
+ -o "$OUTPUT_DIR" \
+ --nologo
+
+nupkg="$OUTPUT_DIR/Icu4c.Android.Fw.Lib.$PKG_VERSION.nupkg"
+[[ -f "$nupkg" ]] || die "Expected package not found: $nupkg"
+
+verify_dir="$(mktemp -d "${TMPDIR:-/tmp}/icu-android-nuget-verify.XXXXXX")"
+unzip -q "$nupkg" -d "$verify_dir"
+fail_verify() {
+ echo "[pack-android-nuget] ERROR: $*" >&2
+ echo "[pack-android-nuget] Package contents:" >&2
+ (cd "$verify_dir" && find . -type f | sort) >&2 || true
+ rm -rf "$verify_dir"
+ exit 1
+}
+[[ -f "$verify_dir/build/Icu4c.Android.Fw.Lib.props" ]] || fail_verify "nupkg missing build props"
+[[ -f "$verify_dir/build/Icu4c.Android.Fw.Lib.targets" ]] || fail_verify "nupkg missing build targets"
+[[ -f "$verify_dir/build/assets/icudt${ICU_MAJOR}l.dat" ]] || fail_verify "nupkg missing build/assets/icudt${ICU_MAJOR}l.dat"
+for abi in "${ARCH_LIST[@]}"; do
+ abi="$(echo "$abi" | xargs)"
+ [[ -z "$abi" ]] && continue
+ for library in libc++_shared.so libicuuc.so libicui18n.so libicudata.so; do
+ [[ -f "$verify_dir/build/native/$abi/$library" ]] \
+ || fail_verify "nupkg missing build/native/$abi/$library"
+ done
+done
+rm -rf "$verify_dir"
+
+log "Created $nupkg"