Skip to content
Closed
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
96 changes: 96 additions & 0 deletions .github/workflows/testing-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,99 @@ jobs:
cmake --build build
ctest --test-dir build --build-config RelWithDebInfo --output-on-failure -LE performance -j "$(nproc)"
ctest --test-dir build --build-config RelWithDebInfo --output-on-failure -L performance --repeat until-pass:5

android-apps:
# Deliberately its own job on Ubuntu (not folded into `macos`/HelloiOS's
# job): unlike HelloiOS, cross-compiling for Android has no dependency on
# Xcode or any other macOS-only tooling, so it doesn't need to spend
# macOS CI minutes. GitHub's hosted ubuntu-24.04 image ships an Android
# SDK+NDK by default, same as its macOS image does for Xcode/HelloiOS.
# Both apps build real APKs via their own Gradle/CMake `externalNativeBuild`
# project -- Halide's generators are built as a side effect of that same
# CMake configure (see app/src/main/cpp/CMakeLists.txt in each app), so no
# separate generator pre-build step is needed here.
if: "!contains(github.event.pull_request.labels.*.name, 'skip_buildbots')"
name: HelloAndroid apps
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v7

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpng-dev libjpeg-dev

- name: Sync CI environment
run: |
uv sync --group ci-llvm-main --no-install-project
echo "${GITHUB_WORKSPACE}/.venv/bin" >> "$GITHUB_PATH"
echo "VIRTUAL_ENV=${GITHUB_WORKSPACE}/.venv" >> "$GITHUB_ENV"

- name: Configure LLVM
run: echo "Halide_LLVM_ROOT=$(halide-llvm --prefix)" >> "$GITHUB_ENV"

- name: Configure CMake
run: >-
cmake --preset ci-linux-x86-64
-DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install"

- name: Build and install Halide
run: cmake --build build --target install

- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"

- uses: gradle/actions/setup-gradle@v4

- name: Point Gradle at a CMake >= 3.28
# AGP's own bundled/SDK-managed CMake package defaults to an older
# version than Halide's CMake floor; point it at the system CMake
# (already >= 3.28, since building Halide itself above requires it)
# via the documented `cmake.dir` local.properties key instead.
run: |
cmake_dir="$(dirname "$(dirname "$(command -v cmake)")")"
echo "cmake.dir=${cmake_dir}" | tee apps/HelloAndroid/local.properties apps/HelloAndroidCamera2/local.properties

- name: Build HelloAndroid
working-directory: apps/HelloAndroid
run: ./gradlew assembleDebug "-PHalide_ROOT=${{ github.workspace }}/install"

- name: Build HelloAndroidCamera2
working-directory: apps/HelloAndroidCamera2
run: ./gradlew assembleDebug "-PHalide_ROOT=${{ github.workspace }}/install"

- name: Enable KVM group perms (required by the emulator)
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

