ndk-build tool


android 명령을 사용해서 project 를 생성하고 나면, 그것을 컴파일해 줄 필요가 있다. 이 컴파일 작업은 ndk-build 를 통해서 이루어 진다. 그런데 책에서는 ndk-build 에 대해서 자세하게 다루고 있지 않다. 그런데 안타깝게도 안드로이드 개발자 사이트에서도 자세한 내용을 다루고 있지 않다. 하지만 $ANDROID_NDK/docs/NDK-BUILD.html 을 열어 보면 해당 도구에 대한 설명을 볼 수 있다.


이 문서는 "ndk-build 란?" 장을 제외하고는 NDK-BUILD.html 의 내용을 번역한 것이다.


ndk-build 란?


일단 ndk-build 라는 것의 개념부터 살펴 보도록 하자. ndk-build 에 대해서 --version 이라는 매개변수를 주고 실행하면 다음과 같은 결과를 볼 수 있다.


GNU Make 3.81

Copyright (C) 2006  Free Software Foundation, Inc.

This is free software; see the source for copying conditions.

There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


위의 결과를 보면 알 수 있겠지만, ndk-build 라는 것은 GNU Make 에 대한 wrapper 이다. 여기에서 이제 또 다른 의문이 생길 것이다. 그럼 make 란 무엇인가?


make 라는 것은 Visual Studio 같은 IDE 가 가지고 있는 빌드 시스템과 유사하다고 생각하면 된다. 예를 들어 우리는 Visual Studio 를 사용해서 솔루션과 프로젝트를 만들고 거기에 소스 파일, 헤더 파일, 리소스 파일 등을 추가한다. 그 다음에 솔루션이나 프로젝트의 속성창을 열어 필요한 옵션들을 설정한다. 그리고 나서 빌드 버튼을 누르게 된다. 그러면 지정된 옵션에 따라서 컴파일과 링크가 수행된다. 이 때 "최소 다시 빌드" 옵션을 설정하게 되면, 변경된 파일을 검색해서 관련 object 파일만을 컴파일하고 링크하게 된다. make 가 바로 이러한 작업을 해 주는 도구이다. 


물론 make 가 하는 일이 단순하게 그런 것만 있는 것은 아니다. 의존성을 설정한다던가 batch 명령과 유사한 작업을 한다던가 여러 가지 작업을 하게 된다. make 는 여러 가지 구현이 존재한다. 그 중에서 가장 많이 사용하는 것이 GNU Make 이다. 자세한 내용은 "GNU Make 강좌" 를 참조하기 바란다.  GNU Make 의 매뉴얼은 "GNU Make Manual" 에서 찾아 볼 수 있다.


어쨌든 ndk-build 는 GNU Make 의 wrapper 이며, 우리는 이것을 프로젝트를 설정하고 빌드하는 것을 도와 주는 도구 정도로 생각하면 되겠다.


용례.


안드로이드 NDK r4 는 새로운 작은 쉡 스크립트인 'ndk-build' 를 소개했는데, 이는 머신 코드를 빌드하는 것을 단순화하기 위한 것이다.


이 스크립트는 NDK 의 최상위 디렉토리에 존재하며, 응용프로그램 프로젝트 디렉토리나 그것의 모든 하위 디렉토리들에서 명령줄에서 실행된다. 예를 들어 :



여기에서 $NDK 는 NDK 설치 경로이다. 당신은 그것을 매번 입력하는 것을 피하기 위해서 PATH 에다가 $NDK 를 추가하거나 alias 를 만들 수 있다.


옵션.


'ndk-build' 에 대한 모든 매개 변수는 NDK 빌드 스크립트를 실행하는 기저에 깔린 GNU Make 명령에 직접적으로 전달된다. 자주 쓰는 용례는 다음과 같다 :


  • ndk-build : 머신 코드를 다시 빌드한다.
  • ndk-build clean : 생성된 바이너리를 모두 지운다.
  • ndk-build NDK_DEBUG=1 : 디버깅할 수 있는 네이티브 코드를 생성한다.
  • ndk-build V=1 : 빌드를 시작하고, 빌드 명령들을 보여 준다.
  • ndk-build -B : 완전한 다시 빌드를 강제한다.
  • ndk-build -B V=1 : 완전한 다시 빌드를 강제하고, 빌드 명령들을 보여 준다.
  • ndk-build NDK_LOG=1 : 내부 NDK 로그 메시지들을 보여 준다( NDK 자체를 디버깅하는데 사용된다 ).
  • ndk-build NDK_DEBUG=1 : 디버깅 가능한 빌드를 강제한다( 아래 참조 ).
  • ndk-build NDK_DEBUG=0 : 릴리스 빌드를 강제한다( 아래 참조 ).
  • ndk-build NDK_HOST_32BIT=1 : 항상 32 비트 툴체인을 사용한다( 아래 참조 ).
  • ndk-build NDK_APPLICATION_MK=<file> : 다시 빌드하는데, NDK_APPLICATION_MK 명령줄 변수에 의해 지정된 특정 Application.mk 를 사용한다.
  • ndk-build -C <project> : <project> 에 위치한 프로젝트 경로를 위해 네이티브 코드를 빌드한다. 터미널에서 'cd' 를 입력하고 싶지 않을 때 유용하다.


