blob: 74d47e4485004592ccd467183c6205cf70f60c0f [file] [log] [blame]
# 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 os
import shlex
from enum import Enum, auto
from .builder import Builder
class AndroidBoard(Enum):
ARM = auto()
ARM64 = auto()
X64 = auto()
X86 = auto()
AndroidStudio_ARM = auto()
AndroidStudio_ARM64 = auto()
AndroidStudio_X64 = auto()
AndroidStudio_X86 = auto()
def TargetCpuName(self):
if self == AndroidBoard.ARM or self == AndroidBoard.AndroidStudio_ARM:
return 'arm'
elif self == AndroidBoard.ARM64 or self == AndroidBoard.AndroidStudio_ARM64:
return 'arm64'
elif self == AndroidBoard.X64 or self == AndroidBoard.AndroidStudio_X64:
return 'x64'
elif self == AndroidBoard.X86 or self == AndroidBoard.AndroidStudio_X86:
return 'x86'
else:
raise Exception('Unknown board type: %r' % self)
def AbiName(self):
if self.TargetCpuName() == 'arm':
return 'armeabi-v7a'
elif self.TargetCpuName() == 'arm64':
return 'arm64-v8a'
elif self.TargetCpuName() == 'x64':
return 'x86_64'
elif self.TargetCpuName() == 'x86':
return 'x86'
else:
raise Exception('Unknown board type: %r' % self)
def IsIde(self):
if (self == AndroidBoard.AndroidStudio_ARM or self == AndroidBoard.AndroidStudio_ARM64
or self == AndroidBoard.AndroidStudio_X64 or self == AndroidBoard.AndroidStudio_X86):
return True
else:
return False
class AndroidApp(Enum):
CHIP_TOOL = auto()
CHIP_TEST = auto()
CHIP_TVServer = auto()
def AppName(self):
if self == AndroidApp.CHIP_TOOL:
return "CHIPTool"
elif self == AndroidApp.CHIP_TEST:
return "CHIPTest"
elif self == AndroidApp.CHIP_TVServer:
return "CHIPTVServer"
else:
raise Exception('Unknown app type: %r' % self)
def AppGnArgs(self):
gn_args = {}
if self == AndroidApp.CHIP_TVServer:
gn_args['chip_config_network_layer_ble'] = False
return gn_args
class AndroidBuilder(Builder):
def __init__(self, root, runner, board: AndroidBoard, app: AndroidApp):
super(AndroidBuilder, self).__init__(root, runner)
self.board = board
self.app = app
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):
self._Execute([
'python3', 'build/chip/java/tests/generate_jars_for_test.py'
], title='Generating JARs for Java build rules test')
self._Execute([
'python3', 'third_party/android_deps/set_up_android_deps.py'
], title='Setting up Android deps through Gradle')
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']
gn_args['chip_use_clusters_for_ip_commissioning'] = True
gn_args.update(self.app.AppGnArgs())
args_str = ""
for key, value in gn_args.items():
if type(value) == bool:
if value:
args_str += '%s=true ' % (key)
else:
args_str += '%s=false ' % (key)
else:
args_str += '%s="%s" ' % (key, shlex.quote(value))
args = '--args=%s' % (args_str)
gn_gen = [
'gn', 'gen', '--check', '--fail-on-unused-args', self.output_dir, args,
]
if self.board.IsIde():
gn_gen += ['--ide=json',
'--json-ide-script=//scripts/examples/gn_to_cmakelists.py']
self._Execute(gn_gen, 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):
if self.board.IsIde():
# App compilation IDE
# TODO: Android Gradle with module and -PbuildDir= will caused issue, remove -PbuildDir=
self._Execute([
'%s/src/android/%s/gradlew' % (self.root,
self.app.AppName()), '-p',
'%s/src/android/%s' % (self.root, self.app.AppName()),
'-PmatterBuildSrcDir=%s' % self.output_dir,
'-PmatterSdkSourceBuild=true',
'-PmatterSourceBuildAbiFilters=%s' % self.board.AbiName(),
'assembleDebug'
],
title='Building APP ' + self.identifier)
else:
self._Execute(['ninja', '-C', self.output_dir],
title='Building JNI ' + self.identifier)
# 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
# to avoid redefined in IDE mode, copy to another place and add that path in build.gradle
# 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/', self.app.AppName(), 'app/libs/jniLibs', self.board.AbiName())
libs_dir = os.path.join(
self.root, 'src/android/', self.app.AppName(), 'app/libs')
self._Execute(['mkdir', '-p', jnilibs_dir],
title='Prepare Native libs ' + self.identifier)
# TODO: Runtime dependencies should be computed by the build system rather than hardcoded
# GN supports getting these dependencies like:
# gn desc out/android-x64-chip_tool/ //src/controller/java runtime_deps
# gn desc out/android-x64-chip_tool/ //src/setup_payload/java runtime_deps
# However this assumes that the output folder has been populated, which will not be
# the case for `dry-run` executions. Hence this harcoding here.
#
# If we unify the JNI libraries, libc++_shared.so may not be needed anymore, which could
# be another path of resolving this inconsistency.
for libName in ['libSetupPayloadParser.so', 'libCHIPController.so', 'libc++_shared.so', 'libCHIPAppServer.so']:
self._Execute(['cp', os.path.join(self.output_dir, 'lib', 'jni', self.board.AbiName(
), libName), os.path.join(jnilibs_dir, libName)])
jars = {
'CHIPController.jar': 'src/controller/java/CHIPController.jar',
'SetupPayloadParser.jar': 'src/setup_payload/java/SetupPayloadParser.jar',
'AndroidPlatform.jar': 'src/platform/android/AndroidPlatform.jar',
'CHIPAppServer.jar': 'src/app/server/java/CHIPAppServer.jar',
}
for jarName in jars.keys():
self._Execute(['cp', os.path.join(
self.output_dir, 'lib', jars[jarName]), os.path.join(libs_dir, jarName)])
# App compilation
self._Execute([
'%s/src/android/%s/gradlew' % (self.root,
self.app.AppName()), '-p',
'%s/src/android/%s' % (self.root,
self.app.AppName()),
'-PmatterBuildSrcDir=%s' % self.output_dir,
'-PmatterSdkSourceBuild=false',
'-PbuildDir=%s' % self.output_dir, 'assembleDebug'
],
title='Building APP ' + self.identifier)
def build_outputs(self):
if not self.board.IsIde():
outputs = {
self.app.AppName() + 'app-debug.apk':
os.path.join(self.output_dir, 'outputs', 'apk', 'debug',
'app-debug.apk'),
'CHIPController.jar':
os.path.join(self.output_dir, 'lib',
'src/controller/java/CHIPController.jar'),
'AndroidPlatform.jar':
os.path.join(self.output_dir, 'lib',
'src/platform/android/AndroidPlatform.jar'),
'SetupPayloadParser.jar':
os.path.join(self.output_dir, 'lib',
'src/setup_payload/java/SetupPayloadParser.jar'),
'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'),
'jni/%s/libc++_shared.so' % self.board.AbiName():
os.path.join(self.output_dir, 'lib', 'jni',
self.board.AbiName(), 'libc++_shared.so'),
}
else:
outputs = {
self.app.AppName() + '-debug.apk':
os.path.join(self.root, "src/android", self.app.AppName(),
'app/build/outputs/apk/debug/app-debug.apk')
}
return outputs