# Both apps' bugs found in the prior two commits (missing runtime
# permission request, native buffer bugs) were invisible to a build-only
# check -- they only showed up when the app actually ran and tried to
# open the camera. x86_64 (not arm64-v8a) so the emulator gets KVM
# hardware acceleration on these x86_64 runners.
- name: Run smoke tests on an emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: default
arch: x86_64
profile: pixel_6
emulator-options: >-
-no-window -gpu swiftshader_indirect -no-snapshot -noaudio
-no-boot-anim -camera-back emulated -camera-front emulated
disable-animations: true
script: |
apps/support/android_smoke_test.sh \
apps/HelloAndroid/app/build/outputs/apk/debug/app-debug.apk \
com.example.hellohalide .CameraActivity android.permission.CAMERA \
"requires a buffer of exactly"
apps/support/android_smoke_test.sh \
apps/HelloAndroidCamera2/app/build/outputs/apk/debug/app-debug.apk \
com.example.helloandroidcamera2 .CameraActivity android.permission.CAMERA
4 changes: 2 additions & 2 deletions apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ add_app(fft)
add_app(gaussian_blur)
add_app(hannk)
add_app(harris)
# add_app(HelloAndroid) # TODO(#5374): missing CMake build
# add_app(HelloAndroidCamera2) # TODO(#5374): missing CMake build
# add_app(HelloAndroid) # don't build HelloAndroid here because it is driven by gradle
# add_app(HelloAndroidCamera2) # don't build HelloAndroidCamera2 here because it is driven by gradle
add_app(HelloBaremetal)
# add_app(HelloiOS) # don't build HelloiOS here because it isn't universal.
add_app(HelloPyTorch)
Expand Down
14 changes: 7 additions & 7 deletions apps/HelloAndroid/.gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.gradle/**
gen/**
gradle_build/**
HelloAndroid.iml
.gradle/
build/
app/build/
app/.cxx/
app/.cxx-generators/
local.properties
obj/**
proguard-project.txt
project.properties
.idea/
*.iml
93 changes: 26 additions & 67 deletions apps/HelloAndroid/README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,34 @@
HelloHalide is a simple application which applies a tone curve and sharpening to
a video preview from the camera on a phone or tablet.

This application builds for multiple native ABIs. (At present armeabi,
armeabi-v7a, arm64-v8a, x86_64, and x86 are supported.) Halide code is generated
for each architecture.

This build is meant to use Android command line tools. (An IDE is not required.)
In order to build, the following will be required:

- Android NDK -- This can be downloaded here:
https://developer.android.com/tools/sdk/ndk/index.html After installing, make
sure the top-level directory of the install is in the PATH. (It should contain
an executable ndk-build file.)

- Android SDK -- This can be downloaded here:
http://developer.android.com/sdk/index.html The standalone SDK is desired.
Once downloaded, the "android" program in the tools directory of the install
will need to be run. It should bring up a UI allowing one to choose components
to install. HelloAndroid currently depends on the android-17 release. (It can
easily be made to run on others, but that is what the scripts are setup to
build against.) Make sure the tools directory is on one's PATH.

- Apache Ant -- which can be downloaded here:
http://ant.apache.org/bindownload.cgi make sure the bin directory is on one's
PATH.

If everything is setup correctly, running the build.sh script in this directory,
with the current directory set to here, would build the HelloAndroid apk and
install it on a connected Android device.

# Gradle

To use Gradle create local.properties file in this folder with sdk.dir and
ndk.dir variables defined like so:
This app targets `arm64-v8a` and `x86_64`. The Halide pipeline
(`app/src/main/cpp/generators/hello_generator.cpp`) is compiled once per ABI via
Gradle's `externalNativeBuild`, which drives a single CMake project
(`app/src/main/cpp/CMakeLists.txt`); the Halide generator itself is built with
the host compiler as a side effect of that same CMake configure, so no separate
pre-build step is needed.

## Prerequisites

- A local Halide install (built via CMake -- see the top-level
`doc/BuildingHalideWithCMake.md`).
- Android Studio, or the Android SDK + NDK (Android Studio's SDK Manager can
install the NDK under SDK Tools -- "NDK (Side by side)").
- A CMake >= 3.28. The CMake bundled with the Android SDK by default is often
older than this; if so, point Gradle at a newer one by adding
`cmake.dir=<path to a CMake >= 3.28 install prefix>` to `local.properties`
(e.g. `cmake.dir=/opt/homebrew` for a Homebrew-installed CMake on macOS).

## Building

```
sdk.dir=/Users/joe/Downloads/android-sdk
ndk.dir=/Users/joe/Downloads/android-ndk
./gradlew assembleDebug -PHalide_ROOT=<path/to/halide install>
```

After that run `gradlew build` which will produce .apk file ready for deployment
to the Android device.

On Linux/Mac you can use `build-gradle.sh` to build, deploy and run this sample
application.

Pay attention to the list of platforms supported by your Halide installation.
They are listed in jni/Application.mk APP_ABI variable and in build.gradle archs
map. For example, if your Halide installation was built without arm64-v8a,
remove it from APP_ABI and archs. Both list and map should match, otherwise you
will be getting compilation errors complaining about a missing hello.h file:

```
:compileDebugNdkClassic FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileDebugNdkClassic'.
...
Output:
/private/tmp/7/halide/apps/HelloAndroid/jni/native.cpp:9:30: fatal error: hello.h: No such file or directory
#include "hello.h"

```

# Android Studio

To load project into Android Studio use "File/Import Project..." in Android
Studio and point to apps/HelloAndroid/build.gradle file.
(`Halide_ROOT` can also be provided via an environment variable of the same
name.) This produces `app/build/outputs/apk/debug/app-debug.apk`, installable
with `adb install`.

You will have to edit automatically-generated local.properties file to add
ndk.dir property so it points to your Android NDK installation as described in
Gradle section above.
To build in Android Studio instead, open this directory as a project, add
`Halide_ROOT=<path/to/halide install>` to `gradle.properties` (or
`local.properties`), and use the Run button.
16 changes: 0 additions & 16 deletions apps/HelloAndroid/ant.properties

This file was deleted.

55 changes: 55 additions & 0 deletions apps/HelloAndroid/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
plugins {
id "com.android.application"
}

def halideRoot = project.findProperty("Halide_ROOT") ?: System.getenv("Halide_ROOT")
if (halideRoot == null) {
throw new GradleException(
"Set -PHalide_ROOT=<path/to/halide install> (or the Halide_ROOT " +
"environment variable) to point this build at a Halide installation."
)
}

android {
namespace = "com.example.hellohalide"
compileSdk = 37

defaultConfig {
applicationId = "com.example.hellohalide"
minSdk = 21
targetSdk = 37
versionCode = 1
versionName = "1.0"

ndk {
abiFilters.addAll("arm64-v8a", "x86_64")
}

externalNativeBuild {
cmake {
arguments "-DCMAKE_PREFIX_PATH=${halideRoot}"
}
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

externalNativeBuild {
cmake {
path = "src/main/cpp/CMakeLists.txt"
}
}

buildTypes {
release {
minifyEnabled = false
}
}
}

dependencies {
implementation "androidx.core:core:1.13.1"
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellohalide"
android:versionCode="1"
android:versionName="1.0">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />

<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">

<activity android:name=".CameraActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
Expand All @@ -17,8 +17,4 @@
</activity>

</application>

<uses-permission android:name="android.permission.CAMERA" />
<uses-sdk android:minSdkVersion="17" />

</manifest>
44 changes: 44 additions & 0 deletions apps/HelloAndroid/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.28)
project(HelloAndroid)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)

# This file configures itself twice: once as a host build (to compile the
# generator) and once as this target build (driven by AGP's
# externalNativeBuild, under the NDK cross toolchain). See "Use
# add_halide_generator" in doc/HalideCMakePackage.md.
set(HelloAndroid-halide_generators_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../.cxx-generators")
if (ANDROID)
# NO_CMAKE_FIND_ROOT_PATH: bypass the NDK toolchain's sysroot-only search.
find_program(HelloAndroid_NATIVE_CXX REQUIRED NAMES c++ clang++ g++ NO_CMAKE_FIND_ROOT_PATH)

execute_process(
COMMAND
${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR} -B ${HelloAndroid-halide_generators_ROOT}
-DCMAKE_CXX_COMPILER=${HelloAndroid_NATIVE_CXX}
-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
COMMAND_ERROR_IS_FATAL ANY
)

execute_process(
COMMAND ${CMAKE_COMMAND} --build ${HelloAndroid-halide_generators_ROOT}
COMMAND_ERROR_IS_FATAL ANY
)
endif ()

# Halide is a host-built config package, not part of the Android sysroot.
find_package(Halide REQUIRED CMAKE_FIND_ROOT_PATH_BOTH)

add_halide_generator(hello_generator SOURCES generators/hello_generator.cpp)

if (NOT ANDROID)
# We're just building the Halide generators.
return()
endif ()

add_halide_library(hello FROM HelloAndroid::halide_generators::hello_generator)

add_library(HelloAndroid SHARED hello_wrapper.cpp)
target_link_libraries(HelloAndroid PRIVATE hello log android)
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ JNIEXPORT void JNICALL Java_com_example_hellohalide_CameraPreview_processFrame(
if (dst) {
srcBuf.host = (uint8_t *)src;
srcBuf.set_host_dirty();
srcBuf.dimensions = 2;
srcBuf.dim = srcDim;
srcBuf.dim[0].min = 0;
srcBuf.dim[0].extent = w;
Expand All @@ -97,6 +98,7 @@ JNIEXPORT void JNICALL Java_com_example_hellohalide_CameraPreview_processFrame(
}

dstBuf.host = dst;
dstBuf.dimensions = 2;
dstBuf.dim = dstDim;
dstBuf.dim[0].min = 0;
dstBuf.dim[0].extent = w;
Expand Down
Loading