디버그 빌드 대 릴리스 빌드.


NDK r5 에서 ndk-build 는 릴리스 빌드와 디버그 빌드 사이를 더 쉽게 전환할 수 있도록 변경되었다. 이는 NDK_DEBUG 변수를 사용해서 수행된다.


예를 들어 :


$NDK/ndk-build NDK_DEBUG=1 => 강제로 디버그 바이너리를 생성한다.

$NDK/ndk-build NDK_DEBUG=0 => 강제로 릴리스 바이너리를 생성한다.


NDK_DEBUG 를 지정하면, ndk-build 는 그것의 기본 동작을 유지하는데, 그것은 AndroidMenifest.xml 에 <application> 엘리먼트가 존재하면  android:debuggable="true" 로 설정되어 있는지를 확인한다.


중요 : 만약 당신이 SDK r8 ( 혹은 그 이상 )의 빌드 툴을 사용한다면, 당신은 AndroidManifest.xml 파일을 전혀 손댈 필요가 없다!

왜냐하면 ( "ant debug" 나 ADT 플러그인의 관련 옵션을 사용해 ) 디버그 패키지를 빌드하게 되면, 그 도구가 자동으로 NDK_DEBUG=1 을 사용해 생성된 네이티브 디버그 파일을 선택할 것이기 때문이다.


그리고 편리하게도 NDK 에 의해 생성된 릴리스 오브젝트 파일과 디버그 오브젝트 파일은 서로 다른 디렉토리에 저장된다( 예를 들어 obj/local/<abi>/objsobj/local/<abi>obj-debug ). 이는 두 모드를 전환할 때 ( 심지어 당신이 한 두개의 소스 파일들만을 수정했을 때 ), 모든 소스들이 다시 빌드되는 것을 막아 준다.


64 비트 툴체인과 32 비트 툴체인.


일부 툴체인들은 64 비트 버전과 32 비트 버전을 둘 다 가지고 있다. 예를 들어 $NDK/toolchain/<name>/prebuilt$NDK/prebuilt 는 "linux-x86" 과 "linux-x86-64" 폴더를, 만약 OS 가 그것을 지원한다면 32 비트 모드 리눅스 툴과 64 비트 모드 리눅스 툴을 위해 모두 포함하고 있다.당신은 환경 변수나 ndk-build 명령줄에서  NDK_HOST_32BIT=1 을 사용하여 32 비트 툴체인을 사용하는 것을 강제할 수 있다.


Note that 64-bit tools utilize host resources better( faster, handle larger programs, etc ) and they should function identically to their 32-bit counterparts. le. 64-bit toolchains still generate 32-bit binaries for Android.


요구 사항.


'ndk-build' 나 NDK 를 범용적으로 사용하기 위해서는 GNU Make 3.81 을 필요로 한다. 빌드 스크립트들은 적절하지 못한 Make 톨을 사용하고 있는지를 확인할 것이며 에러 메시지를 출력하며 불평할 것이다.


만약 GNU Make 3.81 이 설치되어 있지만 기본 'make' 명령에 의해 실행될 수 없는 상태라면, GNUMAKE 를 환경 변수에 정의해서 'ndk-build' 를 실행하기 전에 그것을 가리키도록 하라. 예를 들어 :


GNUMAKE=/usr/local/bin/gmake ndk-build


혹은 그 변경을 더 영구적으로 만들라 :


export GNUMAKE=/usr/local/bin/gmake ndk-build


당신의 쉘과 GNU Make 3.81 설치 위치를 맞춰라.


당신은 다음과 같은 환경 변수들을 사용해서 $NDK/prebuilt/<OS>/bin 의 다른 호스트 prebuilt 툴들을 덮어 쓸 수도 있다.


NDK_HOST_AWK=<path-to-awk>

NDK_HOST_ECHO=<path-to-echo>

NDK_HOST_CMP=<path-to-cmp>


내부 구현.


'ndk-build' 자체는 GNU Make 에 대한 작은 wrapper 이며, 그것의 목적은 올바른 NDK 빌드 스크립트를 쉽게 실행하는 것이다. 이는 다음과 동일하다 :


$GNUMAKE -f $NDK/build/core/build-local.mk [parameters]


여기에서 '$GNUMAKE' 는 GNU Make 3.81 이상을 가리키며, '$NDK' 는 당신의 NDK 설치 디렉토리를 가리킨다.


다른 쉘 스크립트들( 혹은 당신만의 Makefile 들 )로부터 NDK 빌드 스크립트를 실행하고자 한다면 이에 대한 지식을 사용하라.


책에 보면 android 툴을 사용해서 프로젝트를 생성하는 것을 볼 수 있다. 예를 들면 다음과 같은 명령을 사용한다.



책에서는 그냥 저렇게 하면 프로젝트가 생성된다고만 말하고 실제 그것이 어떤 의미를 가지고 있는지 혹은 어떤 동작을 하는지에 대해서 설명하지 않는다. 그래서 "그런가 보다"하고 넘어 갔었는데, 역시 근원을 파고들고자 하는 욕구가 사라지지 않아서 정리해 본다. 다행히도 Android Developer 사이트에 관련 문서가 있어서 번역해 본다.


