카테고리 보관물: Android

Android Studio에서 Googletest 사용을 위한 설정

Android Studio는 내 PC에서 좀 느리긴 하지만, 다른 IDE들에 비해서는 Emacs key binding이 비교적 잘되어 있어서 만족 하면서 조금씩 배워가고 있다. NDK로 JNI에서 불러다 쓸 native code를 구현하다 보니 Googletest를 사용하기 위해 매번 device로 push 하고 실행하는 과정이 꽤나 번거로와서 script로 만들어 보았다. 이 글에서는 Android Studio에서 Googletest를 사용하기 위한 기본적인 설정과 device에 push하는 과정을 편하게 해주는 script에 대해 설명한다.

환경

Bash script를 사용할 것이므로 Linux 환경이나 Cygwin이 설치되어 있어야 한다. 사실 여기의 내용은 Windows + Cygwin에서 시험되었으나 Bash가 동작하는 환경이라면 특별히 문제는 없을 것이다.

Googletest

Google의 C++ testing framework인 Googletest는 Googletest GitHub에서도 받아서 사용할 수 있으나, NDK package안에도 들어 있다. 여기서는 NDK안의 다음 경로에 있는 Googletest를 사용하기로 한다. Native test case들을 작성할 공간을 app/src/main/jni 아래에 ‘tests’라는 이름으로 만들고 이곳으로 gogoletest를 복사해 온다.

$>mkdir ./app/src/main/jni/tests
$>cp -r $ANDROID_NDK_PATH/sources/third_party/googletest ./app/src/main/jni/tests/

Android.mk

기존에 사용하던 native용 Android.mk file에 Googletest를 사용하기 위한 추가 수정을 해준다.

#
# 1. 시험할 기능을 포함하는 라이브러리
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := 라이브러리_이름
LOCAL_SRC_FILES := 소스파일들

include $(BUILD_SHARED_LIBRARY)


#
# 2. Googletest static library
#
# Google test static library
include $(CLEAR_VARS)
GTEST_PATH := tests/googletest

LOCAL_MODULE := googletest_main
LOCAL_CFLAGS := -Ijni/tests/googletest/include -Ijni/tests/googletest/

LOCAL_SRC_FILES := \
    $(GTEST_PATH)/src/gtest-all.cc

include $(BUILD_STATIC_LIBRARY)


#
# 3. 작성된 test case 실행파일
#
include $(CLEAR_VARS)

LOCAL_MODULE := testcases
LOCAL_CFLAGS := -Ijni/tests/googletest/include -Ijni

LOCAL_SRC_FILES := \
    tests/Test_Main.cpp \
    tests/테스트케이스_소스파일들

LOCAL_SHARED_LIBRARIES := 라이브러리_이름
LOCAL_STATIC_LIBRARIES := googletest_main
include $(BUILD_EXECUTABLE)

위에서 첫 번째 항목 “라이브러리_이름“은 시험 대상이 되는 code를 포함하는 shared library file이고 두 번째 항목은 Googletest의 기능을 static library로 컴파일 하는 과정이다. 사실 googletest 디렉토리 안에는 이것 외에도 많은 소스파일들이 들어 있는데, 시험해 본 바로는 gtest-all.cc만으로도 동작에 문제가 없는것 같다.

이제 마지막으로 테스트 케이스들과 이 테스트 케이스들을 호출하는 Test_Main.cpp를 작성하고, 이 때 필요한 “라이브러리_이름”과 googletest_mian static 라이브러리를 링크시켜준다.

Test_Main.cpp는 예제에 있는것을 그대로 사용했는데 내용은 다음과 같다.

#include <stdio.h>

#include "gtest/gtest.h"

