Skip to content

Commit

Permalink
Compile android in build_examples.py and move android compilation out…
Browse files Browse the repository at this point in the history
…side of Github CI (project-chip#8862)

* Starting to build android: native library seems to build

* Validate that sdkmanager is executable, then ensure ndk licenses are accepted at build time

* Compiling the apk now works (no jni so copying yet though)

* Restyle fixes

* Apk build now includes .so files for jni

* PyFormat

* Another pyformat on factory/targets

* Move android to workflow_dispatch

* Fix unit tests, pyformat android and test.py

* Update error text for writable check on android home licenses

* Accept the possibility that the android licenses folder does not exist at all

* Update to _build and build_outputs method naming for android

* Do not use rsync and instead do explicit path updates for android native libraries

* Remove obsolete code for output calculation

* Update GH Actions to use the updated examples build script

* Fix android build examples command line - script to be executed must be a string apparently
  • Loading branch information
andy31415 authored and Nikita committed Sep 23, 2021
1 parent f208c24 commit e8af1e1
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 76 deletions.
50 changes: 3 additions & 47 deletions .github/workflows/android.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,51 +54,7 @@ jobs:
path: |
.environment/gn_out/.ninja_log
.environment/pigweed-venv/*.log
- name: Build arm libs
timeout-minutes: 2
- name: Build android examples
run: |
./scripts/examples/android_app.sh
env:
BUILD_TYPE: android_arm
TARGET_CPU: arm
- name: Build arm App
timeout-minutes: 5
run: |
yes | "$ANDROID_HOME"/tools/bin/sdkmanager --licenses
cd src/android/CHIPTool
./gradlew build
env:
BUILD_TYPE: android_arm
TARGET_CPU: arm
- name: Build arm64 libs
timeout-minutes: 2
run: |
./scripts/examples/android_app.sh
env:
BUILD_TYPE: android_arm64
TARGET_CPU: arm64
- name: Build arm64 App
timeout-minutes: 5
run: |
yes | "$ANDROID_HOME"/tools/bin/sdkmanager --licenses
cd src/android/CHIPTool
./gradlew build
env:
BUILD_TYPE: android_arm64
TARGET_CPU: arm64
- name: Build x64 libs
timeout-minutes: 2
run: |
./scripts/examples/android_app.sh
env:
BUILD_TYPE: android_x64
TARGET_CPU: x64
- name: Build x64 App
timeout-minutes: 5
run: |
yes | "$ANDROID_HOME"/tools/bin/sdkmanager --licenses
cd src/android/CHIPTool
./gradlew build
env:
BUILD_TYPE: android_x64
TARGET_CPU: x64
./scripts/run_in_build_env.sh \
"./scripts/build/build_examples.py --platform android build"
25 changes: 18 additions & 7 deletions scripts/build/build/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
from typing import Set

from builders.builder import Builder
from builders.host import HostBuilder, HostApp
from builders.qpg import QpgBuilder
from builders.esp32 import Esp32Builder, Esp32Board, Esp32App

from builders.android import AndroidBoard, AndroidBuilder
from builders.efr32 import Efr32Builder, Efr32App, Efr32Board
from builders.esp32 import Esp32Builder, Esp32Board, Esp32App
from builders.host import HostBuilder, HostApp
from builders.nrf import NrfApp, NrfBoard, NrfConnectBuilder
from builders.qpg import QpgBuilder

from .targets import Application, Board, Platform

Expand Down Expand Up @@ -84,13 +86,16 @@ def Create(self, runner, __board_key: Board, __app_key: Application,
Platform.QPG: Matcher(QpgBuilder),
Platform.EFR32: Matcher(Efr32Builder),
Platform.NRF: Matcher(NrfConnectBuilder),
Platform.ANDROID: Matcher(AndroidBuilder),
}

# Matrix of what can be compiled and what build options are required
# by such compilation
_MATCHERS[Platform.HOST].AcceptBoard(Board.NATIVE)
_MATCHERS[Platform.HOST].AcceptApplication(Application.ALL_CLUSTERS, app=HostApp.ALL_CLUSTERS)
_MATCHERS[Platform.HOST].AcceptApplication(Application.CHIP_TOOL, app=HostApp.CHIP_TOOL)
_MATCHERS[Platform.HOST].AcceptApplication(
Application.ALL_CLUSTERS, app=HostApp.ALL_CLUSTERS)
_MATCHERS[Platform.HOST].AcceptApplication(
Application.CHIP_TOOL, app=HostApp.CHIP_TOOL)

_MATCHERS[Platform.ESP32].AcceptBoard(Board.DEVKITC, board=Esp32Board.DevKitC)
_MATCHERS[Platform.ESP32].AcceptBoard(Board.M5STACK, board=Esp32Board.M5Stack)
Expand All @@ -114,13 +119,18 @@ def Create(self, runner, __board_key: Board, __app_key: Application,
_MATCHERS[Platform.EFR32].AcceptApplication(
Application.WINDOW_COVERING, app=Efr32App.WINDOW_COVERING)


_MATCHERS[Platform.NRF].AcceptBoard(Board.NRF5340, board=NrfBoard.NRF5340)
_MATCHERS[Platform.NRF].AcceptBoard(Board.NRF52840, board=NrfBoard.NRF52840)
_MATCHERS[Platform.NRF].AcceptApplication(Application.LOCK, app=NrfApp.LOCK)
_MATCHERS[Platform.NRF].AcceptApplication(Application.LIGHT, app=NrfApp.LIGHT)
_MATCHERS[Platform.NRF].AcceptApplication(Application.SHELL, app=NrfApp.SHELL)

_MATCHERS[Platform.ANDROID].AcceptBoard(Board.ARM, board=AndroidBoard.ARM)
_MATCHERS[Platform.ANDROID].AcceptBoard(Board.ARM64, board=AndroidBoard.ARM64)
_MATCHERS[Platform.ANDROID].AcceptBoard(Board.X64, board=AndroidBoard.X64)
_MATCHERS[Platform.ANDROID].AcceptApplication(Application.CHIP_TOOL)


class BuilderFactory:
"""Creates application builders."""

Expand All @@ -140,7 +150,8 @@ def Create(self, platform: Platform, board: Board, app: Application, enable_flas
output_prefix=self.output_prefix)

if builder:
builder.SetIdentifier(platform.name.lower(), board.name.lower(), app.name.lower())
builder.SetIdentifier(platform.name.lower(), board.name.lower(),
app.name.lower())
builder.enable_flashbundle(enable_flashbundle)

return builder
Expand Down
6 changes: 6 additions & 0 deletions scripts/build/build/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Platform(IntEnum):
ESP32 = auto()
EFR32 = auto()
NRF = auto()
ANDROID = auto()

@property
def ArgName(self):
Expand Down Expand Up @@ -59,6 +60,11 @@ class Board(IntEnum):
NRF52840 = auto()
NRF5340 = auto()

# Android platform
ARM = auto()
ARM64 = auto()
X64 = auto()

@property
def ArgName(self):
return self.name.lower()
Expand Down
163 changes: 163 additions & 0 deletions scripts/build/builders/android.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Copyright (c) 2021 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os
import shlex

from enum import Enum, auto

from .builder import Builder


class AndroidBoard(Enum):
ARM = auto()
ARM64 = auto()
X64 = auto()

def TargetCpuName(self):
if self == AndroidBoard.ARM:
return 'arm'
elif self == AndroidBoard.ARM64:
return 'arm64'
elif self == AndroidBoard.X64:
return 'x64'
else:
raise Exception('Unknown board type: %r' % self)

def AbiName(self):
if self == AndroidBoard.ARM:
return 'armeabi-v7a'
elif self == AndroidBoard.ARM64:
return 'arm64-v8a'
elif self == AndroidBoard.X64:
return 'x86_64'
else:
raise Exception('Unknown board type: %r' % self)


class AndroidBuilder(Builder):

def __init__(self, root, runner, output_prefix: str, board: AndroidBoard):
super(AndroidBuilder, self).__init__(root, runner, output_prefix)
self.board = board

def validate_build_environment(self):
for k in ['ANDROID_NDK_HOME', 'ANDROID_HOME']:
if k not in os.environ:
raise Exception(
'Environment %s missing, cannot build android libraries' % k)

# SDK manager must be runnable to 'accept licenses'
sdk_manager = os.path.join(os.environ['ANDROID_HOME'], 'tools', 'bin',
'sdkmanager')
if not (os.path.isfile(sdk_manager) and os.access(sdk_manager, os.X_OK)):
raise Exception("'%s' is not executable by the current user" %
sdk_manager)

# In order to accept a license, the licenses folder is updated with the hash of the
# accepted license
android_home = os.environ['ANDROID_HOME']
licenses = os.path.join(android_home, 'licenses')
if not os.path.exists(licenses):
# Initial install may not have licenses at all
if not os.access(android_home, os.W_OK):
raise Exception(
"'%s' is NOT writable by the current user (needed to create licenses folder for accept)"
% android_home)

elif not os.access(licenses, os.W_OK):
raise Exception(
"'%s' is NOT writable by the current user (needed to accept licenses)"
% licenses)

def generate(self):
if not os.path.exists(self.output_dir):
# NRF does a in-place update of SDK tools
if not self._runner.dry_run:
self.validate_build_environment()

gn_args = {}
gn_args['target_os'] = 'android'
gn_args['target_cpu'] = self.board.TargetCpuName()
gn_args['android_ndk_root'] = os.environ['ANDROID_NDK_HOME']
gn_args['android_sdk_root'] = os.environ['ANDROID_HOME']

args = '--args=%s' % (' '.join([
'%s="%s"' % (key, shlex.quote(value))
for key, value in gn_args.items()
]))

self._Execute([
'gn', 'gen', '--check', '--fail-on-unused-args', self.output_dir, args
],
title='Generating ' + self.identifier)

self._Execute([
'bash', '-c',
'yes | %s/tools/bin/sdkmanager --licenses >/dev/null' %
os.environ['ANDROID_HOME']
],
title='Accepting NDK licenses')

def _build(self):
self._Execute(['ninja', '-C', self.output_dir],
title='Building JNI ' + self.identifier)

# NOTE: the following IDE-specific build instructions are NOT used:
# - "rsync -a out/"android_$TARGET_CPU"/lib/*.jar src/android/CHIPTool/app/libs"
# => using the 'ninjaOutputDir' project property instead to take the jar files directly
# from the output

# JNILibs will be copied as long as they reside in src/main/jniLibs/ABI:
# https://developer.android.com/studio/projects/gradle-external-native-builds#jniLibs

# We do NOT use python builtins for copy, so that the 'execution commands' are available
# when using dry run.
jnilibs_dir = os.path.join(self.root, 'src/android/CHIPTool/app/src/main/jniLibs', self.board.AbiName())
self._Execute(['mkdir', '-p', jnilibs_dir], title='Prepare Native libs ' + self.identifier)

for libName in ['libSetupPayloadParser.so', 'libCHIPController.so']:
self._Execute(['cp', os.path.join(self.output_dir, 'lib', 'jni', self.board.AbiName(), libName), os.path.join(jnilibs_dir, libName)])

# App compilation
self._Execute([
'%s/src/android/CHIPTool/gradlew' % self.root, '-p',
'%s/src/android/CHIPTool' % self.root,
'-PchipSdkJarDir=%s' % os.path.join(self.output_dir, 'lib'),
'-PbuildDir=%s' % self.output_dir, 'build'
],
title='Building APP ' + self.identifier)

def build_outputs(self):
outputs = {
'CHIPController.jar':
os.path.join(self.output_dir, 'lib', 'CHIPController.jar'),
'SetupPayloadParser.jar':
os.path.join(self.output_dir, 'lib', 'SetupPayloadParser.jar'),
'ChipTool-debug.apk':
os.path.join(self.output_dir, 'outputs', 'apk', 'debug',
'app-debug.apk'),
'ChipTool-release-unsigned.apk':
os.path.join(self.output_dir, 'outputs', 'apk', 'release',
'app-release-unsigned.apk'),

'jni/%s/libSetupPayloadParser.so' % self.board.AbiName():
os.path.join(self.output_dir, 'lib', 'jni', self.board.AbiName(), 'libSetupPayloadParser.so'),

'jni/%s/libCHIPController.so' % self.board.AbiName():
os.path.join(self.output_dir, 'lib', 'jni', self.board.AbiName(), 'libCHIPController.so'),
}

return outputs
59 changes: 58 additions & 1 deletion scripts/build/expected_all_platform_commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
gn gen --check --fail-on-unused-args --root={root}/examples/all-clusters-app/linux {out}/{real_platform}-native-all_clusters

# Generating {real_platform}-native-chip_tool
gn gen --check --fail-on-unused-args --root=/TEST/BUILD/ROOT/examples/chip-tool {out}/{real_platform}-native-chip_tool
gn gen --check --fail-on-unused-args --root={root}/examples/chip-tool {out}/{real_platform}-native-chip_tool

# Generating qpg-qpg6100-lock
gn gen --check --fail-on-unused-args --root={root}/examples/lock-app/qpg {out}/qpg-qpg6100-lock
Expand Down Expand Up @@ -76,6 +76,24 @@ bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh";
export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR";
west build --cmake-only -d {out}/nrf-nrf5340-shell -b nrf5340dk_nrf5340_cpuapp {root}/examples/shell/nrfconnect'

# Generating android-arm-chip_tool
gn gen --check --fail-on-unused-args {out}/android-arm-chip_tool '--args=target_os="android" target_cpu="arm" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME"'

# Accepting NDK licenses
bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'

# Generating android-arm64-chip_tool
gn gen --check --fail-on-unused-args {out}/android-arm64-chip_tool '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME"'

# Accepting NDK licenses
bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'

# Generating android-x64-chip_tool
gn gen --check --fail-on-unused-args {out}/android-x64-chip_tool '--args=target_os="android" target_cpu="x64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME"'

# Accepting NDK licenses
bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'

# Building {real_platform}-native-all_clusters
ninja -C {out}/{real_platform}-native-all_clusters

Expand Down Expand Up @@ -130,4 +148,43 @@ ninja -C {out}/nrf-nrf5340-lock
# Building nrf-nrf5340-shell
ninja -C {out}/nrf-nrf5340-shell

# Building JNI android-arm-chip_tool
ninja -C {out}/android-arm-chip_tool

# Prepare Native libs android-arm-chip_tool
mkdir -p {root}/src/android/CHIPTool/app/src/main/jniLibs/armeabi-v7a

cp {out}/android-arm-chip_tool/lib/jni/armeabi-v7a/libSetupPayloadParser.so {root}/src/android/CHIPTool/app/src/main/jniLibs/armeabi-v7a/libSetupPayloadParser.so

cp {out}/android-arm-chip_tool/lib/jni/armeabi-v7a/libCHIPController.so {root}/src/android/CHIPTool/app/src/main/jniLibs/armeabi-v7a/libCHIPController.so

# Building APP android-arm-chip_tool
{root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PchipSdkJarDir={out}/android-arm-chip_tool/lib -PbuildDir={out}/android-arm-chip_tool build

# Building JNI android-arm64-chip_tool
ninja -C {out}/android-arm64-chip_tool

# Prepare Native libs android-arm64-chip_tool
mkdir -p {root}/src/android/CHIPTool/app/src/main/jniLibs/arm64-v8a

cp {out}/android-arm64-chip_tool/lib/jni/arm64-v8a/libSetupPayloadParser.so {root}/src/android/CHIPTool/app/src/main/jniLibs/arm64-v8a/libSetupPayloadParser.so

cp {out}/android-arm64-chip_tool/lib/jni/arm64-v8a/libCHIPController.so {root}/src/android/CHIPTool/app/src/main/jniLibs/arm64-v8a/libCHIPController.so

# Building APP android-arm64-chip_tool
{root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PchipSdkJarDir={out}/android-arm64-chip_tool/lib -PbuildDir={out}/android-arm64-chip_tool build

# Building JNI android-x64-chip_tool
ninja -C {out}/android-x64-chip_tool

# Prepare Native libs android-x64-chip_tool
mkdir -p {root}/src/android/CHIPTool/app/src/main/jniLibs/x86_64

cp {out}/android-x64-chip_tool/lib/jni/x86_64/libSetupPayloadParser.so {root}/src/android/CHIPTool/app/src/main/jniLibs/x86_64/libSetupPayloadParser.so

cp {out}/android-x64-chip_tool/lib/jni/x86_64/libCHIPController.so {root}/src/android/CHIPTool/app/src/main/jniLibs/x86_64/libCHIPController.so

# Building APP android-x64-chip_tool
{root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PchipSdkJarDir={out}/android-x64-chip_tool/lib -PbuildDir={out}/android-x64-chip_tool build


Loading

0 comments on commit e8af1e1

Please sign in to comment.