원문 : Manaing Projects from the Command Line

주의 : 번역이 개판이므로 이상하면 원문을 참조하십시오.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해서 잘 알려진 용어나 발음이 비슷한 용어는 한글로 표기합니다.




명령줄에서 프로젝트 관리하기


android 툴은 세 종류의 프로젝트들을 모두 생성하기 위한 명령들을 제공한다. 안드로이드 프로젝트는 프로젝트를 설치를 위한 .apk 파일로 빌드하는데 필요한 파일들과 리소스들을 포함한다.


  • 안드로이드 프로젝트는 프로젝트를 설치를 위한 .apk 파일로 빌드하는데 필요한 파일들과 리소스들을 포함한다. 당신은 최종적으로 장치에 설치하고자 하는 모든 응용프로그램을 위해 안드로이드 프로젝트를 생성할 필요가 있다.
  • 당신은 안드로이드 프로젝트를 라이브러리 프로젝트로서 설계할 수도 있는데, 이는 그것에 의졶하는 다른 프로젝트들과 공유되는 것을 허용한다. 안드로이드 프로젝트가 라이브러리 프로젝트로 설계되면, 그것은 장치에 설치될 수 없다.
  • 테스트 프로젝트들은 JUnit 기능을 확장해 안드로이드에 특화된 기능들을 포함한다. 테스트 프로젝트를 생성하는 것과 관련한 더 많은 정보를 원한다면, Testing from other IDEs 를 참조하라.


안드로이드 프로젝트 생성하기기



안드로이드 프로젝트를 생성하려면, 당신은 android 툴을 사용해야만 한다. 당신이 android 로 새로운 프로젝트를 생성할 때, 그것은 일부 기본 파일들, stub 파일들, 설정 파일들, 빌드 파일을 가진 프로젝트 디렉토리를 생성할 것이다.


새로운 안드로이드 프로젝트를 생성하려면, 명령줄을 열고, SDK 의 tools/ 디렉토리로 가서, 다음을 실행하라 :


android create project \

--target <target_ID> \

--name <your_project_name> \

--path path/to/your/project \

--activity <your_activity_name> \

--package <your_package_namespace>


  • target 은 응용프로그램을 위한 "빌드 타겟" 이다. 그것은 당신이 응용프로그램을 빌드하려고 하는 ( Google APIs 와 같은 모든 애드온을 포함하는 ) 안드로이드 플래폼 라이브러리와 관련이 있다. 이용 가능한 타겟들과 그것들의 관련 아이디들을 보려면, 다음을 실행하라 : android list targets.
  • name 은 프로젝트의 이름이다. 이것은 선택 사항이다. 만약 이름이 제공되면, 이 이름은 응용프로그램을 빌드할 때 .apk 파일 이름으로 사용될 것이다.
  • path 는 프로젝트 디렉토리의 위치이다. 만약 디렉토리가 존재하지 않으면, 자동으로 생성될 것이다.
  • activity 는 기본 Activity 클래스의 이름이다. 이 클래스 파일은 <path_to_your_project>/src/<your_package_namespace_path>/ 내부에 생성될 것이다. 당신이 name 을 지정하지 않으면 그것은 .apk 파일 이름으로 사용될 것이다.
  • package 는 프로젝트를 위한 패키지 이름인데, 자바 프로그래밍 언어에서의 패키지를 위한 규칙과 동일하다.


여기 예제가 있다 :


android create project \

--target 1 \

--name MyAndroidApp \

--path ./MyAndroidAppProject \

--activity MyAndroidAppActivity \

--package com.example.myandroid


프로젝트를 생성하고 나면, 당신은 개발을 시작할 준비가 된 것이다. 원하는 곳에 프로젝트 폴더를 옮길 수는 있지만, 응용프로그램을 에뮬레이터에 보내기 위해서는  Android Debug Bridge ( adb ) - SDK 의 platform-tools/ 디렉토리에 있음 - 를 사용해야만 한다는 것을 염두에 두기 바란다( 이에 대해서는 나중에 언급한다 ). 그래서 당신은 프로젝트 솔루션과 paltform-tools/ 폴더에 접근할 필요가 있다.

Tip : platform-tools/tools/ 를 PATH 환경 변수에 추가하라.

Caution : 당신은 SDK 디렉토리의 위치를 움직여서는 안 된다. 왜냐하면 이는 local.properties 에 위치한 SDK 위치 속성을 깨 버리기 때문이다. 만약 당신이 SDK 위치를 옮기고자 한다면, android update project  명령을 사용하라. 더 많은 정보를 원한다면 "프로젝트 갱신하기" 를 참조하라.


프로젝트 갱신하기