GTEST_API_ int main(int argc, char **argv) {
  printf("Running main() from gtest_main.cc\n");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

Gooletest에서 사용하는 테스트 케이스를 작성하는 방법들은 여러 곳에서 찾을 수 있으므로 여기에서는 설명하지 않는다.

runtest.sh

이제 빌드를 수행하면 동적 라이브러리인 lib라이브러리_이름.sotestcases 파일이 만들어진다. 이것을 시험하려면 디바이스에 push한 다음 library 경로를 설정해주고 실행시켜서 결과를 봐야 하는데, 매번 시험할 때마다 이 과정을 반복하기는 매우 귀찮다. 여러개의 so file로 나눠서 작성한 경우라면 더욱 그렇다.

그래서 이 귀찮은 과정을 bash로 작성해 두고 Android Studio의 External Tools 기능을 이용해서 부를 수 있도록 설정한다. (단축키 까지 달아 두면 훨씬 편하겠지?)

우선, 귀찮은 일을 해주는 bash script는 다음과 같다. 실행파일 이름과 함께 사용할 라이브러리 파일의 목록을 인자로 받아서 디바이스에 push하고 라이브러리 path를 잡아서 실행시킨다.

#!/bin/bash
#
# This script pushes specified gtest related files into devices'
# /cache/xxx directory and runs it.
#

if [ "$#" -eq 0 ]; then
    echo "USAGE: ${0} exe_to_push [libs_to_push...]"
    exit 1
fi

exe=$1
exedir=$exe
libs=$@

# A directory at the device where binaries to be pushed.
remote_dest_dir=/cache/${exedir}

# A directory at local where binaries exist.
local_src_dir=app/src/main/libs/armeabi-v7a

adb wait-for-device
adb shell rm -rf ${remote_dest_dir}
adb shell mkdir ${remote_dest_dir}

# Push libraries.
for lib in ${libs}
do
    adb push ${local_src_dir}/${lib} ${remote_dest_dir}
done

# Push the executable then run.
adb push ${local_src_dir}/${exe} ${remote_dest_dir}
adb shell chmod 755 ${remote_dest_dir}/${exe}
adb shell "LD_LIBRARY_PATH=${remote_dest_dir} ${remote_dest_dir}/${exe}"

이제 Android Studio의 File -> Settings -> Tools -> External Tools 메뉴에 runtest.sh를 등록하자.

androidstudio_edittool

Program은 script를 수행 시켜줄 bash.exe의 위치를 지정해주고, Parameters로 실행할 script인 runtest.sh와  실행파일인 testcases 그리고 관련된 라이브러리 목록을 적어준다. 여기서는 lib라이브러_이름.so에만 의존한다고 가정했다.

그리고 마지막으로 Working directory는 project의 최상위 디렉토리를 의미하는 $ProjectFileDir$을 적어 준다.

수행결과

지정한 단축키 혹은 위의 설정대로 라면 Tools -> Android -> Run native test를 실행시키면 다음과 같이 test case들이 실행되고 결과가 출력된다.

androidstudio_runtest_result

MinnowboardMAX에 Android 올려 본 내용 정리

Android / Chromium OS / Windows를 올려 볼 수 있는 Intel Baytrail 기반의 개발보드인 MinnowboardMAX에 Android 5.1을 install하는 과정을 설명한다.

필요한 것 들

  • Minnowboard MAX판매하는 곳, 2015년 9월 현재 국내에서 판매하는 곳은 없는것 같다.
  • FTDI cable – Serial 출력을 PC에서 받기 위해 guide 문서에는 TTL-232R을 사용하라고  나와 있는데 Amazon.com에서 배송비 빼고 $23정도에 판매한다. 하지만 국내에서도 호환가능한 케이블을 16,500원에 구할 수 있었는데 Windows 10에서 잘 인식되지 않았고 (Windows 7 / Linux에서는 잘 동작함) 핀 배열이 달라서 직접 배선을 연결해 주어야 하는 문제가 있기는 했다. FTDI 항목 참조.
  • HDMI monitor와 cable – 내부 display가 없기 때문에 화면 출력을 위해서는 Micro HDMI 변환 젠더나 이를 지원하는 cable이 있어야 한다.
  • Mouse / Keyboard – USB를 지원하는 mouse와 keyboard가 필요 하다.
  • SD card (16GB 이상) – Android OS가 설치되는 공간이다.
  • USB (thumb) drive – Android OS를 올릴 image를 flashing하기 위해 필요하다 5GB 이상이 필요하다고 하는데, 실제로는 그렇게 image가 큰 것 같진 않다.

Hooking up

minnowboardMAX_connection
(* Image from Minnowboard MAX wiki)

  • Micro-HDMI : HDMI를 지원하는 monitor와 연결
  • Network : Android image file들을 전송해 줄 host와 같은 network에 유선 LAN cable로 연결
  • Power : 구매시에 포함되어 있는 5V adapter에 연결
  • USB thumb drive : Fastboot image를 포함하고 있는 USB drive를 연결
  • USB keyboard : BIOS와 fastboot 진입 후 메뉴 선택등을 위해 keyboard를 연결
  • FTDI Serial : BIOS menu 선택이나 booting log를 보기 위해 필요하나 없어도 동작에는 문제가 없다.

FTDI 연결

“FTDI Serial”의 핀 배열은 다음과 같다.

minnowboardMAX_FTDI
(* Image from Minnowboard MAX wiki)

PN-USBTTL_connector추천되는 TTL-233R cable을 사용한다면 그냥 연결만 해도 되겠지만, 국내에서 판매하는 PN-USBTTL 제품은 GND / VCC / TXD / RXD 만을 지원하는데 이 네개만 연결해도 동작에는 문제가 없다. 다만 한가지 주의 할 점은 일반적인 연결과 달리 MinowboardMAX의 FTDI TXD연결 (4번핀)은 이 케이블의 G-TXD, RXD연결(5번핀)은 W-RXD로 연결해 주어야 한다는 점이다. 일반적으로 RX-TX, TX-RX로 연결하는 것과 다르다.

정리하면 다음과 같다.

FTDI Pin # PN-USBTTL connector
1 (GND) B-GND
3 (VCC) R-VCC
4 (TXD) G-TXD
5 (RXD) W-RXD

마지막으로 serial monitor program (PuTTY나 Minicom 같은)을 열어서 설정을 해주면 serial 입출력을 위한 준비는 끝난다.

Baud rate: 115200
Flow Control: No
Data bits: 8
Stop bits: 1

Firmware update

MinnowboardMAX를 구매 할 때 포함되어 있는 BIOS의 버전은 최신이 아닐 가능성이 많다. Version 0079 이상이면 굳이 업데이트 할 필요는 없으나 필요하다면 다음 과정에 따라 최신 firmware로 업데이트 할 수 있다.

  • Intel firmware resource center에서 MinnowboardMAX용 firmware를 download
  • USB thumb drive를 FAT32로 포맷하고 다운로드 받은 zip file을 풀어서 모든 내용을 복사
  • USB thumb drive를 MinnowboardMAX에 연결하고 전원을 켜기
  • Booting중 F2 혹은 DEL key를 눌러서 BIOS로 진입해서 UEFI shell을 실행
  • UEFI shell에서 다음의 명령어를 입력해서 firmware update를 실행
UEFI shell> fs0:
UEFI fs0:\> MinnowBoard.MAX.FirmwareUpdateX64.efi MinnowBoard.MAX.X64.082.R01.bin

Android image file 구하기

  • Pre-built binary download
    : 빌드 안해도 되는건 좋은데, userdebug image는 su 명령어를 사용할 수 없어서 제약이 있을 수 있다는 점을 고려해야 한다.
wget https://download.01.org/android-ia/releases/android-5.1.0_r1-ia0-minnowboard_max-64bit-userdebug.zip
  • MinnowboardMAX용 Android source code를 download 받고 직접 build
repo init -u https://github.com/android-ia/platform_manifest -b release/android-5.1.0_r1-ia0
repo sync -j4 -q -c --no-clone-bundle
source build/envsetup.sh
lunch minnow_64p-eng
make -j$(nproc) dist

Creating a fastboot USB thumb drive

Android image를 구했다면 fastboot-usb.img file을 USB thumb drive에 write한다. Linux를 사용한다면 USB drive를 unmount한다음 dd utility로 해당 device 경로에 write하고, Windows를 사용한다면 Win32 Disk imager 혹은 그와 비슷한 program을 사용하면 된다.

다음 예제는 Linux host에서 /media/THUMBDRIVE에 mount된 drive를 해제하고 /dev/sdg1 device path에 writing하는 상황을 설명한다. 당연한 말이지만, 자동으로 mount되지 않았다면 umount를 실행 필요가 없고, 연결된 device path에 대한 정보는 dmesg를 통해 확인할 수 있다.

sudo umount /media/THUMBDRIVE
sudo dd bs=1M if=fastboot-usb.img of=/dev/sdg1 conv=fsync

Flashing Android Image files

fastboot-usb.img가 설치된 USB thumb drive를 연결하고 USB drive로 MinnowboardMAX를 부팅시키면 다음과 같은 화면이 나온다. ‘BOOTLOADER ERROR CODE 04’라고 나오긴 하지만 사실 오류는 아니고 사용자입력을 기다리는 화면이다. SD card와 LAN cable이 제대로 연결되었는지 다시 한 번 확인하고 계속 하기위해 연결된 key board의 윗쪽 화살표키를 누른다.

Intel_bootloader_error_code_04

한참동안 화면 업데이트가 없어서 동작하지 않는것 처럼 보일수도 있는데, 연결된 Serial port로는 kernel이 load되고 실행되는 과정이 보이고 있을 것이다. Serial port에서 booting이 완료되는 것이 보이면, MinnowboardMAX와 같은 network에 연결되어 있는 host PC에서 다음 명령어를 수행해서 image file들을 LAN으로 전송한다.

# Linux
$ provision.sh <IP_ADDR>

# Windows
C:> provision.bat <IP_ADDR>

Tips

  • 실제로 잘 먹힌것인지는 모르겠는데… Network이 안 잡혀서 헤매다가 BIOS에서 IPv4 설정하는 menu가 있어서 DHCP를 설정해 주었다. Network을 못 잡는 다거나 한다면 한번 시도해 보자.
    – Device Manager -> Network Device List -> MAC:xx:xx:xx:xx:xx:xx -> IPv4 Network

SC_enabling_DHCP_from_BIOS

  • Guide 문서에 나와 있는것과는 달리 network 설정화면이 나오지 않아서 MinnowboardMAX의 IP 주소가 무엇을 받았는지 몰랐는데, 공유기의 IP 할당 테이블을 보면 유추할 수 있다.

SC_guessing_minnowboard_IP“할당된 device는 4개인데 사용중인 디바이스는 3개니까… IP는 중간에 건너뛴 4가 아니면 6으로 끝나지 않을까?” 너무 막 찍는것 같지만 실제로 4번으로 끝나는 IP였다! 😉

Boot up

이제 USB thumb drive를 빼고, BIOS menu에서 SD card로 부팅하도록 설정하면 Android로 부팅되는 것을 볼 수 있을 것이다.

ADB over network

USB로 직접 연결할 수 없으므로, 같은 network에 연결되어 있는 PC에서 ADB over network으로 ADB를 실행시킬 수 있다. 먼저 serial emulator program으로 Android device에서 다음을 실행 시킨다.

#> adb tcpip 5555

이제 host PC에서 다음 명령어로 연결한다.

CMD> adb connect 192.168.173.4:5555
CMD> adb devices

References & Links

Android emacs (android-host.el) 기능 추가

AOSP에서 제공되는 Emacs용 Andorid 개발환경을 설정한 이후(이 post 참조) 잘 모르는 LISP을 더듬어 가며 추가한 몇 가지 기능을 소개합니다.

adb reboot (M-x android-adb-reboot)

Module등을 변경한 후에 device를 reset하는 명령어가 없어서 추가했다. 이 기능의 장점을 굳이 꼽자면 shell을 따로 뛰우지 않고 리붓을 할수 있다는거…

(defun android-adb-reboot ()
  "Execute 'adb reboot'."
  (interactive)
  (android-adb-command "reboo

Module push (M-x android-adb-push-module)

Compile한 module을 target에 push 할 때 사용한다. Command를 입력하면 push 할 source와 target을 물어보고 adb push command를 실행한다.

(defun android-adb-push-module (source target)
  "Push specified module into target path."
  (interactive "fSource: \nsTarget: ")

  (android-adb-root)
  (android-adb-remount)
  (android-adb-command
    (concat "push " source " " target) 'p))

Module push Hotkey  (C-x a p)

위의 android-adb-push-module 함수를 쓰다보니 path를 입력하는게 무척 귀찮다. android-compile 명령어를 수행해서 만들어지는 출력 버퍼에서 위의 단축키로 target의 해당 directory로 module을 push한다.

Install: out/target/product/TARGET_DEVICE/system/xxx/yyy.zz

출력 버퍼로 이동해서 출력된 결과물 위에 cursor를 놓고 ‘C-x a p’를 입력하면 그 경로를 읽어서 target으로 push 한다. Source의 path에서 target 위치를 읽어 오게 되어 있어서 특별한 입력이 필요 하지 않다.

(define-key map (kbd "p") 'android-adb-push-module-at-point)
...

(defun android-adb-push-module-at-point ()
  "Push module path at cursor point an push to the target path."
  (interactive)

  (let*
    (
      (sourcepath (concat (android-find-build-tree-root) (thing-at-point 'filename)))
      (targetpath (substring sourcepath (string-match "\/system" sourcepath) nil)))
    (android-adb-push-module sourcepath targetpath)))

전체 수정내역을 포함하는 파일: icon_text_file android-host.el

Cygwin에서 Android systrace 실행 문제

2014년 8월 현재, Cygwin으로 systrace를 사용하려고 하면 zlib 관련한 error가 나면서 동작하지 않는다.

$ python systrace.py -o output.html gfx input view wm am video hal dalvik
capturing trace... done
downloading trace... done

Traceback (most recent call last):
  File "systrace.py", line 286, in <module>
    main()
  File "systrace.py", line 231, in main
    decoded_chunk = dec.decompress(chunk)
zlib.error: Error -3 while decompressing: incorrect header check

AOSP project의 이 링크에 보면 문제를 해결하기 위한 patch가 올려져 있는데 comment를 보니 한동안 받아들여 지지 않다가 현재 source와 달라져서 적용할 수 없게 된 모양이다. 같은 동작을 하도록 약간만 수정해서 이렇게 고쳐 보니 좀 지저분하긴 해도 zlib error 없이 Cygwin에서 systrace를 사용할 수 있었다.

diff --git a/systrace.py b/systrace.py
index 387f413..0d3be25 100644
--- a/systrace.py
+++ b/systrace.py
@@ -205,8 +205,12 @@ def main():
       data = ''.join(data)

       # Collapse CRLFs that are added by adb shell.
-      if data.startswith('\r\n'):
-        data = data.replace('\r\n', '\n')
+      if sys.platform == 'cygwin':
+        if data.startswith('\r\r\n'):
+          data = data.replace('\r\r\n', '\n')
+      else:
+        if data.startswith('\r\n'):
+          data = data.replace('\r\n', '\n')

       # Skip the initial newline.
       data = data[1:]


Emacs에서 Android 개발 환경 사용

AOSP에는 Emacs에서 Android 개발환경을 사용할 수 있도록 몇몇 el file들을 제공하는데, platform  module build를 주로 하게 되는 나로써는 사용에 부족함이 없는 것 같다. Module build와 기본적인 ADB 조작을 지원하는 이 script들의 기능이 부족하다고 느낀다면 Android 개발환경을 보다 적극적으로 지원하는 android-mode와 같은 project를 고려해 보는것도 좋을 것이다.

buildspec.mk 설정

사실 이 기능은 그동안 사용하지 않았었는데, AOSP에서 지원하는 el script들을 사용하려면 설정해 주어야 한다. 간단한 과정을 거쳐 설정해 놓고 보니 shell에서도 사전설정 없이 ‘make target’ command만으로 build를 할 수 있어서 편하다. 대신 ‘cd $OUT’이나 ‘croot’ 같은 이동 command들을 사용할 수 없다는 것은 불편한 점이다.

<ANDROID_ROOT>/build/buildspec.mk.default file을 복사해서 환경에 맞게 수정한 다음 <ANDROID_ROOT> 위치에 놓아두면 build 명령을 내릴때 자동으로 참조된다. 기본적으로 TARGET_PRODUCT, TARGET_BUILD_VARIANT 정도만 수정하면되는데 특이한 build환경을 사용하고 있다면 나머지 항목들은 읽어보고 자신에 맞게 수정하자.

# Choose a product to build for. Look in the products directory for ones
# that work.
ifndef TARGET_PRODUCT
  TARGET_PRODUCT:=product_name
endif

# Choose a variant to build. If you don't pick one, the default is eng.
# User is what we ship. Userdebug is that, with a few flags turned on
# for debugging. Eng has lots of extra tools for development.
ifndef TARGET_BUILD_VARIANT
  TARGET_BUILD_VARIANT:=eng
endif

el file들 복사 및 설정

<ANDROID_ROOT>/development/ide/emacs/ 안에 있는 file들을 Emacs 환경 directory로 복사한 다음 해당 file이 load되도록 init file을 수정한다.

mkdir ~/.emacs.d/android.el
cp <ANDROID_ROOT>/development/ide/emacs/*.el ~/.emacs.d/android.el/ android-common.el android-compile.el android-host.el

다음을 init file에 붙여 넣으면 된다.

; Load Android tools
(add-to-list 'load-path "~/.emacs.d/android.el/")
(require 'android-host)
(require 'android-compile)


사용

편집 중인 file의 module을 build하려면 ‘M-x android-compile’을 입력 하면 된다. 그 외의 ADB에 관련한 명령어 들은 android-host.el script에 설명되어 있다.

;; C-x a a android-adb-root
;; C-x a r android-adb-remount
;; C-x a s android-adb-sync
;; C-x a b android-adb-shell-reboot-bootloader
;; C-x a f android-fastboot-flashall