Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion .github/workflows/fw_icu4c_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ Build | Status
------|-------
GitHub Actions | [![FW Branch CI](https://github.com/sillsdev/icu/actions/workflows/fw_icu4c_ci.yml/badge.svg?branch=fw)](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/) | [![NuGet version](https://img.shields.io/nuget/v/Icu4c.Win.Fw.Lib.svg?style=flat-square)](https://www.nuget.org/packages/Icu4c.Win.Fw.Lib/)
[Icu4c.Win.Fw.Bin](https://www.nuget.org/packages/Icu4c.Win.Fw.Bin/) | [![NuGet version](https://img.shields.io/nuget/v/Icu4c.Win.Fw.Bin.svg?style=flat-square)](https://www.nuget.org/packages/Icu4c.Win.Fw.Bin/)
[Icu4c.Android.Fw.Lib](https://www.nuget.org/packages/Icu4c.Android.Fw.Lib/) | [![NuGet version](https://img.shields.io/nuget/v/Icu4c.Android.Fw.Lib.svg?style=flat-square)](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).
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions icu4c/nugetpackage/assets/android/build/Icu4c.Android.Fw.Lib.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Stamped at pack time; consumers (e.g. AndroidIcuBootstrap) may read this. -->
<IcuFwAndroidMajorVersion Condition="'$(IcuFwAndroidMajorVersion)' == ''">70</IcuFwAndroidMajorVersion>
<IcuFwAndroidPackageDir>$(MSBuildThisFileDirectory)</IcuFwAndroidPackageDir>
<!-- Forward slashes: MSBuild normalizes these on Windows and Unix. -->
<IcuFwAndroidNativeDir>$(IcuFwAndroidPackageDir)native/</IcuFwAndroidNativeDir>
<IcuFwAndroidAssetsDir>$(IcuFwAndroidPackageDir)assets/</IcuFwAndroidAssetsDir>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'android'">
<AndroidNativeLibrary
Include="$(IcuFwAndroidNativeDir)x86_64/*.so"
Condition="Exists('$(IcuFwAndroidNativeDir)x86_64/libicuuc.so')" />
<AndroidNativeLibrary
Include="$(IcuFwAndroidNativeDir)arm64-v8a/*.so"
Condition="Exists('$(IcuFwAndroidNativeDir)arm64-v8a/libicuuc.so')" />
<AndroidNativeLibrary
Include="$(IcuFwAndroidNativeDir)armeabi-v7a/*.so"
Condition="Exists('$(IcuFwAndroidNativeDir)armeabi-v7a/libicuuc.so')" />

<AndroidAsset Include="$(IcuFwAndroidAssetsDir)icudt$(IcuFwAndroidMajorVersion)l.dat"
Condition="Exists('$(IcuFwAndroidAssetsDir)icudt$(IcuFwAndroidMajorVersion)l.dat')">
<LogicalName>%(Filename)%(Extension)</LogicalName>
</AndroidAsset>
</ItemGroup>
</Project>
31 changes: 31 additions & 0 deletions icu4c/nugetpackage/assets/android/icu-android-fw-lib.nuspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<package>
<metadata>
<id>Icu4c.Android.Fw.Lib</id>
<version>$version$</version>
<authors>SIL International</authors>
<copyright>Copyright (c) 2016-2026 SIL International</copyright>
<license type="file">LICENSE</license>
<projectUrl>https://github.com/sillsdev/icu</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<tags>native android maui</tags>
<summary>ICU - International Components for Unicode (Android)</summary>
<description>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.
</description>
<language>en-US</language>
<releaseNotes>
$version$ - FieldWorks ICU Android natives (x86_64, arm64-v8a, armeabi-v7a) plus icudt*l.dat
</releaseNotes>
<references/>
</metadata>
<files>
<file src="LICENSE" target="" />
<file src="build/**/*" target="build" />
</files>
</package>
137 changes: 137 additions & 0 deletions icu4c/packaging/README.android.md
Original file line number Diff line number Diff line change
@@ -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/<version>` 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
<ItemGroup>
<PackageReference Include="Icu4c.Android.Fw.Lib" Version="[70.1.0,71.0.0)" />
</ItemGroup>
```

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).
Loading
Loading