만약 이전 버전의 안드로이드 SDK 에서 만든 프로젝트를 업그레이드하고 싶거나 현존하는 코드로부터 새로운 프로젝트를 생성하고 싶다면, android update project 명령을 사용해 프로젝트를 새로운 개발 환경으로 갱신하라. 당신은 이 명령을 ( --target 옵션과 함께 ) 사용해서 현존하는 프로젝트의 빌드 타겟을 변경하거나 ( --name 옵션과 함께 ) 프로젝트 이름을 변경할 수 있다. android 툴은 분실되거나 갱신될 필요가 있는 ( 이전 섹션에서 열거되었던 ) 모든 파일들과 폴더들을 안드로이드 프로젝트가 필요한 만큼 생성하게 될 것이다.


현존하는 안드로이드 프로젝트를 갱신하려면, 명령줄을 열고 SDK 의 tools/ 디렉토리로 이동하라. 그리고 다음을 실행하라 :


android update project --name <project_name> --target <target_ID>

--path <path_to_your_project>


  • target 은 응용프로그램을 위한 "빌드 타겟" 이다. 그것은 당신이 응용프로그램을 빌드하려고 하는 ( Google APIs 와 같은 모든 애드온을 포함하는 ) 안드로이드 플래폼 라이브러리와 관련이 있다. 이용 가능한 타겟들과 그것들의 관련 아이디들을 보려면, 다음을 실행하라 : android list targets.
  • path 는 프로젝트 디렉토리의 위치이다. 만약 디렉토리가 존재하지 않으면, 자동으로 생성될 것이다.
  • name 은 프로젝트의 이름이다. 이것은 선택 사항이다. 만약 이름이 제공되면, 이 이름은 응용프로그램을 빌드할 때 .apk 파일 이름으로 사용될 것이다.


여기 예제가 있다 :


android update project --name MyApp --target 2 --path ./MyAppProject


라이브러리 프로젝트 설정하기



라이브러리 프로젝트는 표준 안드로이드 프로젝트이다. 그래서 당신은 새로운 응용프로그램 프로젝트를 생성하는 것과 같은 방식으로 새로운 라이브러리 프로젝트를 생성할 수 있다. 특히, 당신은 필요한 파일들과 폴더들을 가진 새로운 라이브러리를 생성하기 위해 android 툴을 사용할 수 있다.


새로운 라이브러리 프로젝트를 생성하려면, <sdk>/tools/ 디렉토리로 이동하고 다음 명령을 사용하라 :


android create lib-project --name <your_project_name> \

--target <target_ID> \

--path path/to/your/project \

--package <your_library_package_namespace>


create lib-project 명령은 그 프로젝트가 라이브러리임을 빌드 시스템에 알려주는 프리셋 속성을 포함하는 표준 프로젝트 구조를 생성한다. 그것은 프로젝트의 project.properties 파일에다가 다음 라인을 추가함으로써 수행된다 :


android.library=true


명령이 종료되면, 라이브러리 프로젝트가 생성되고 당신은 아래 섹션들에서 설명하는 것처럼 소스 코드와 리소스들을 그 프로젝트 안으로 옮기기 시작할 수 있다.


만약 당신이 현존하는 응용프로그램 프로젝트를 라이브러리 프로젝트로 변환해서 다른 응용프로그램들이 그것을 사용할 수 있도록 하고 싶다면, 당신은 adnroid-library=true 속성을 응용프로그램의 project.properties 파일에 추가함으로써 그 작업을 수행할 수 있다.


매니페스트 파일 생성하기


라이브러리 프로젝트의 매니페스트 파일은 표준 안드로이드 응용프로그램처럼 반드시 그것이 포함하고 있는 공유 요소들을 모두 선언해야만 한다. 더 많은 정보를 원한다면, AndroidManifest.xml 을 위한 문서를 참조하라.


예를 들어, TicTacToeLib 예제 라이브러리 프로젝트는 GameActivity 라는 Activity 를 선언한다 :


<manifest>

  ...

  <application>

    ...

    <activity android:name="GameActivity" />

    ...

  </application>

</manifest>


라이브러리 프로젝트 갱신하기


만약 당신이 라이브러리 프로젝트의 빌드 속성들( 빌드 타겟, 위치 )을 갱신하고자 한다면, 다음 명령을 사용하라 :


android update lib-project \

--target <target_ID> \

--path path/to/your/project


라이브러리 프로젝트 참조하기



만약 당신이 응용프로그램을 개발하고 있고 공유 코드와 공유 리소스들을 라이브러리 프로젝트로부터 가져 오기를 원한다면, 당신은 응용프로그램 프로젝트의 빌드 속성들에 라이브러리 프로젝트에 대한 참조를 추가함으로써 이를 쉽게 수행할 수 있다.


라이브러리 프로젝트에 대한 참조를 추가하려면, <sdk>/tools/ 디렉토리로 가서 다음 명령을 사용하라 :


android update project \

--target <target_ID> \

--path path/to/your/project

--library path/to/library_projectA


이 명령은 응용프로그램의 빌드 속성들을 갱신해서 라이브러리 프로젝트에 대한 참조를 포함하도록 한다. 특히, 그것은 android.library.reference.n 속성을 프로젝트의 project.properties 파일에 추가한다. 예를 들면 :


android.library.reference.1=pat/to/library_projectA


여러 개의 라이브러리들에 대한 참조를 추가하고 있다면, 당신은 그것의 상대적인 우선순위를 ( 그리고 merge 순서를 ) 설정할 수 있는데, 이는 project.properties 파일을 수작업으로 편집하고 각 참조들의 .n 인덱스를 적절하게 변경함으로써 이루어 진다. 예를 들어, 이러한 참조들을 가정해 보자 :


android.library.reference.1=path/to/library_projectA

android.library.reference.2=path/to/library_projectB

android.library.reference.3=path/to/library_projectC


당신은 library_projectC 에 가장 높은 우선순위를 부여하기 위해 다음과 같이 참조들을 재정렬할 수 있다 :



android.library.reference.2=path/to/library_projectA

android.library.reference.3=path/to/library_projectB

android.library.reference.1=path/to/library_projectC


레퍼런스들에서 .n 인덱스는 "1" 로 시작하며 "구멍" 없이 하나씩 증가한다는 것에 주의하라. 빠진 부분이 생긴 다음의 인덱스에서 나타나는 참조들은 무시된다.


빌드시에, 그 라이브러리들은 응용프로그램에 동시에 merge 되는데, 가장 낮은 우선순위에서 시작해 가장 높은 우선순위로 merge 된다. 라이브러리는 스스로는 다른 라이브러리를 참조할 수 없으며, 빌드시에 라이브러리들은 응용프로그램과 merge 되기 전에 다른 것들과 merge 되지 않는다는 것에 주의하라.


매니페스트 파일에서 라이브러리 요소 선언하기


응용프로그램 프로젝트의 매니페스트 파일에서, 당신은 응용프로그램이 라이브러리 프로젝트로부터 들여 오게 되는 응용프로그램이 사용하게 될 모든 요소들에 대한 선언을 추가해야만 한다. 예를 들어, 당신은 모든 <activity>, <service>, <receiver>, <provider> 등을 선언해야만 한다. 그리고 <permission>, <uses-library> 및 그와 유사한 요소들도 선언해야만 한다.


선언들은 라이브러리 요소들의 fully-qualified 이름들을 사용해 적절한 위치의 라이브러리 요소들을 참조한다.


예를 들어, TicTacToeMain 예제 응용프로그램은 다음과 같이 GameActivity 라는 라이브러리 Activity 를 선언한다 :


<manifest>

  ...

  <application>

    ...

    <activity android:name="com.example.android.tictactoe.library.GameActivity" />

    ...

  </application>

</manifest>


매니페스트 파일에 대한 더 많은 정보를 원한다면, AndroidManifest.xml 을 위한 문서를 참조하라.


의존성 있는 응용프로그램 빌드하기


하나 이상의 라이브러리 프로젝트에 의존하는 응용프로그램 프로젝트를 빌드하기 위해, 당신은 Build and Running 에 기술된 것처럼 표준 Ant 빌드 명령들과 컴파일 모드들을 사용할 수 있다. 그 툴들은 의존성 있는 응용프로그램 프로젝트를 컴파일하는 작업의 일부로서 응용프로그램에 의해 참조되는 모든 라이브러리들을 컴파일하고 merge 한다. 다른 명령들이나 단계들이 필요하지 않다.


Ubuntu 12.10 에서 NDK 를 설치할 때, 압축 관리자로 그냥 압축을 푸는 경우에 ndk-build 를 실행하면 다음과 같은 에러를 만날 수 있다.



이 문제 때문에 한참 고생을 했는데, 압축이 잘 못 풀려서 arm-linux-androideabi-gcc 가 생성되지 않기 때문에 발생하는 문제이다. 파일탐색기에서 "여기에 풀기"를 통해 압축을 풀면 이상하게 압축이 제대로 풀리지 않는 문제가 있다.


그래서 다음과 같이 직접 압축을 풀었더니 arm-linux-androideabi-gcc 가 제대로 생성되었다.



파일탐색기에서 푸는 것이 왜 제대로 안 풀리는 지는 모르겠다. 한 번 알아 볼 필요가 있을 것 같다.


AskUbuntu 에다가 질문을 올려 두었다. 어떤 답이 나올지 궁금하다. 리눅스 초보자라 이것 저것 모르는게 많아 골치 아프다; Why does a "Extract Here" context menu work incorrectly?


주의 : 공부하면서 정리한 내용이라 오류가 있을 수 있습니다.

주의 : 가독성을 높이기 위해서 잘 알려진 용어나 발음이 비슷한 용어는 한글로 표기합니다.




Android NDK 개발 환경 : Cygwin


왜 Cygwin 이 필요한가?



개발자라면 자신이 개발하고 있는 환경에 대해서 제대로 이해해야 할 필요가 있다고 생각한다. NDK 를 사용해서 안드로이드 앱을 개발하기 전에 개발환경에 대해서 대강이라도 이해해 보고자 공부하면서 정리해 본다.


안드로이드는 기본적으로 리눅스 커널을 기반으로 올라 가 있는 가상머신이기 때문에 NDK 는 리눅스 개발환경과 관련이 있다. 그러므로 윈도우즈 환경에서 안드로이드 NDK 프로그래밍을 하기 위해서는 시그윈이 필수이다. 아래 그림은 안드로이드의 시스템 아키텍처를 설명하는 것으로서, 인터넷에 가장 많이 퍼져 있는 것을 주워 왔다.



NDK 로 개발을 한다는 것이 의미하는 바가 무엇인지 생각해 볼 필요가 있다. 많이들 알고 있는 사실이겠지만, 어떤 프로그램이 어떤 머신에서 실행되기 위해서는 해당 머신에 맞는 실행가능한 파일( executable )을 만들어야만 한다. 그것이 소위 우리가 알고 있는 '컴파일'과 '링크'라는 작업이다. 이 컴파일 작업과 링크 작업을 합쳐서 '빌드'라고 표현한다.


우리가 열심히 코딩한 라인들은 어셈블리어로 변환되고, 이 어셈블리어들은 다시 기계어로 변환된다. 그리고 CPU 는 이 기계어를 마이크로 명령어로 나누어서 프로그램을 실행하게 된다. 그런데 이 CPU 가 어떠한 명령어 집합 구조( Instruction Set Architecture, ISA )를 제공하느냐에 따라서 다른 기계어를 만들어야 하기 때문에, 프로그램을 빌드할 때는 해당 CPU 에 맞는 구성을 만들어야 한다. 예를 들어 CPU 가 x86 ISA 를 제공한다면 x86 에 맞는 빌드 구성을 해야 하고, CPU 가 x86/64 를 제공한다면 거기에 맞는 빌드 구성을 만들어야 한다.


자 우리가 NDK 를 통해 만든 바이너리를 실행할 플래폼의 환경은 어떠한가? 일반적으로 현재 안드로이드 OS 가 돌아 가는 환경은 ARM 코어를 사용하는 리눅스 환경이다. 이는 어떠한 사실을 내포할까?


    • 우리가 C/C++ 응용프로그램을 개발할 때 CreateFile() 과 같은 Win32 API 를 사용할 수 없다.

    • Visual Studio 를 이용할 수 없다. --> VC++ 컴파일러를 사용할 수 없다.


사실 Visual Studio 의 컴파일러를 GCC( GNU Compiler Collection ) C++ 컴파일러로 연결해 놓고 개발할 수는 있을 것이다. 하지만 기본 환경 자체가 Win32 서브시스템과 x86 혹은 x86-64 아키텍처를 위한 개발에 최적화되어 있으므로 참 어려운 일이 될 것이다. 구글링을 해 보면 여러 가지 자료들이 나오기는 하는데 얼마나 편한 지는 잘 모르겠다. 어쨌든 우리는 리눅스 OS 를 기반으로 ARM ISA 에 맞춰서 빌드해야만 하는 상황이고, 윈도우즈 환경에서 이를 가능하게 만들 수 있는 방법을 찾아야만 한다. 


이때 우리에게 한 줄기 빛을 비춰주는 존재가 있으니, 그것이 바로 시그윈이다.


Cygwin 이란 무엇인가?



일단 시그윈 공식 사이트의 설명을 인용해 보도록 하겠다. 출처 : http://cygwin.com/index.html


시그윈은 :

  • 윈도우즈를 위해 리눅스처럼 느껴지는 환경을 제공하는 도구들의 집합이다.

  • 많은 리눅스 API 기능을 제공하는 리눅스 API 레이어로서 동작하는 DLL( cygwin1.dll )이다.


그리고 다음과 같이 착각하지 말라고 경고하고 있다.


시그윈은  :

  • 윈도우즈 상에서 네이티브 리눅스 앱들을 실행하는 방법이 아니다. 당신이 윈도우즈 상에서 프로그램을 실행하기 원한다면, 소스로부터 앱들을 리빌드해야만 한다.
  • signal, pty 등과 같은 유닉스 기능들을 인지하는 네이티브 윈도우즈 앱들을 마술처럼 만들어 주는 방법이 아니다. 다시 말하지만, 당신이 시그윈 기능의 이점을 취하기 원한다면, 소스로부터 앱들을 리빌드할 필요가 있다.


앞에서도 언급했지만 특정 CPU 아키텍처에 대해 리눅스용으로 빌드된 프로그램은 윈도우즈에서 실행될 수 없다. 당연히 윈도우즈 환경에 맞는 구성으로 다시 빌드해야만 한다. 시그윈은 크로스 플래폼 개발을 가능하게 해주는 환경일 뿐이지, 에뮬레이터가 아니라는 것을 명심해야만 한다.


Cygwin 의 작동 원리.



그렇다면 시그윈은 어떻게 해서 리눅스와 같은 개발 환경을 지원하게 되는 것일까?


여러분은 Visual Sutdio 를 통해 프로젝트 설정을 하다가 '서브시스템' 이라는 항목을 본 적이 있을 것이다. 윈도우즈는 여러 개의 서브시스템을 가지고 있는데, 우리가 주로 사용하는 것이 Win32 서브시스템이다. 아래 그림은 Windows NT 의 시스템 아키텍처에서의 POSIX 서브시스템을 보여 주고 있다.


윈도우즈 NT 시스템 아키텍처( 출처 : 위키피디아 )


이 POSIX 라는 것은 Portable Operating System Interface 의 약자로 IEEE 가 운영체제 간의 호환성을 유지하기 위해서 제정한 표준이다.


POSIX( 대충 '파직스'라고 발음됨 )는 "Portable Operating System Interface" 의 머리글자들이며, 운영체제 간의 호환성을 유지하기 위해서 IEEE 에 의해 제정된 표준 패밀리이다. POSIX 는 다양한 유닉스 및 다른 운영 체제들 간의 소프트웨어 호환성을 위해 응용프로그램 인터페이스( API )와 명령줄 쉘, 그리고 유틸리티 인터페이스들을 정의한다.


중략...


유닉스 같은 운영 체제 환경을 위한 POSIX 명세는 원래 코어 프로그래밍 인터페이스를 위한 단일 문서로 구성되어 있었지만, 결국에는 19 개의 분리된 문서로 늘어 났다( 예를 들면 POSIX1, POSIX2, 등 ). 표준화된 사용자 명령 줄과 스크립팅 인터페이스들은 Korn Shell 에 기반했다. awk, echo, ed 등을 포함하는 많은 사용자 수준 프로그램, 서비스, 유틸리티들도 표준화되었으며, 기본적인 I/O( 파일, 터미널, 네트웤 )을 포함하는 프로그램 수준 서비스들도 포함한다. POSIX 는 표준 스레딩 라이브러리 API 도 정의하는데, 이는 현대의 거의 대부분의 OS 에 의해서 지원된다. 요새는 대부분의 POSIX 파트들이 하나의 IEEE Std 1003. 1-2008 표준에 포하모디었으며, 이는 POSIX 1-2008 로 알려져 있다.


하략...


- 출처 : 위키피디아, POSIX


OS/2 라는 것은 MS 와 IBM 이 초기에 제작한 운영체제로 요새는 별로 의미가 없으므로 관심있는 분들은 위키피디아의 OS/2 라는 글을 따로 읽어 보기 바란다.


POSIX 를 지원하는 OS 리스트는 다음 링크의 'POSIX-oriented operating systems' 절에서 찾아 볼 수 있다 : 위키피디아, POSIXGNU/Linux 같은 경우에는 POSIX 에 대해 '거의 대부분의 호환성' 을 가지고 있다. 시그윈은 POSIX 에 대해 '많은 호환성' 을 가지고 있다고 표현되고 있다. 즉 안드로이드 시스템의 기반이 되는 리눅스가 POSIX 를 거의 완벽하게 지원하고 있기 때문에, 우리는 윈도우즈 상에서 시그윈을 통해 거기에 맞는 환경을 구성할 수 있는 것이다.


위에서 윈도우즈의 POSIX 서브시스템에서 언급을 했는데 여기에서 윈도우즈의 POSIX 를 언급한 것은 그냥 예를 든 것 뿐이다. 시그윈이 윈도우즈의 POSIX 서브시스템의 레이어라는 이야기는 아니다. 왜 그럴까? 윈도우즈의 POSIX 서브시스템은 POSIX 표준을 제대로 지키고 있지 않기 때문이다. 


시그윈은 윈도우즈의 POSIX 와는 개별적으로 동작하는 개발환경이다. 시그윈 환경, 시그윈 서브시스템, 시그윈 플래폼 등의 용어를 사용해도 무방하지 않을까 싶다. 시그윈이 윈도우즈의 POSIX 서브시스템의 레이어로서 동작하지 않는 이유는 다음 글을 보면 좀 더 이해가 가지 않을까 싶다 : The Sad History of the Microsoft POSIX Subsystem. 아래에 일부만 인용해 보았다. 요약하면 윈도우즈 POSIX 가 허접(?)이란 것과 XP 부터는 POSIX 서브시스템을 지원하지 않는다는 것이다. 그 뒤의 이야기가 쭉 나오는데 다 읽어 보지는 못했다.


윈도우즈 NT 가 처음 개발되고 있었을 때, 목표 중의 하나는 커널을 프로그래밍 인터페이스와 분리하는 것이었다. NT 는 원래 OS/2 를 성공시키기 위한 것이었지만, 마이크로소프트는 윈도우즈 3.x 응용프로그램들을 실행하기 위한 호환성을 포함시키고, 방위산업 시장에 판매하기 위한 1980 년대 DoD( 국방성 ) Orange Book 과 FIPS 명세들을 만족시키기 원했다. 결과적으로 윈도우즈 NT 는 복잡한 자유재량에 의한 접근 제어 기능을 가진 멀티유저 플래폼으로서 개발되었으며, 하이브리드 마이크로커널 3 유저랜드[각주:1] 환경들로서 구현되었다 :


  • Windows( Win32 )
  • OS/2
  • POSIX


마이크로소프트는 Win32 문제를 가지고 IBM 과 다퉜으며, NT 프로젝트는 OS/2 와 갈라섰다. 팀의 초점은 Win32 로 이동했다. 그래서 Win32 API 를 호스트하는 Client Server Runtime Subsystem( CSRSS ) 은 의무적이 되었으며, OS/2 와 POSIX 서브시스템이 실제로 완료되지 않았음에도 불구하고 그것들은 윈도우즈 NT 부터 윈도우즈 2000 까지 5 개의 버전에 포함되었다. OS/2 서브시스템은 단지 OS/2 1.0 명령줄 프로그램들만을 실행할 수 있으며, presentation manager 에 대한 지원이 존재하지 않았다. POSIX 서브시스템은 POSIX.1 명세를 지원했지만, 모든 종류의 쉘이나 유닉스 같은 환경을 제공하지 않았다. Win32 가 윈도우즈 95 의 형태에서 성공하면서, OS/2 와 POSIX 서브시스템의 개발은 중단되었다. 그것들은 윈도우즈 XP 와 윈도우즈 서버 2003 에서 완전히 죽고 사라졌다.


반면에 Softway Systems 는 1996 년 쯤에 OpenNT 라고 불리는 유닉스-윈도우즈 NT 간 포팅 시스템을 개발했다. OpenNT 는 NT POSIX 서브시스템 상에서 빌드되었지만, 그것에 살을 붙여 이용할 수 있는 유닉스 환경을 제공했다. 이는 유닉스 시스템이 매우 비싸진 시점이었다. Softway 는 OpenNT 를 사용해서 US Federal agency 들을 위한 여러 유닉스 응용프로그램들을 윈도우즈 NT 로 변경했다. 1998 년에 OpenNT 는 Interix 라고 이름을 바꿨다. Softway Systems 는, 마이크로소프트 POSIX 서브시스템이 지원하지 않았던 시스템 호출을 구현하고, 풍부한 libc,  파일 시스템에 대한 single-rooted view, 그리고 실용적인 gcc 를 개발하기 위해, 최종적으로 NT POSIX 서브시스템에 대한 완전한 대체물을 만들었다.


하략...


위키피디아에 의하면 Windows Server 2003 R2 부터는 사실은 Interix 의 변형인 SUA( Subsystem for UNIX-based Applications )를 통해서 POSIX 를 지원하고 있다고 한다. 이것은 SFU( Windows Services for UNIX )라고 불리기도 한다. Windows Vista 및 Windows 7 의 Enterprise 에디션과 Ultimate 에디션도 SUA 를 포함하고 있다고 한다. 자세한 내용은 Windows Services for UNIX 를 참조하라.


결론.



우리가 윈도우즈 환경에서 리눅스 ARM 용 프로그램을 빌드하기 위해서는, 그에 맞는 바이너리를 생성해 줄 수 있는 환경( 컴파일러, API 등 )을 구성할 필요가 있다. 윈도우즈에서 이를 가능하게 해 주는 것이 POSIX 기반의 시그윈 개발 환경이다. 


우리는  시그윈 환경을 이용해 리눅스 ARM 용 프로그램을 빌드하고, 그것을 안드로이드 장치로 복사하고 설치하게 된다. 이것이 윈도우즈와 리눅스 간의  크로스 플래폼 개발이다. 안드로이드 SDK 를 사용하게 되면 자바 바이트 코드( 플래폼에 독립적인 중간 코드 )를 생성하기 때문에 빌드 환경이 중요하지 않지만, 안드로이드 NDK 는 실제 리눅스 API 를 이용하고 기계어로 컴파일하기 때문에 반드시 올바른 환경에서 개발해야만 한다.


이제 시그윈이라는 것의 정체에 대해 어느 정도 이해할 수 있게 되었을 것이라 생각한다. 그리고 시그윈을 통해 뭔가를 빌드한다는 것의 의미에 대해서 이해할 수 있게 되었을 것이라 생각한다.


다음에 시간이 나면 POSIX 에 대해서 좀 더 구체적으로 다뤄 보도록 하겠다.


  1. 사용자의 응용프로그램들이 운영체제에 대한 위험 부담 없이 실행될 수 있는 커널 외부의 개념 공간 - 출처 : 윅셔너리 [본문으로]

책의 71 페이지에서는 다음과 같은 명령어를 사용해서 APK 를 만들라고 하고 있다.



현재 1.8.4 버전의 ant 를 설치했는데, 이것은 구체적으로 빌드 타겟을 지정할 것을 요구한다.




그러므로 위에서 설명하고 있는 것처럼 아래와 같은 형식으로 호출해야 한다.



책의 70 페이지에서는 다음과 같이 project 를 설정하라고 이야기하고 있다.



필자는 윈도우 환경이기 때문에 cygwin 에서 다음과 같이 실행했다.



error 를 보면 알 수 있듯이 "-p" 옵션은 프로젝트가 있는 디렉토리의 경로를 지정할 것을 요구한다.


그러므로 현재 경로인 "./" 을 넣어서 해결했다.



책의 35 페이지에서는 ".bash_profile" 이라는 파일에 다음과 같은 식으로 스크립트를 추가할 것을 요구하고 있다.



환경변수를 가지고 올 때 MS_DOS 형식으로 인식하는 것을 막기 위함이라고 하는데, 저자가 설치한 것이 어떤 버전인지는 모르겠지만, 현재 설치한 버전(  1.7.17-1 )에서는 그런 문제가 자동으로 해결되어 있는 것 같다.


실제로 저런 스크립트를 추가하게 되면 오히려 제대로 동작하지 않는다.

+ Recent posts