android-gcc

안드로이드(Android) NDK 의 목표중의 하나는 ARM 바이너리를 만드는 크로스툴체인(cross-toolchains)을 제공하는 것이다. 하지만 이 툴체인은 NDK가 제공하는 빌드환경과 결합되어 있다. 때문에 따로 분리하여 독립적인 환경에서 사용하려고 하면 불편한 점이 많다. Andrew Ross 의 agcc 는 이러한 불편을 해소하는 한가지 방법을 제공한다. 리눅스 배포판에 포함된 gcc와 유사한 동작을 하는 펄 스크립트를 제공하는 것이다. 이 펄 스크립트가 안드로이드에 필요한 명령행 옵션들을 적절한 위치에 추가하게 된다. 이런 접근법은 외부 라이브러리를 빌드 하는 작업에서 꽤 편리하고 효과적인 수단을 제공한다. 하지만 agcc 는 NDK가 아닌 안드로이드 소스트리에 포함된 툴체인과 함께 사용되도록 준비되었고, 최근의 안드로이드 변경에 따른 수정판도 발견되지 않는다는 문제점이 있다. 이 곳에서 제공되는 android-gcc 는 NDK 최신 버전에 포함된 툴체인을 사용하도록 파이썬(Python)으로 재 작성되었다. android-gcc 가 동작하기 위한 선행조건은 다음과 같다.

  • Python 2.5 이상
  • NDK 1.6 이상 (Windows 와 Linux 버전만 확인되었다.)
  • NDK 의 arm-eabi-gcc 가 포함된 디렉토리가 PATH 에 포함되어 있어야 한다.

gcc를 흉내 내므로 사용법을 따로 설명하지는 않지만, 몇 가지 기억해둘 내용이 있다.

  • android-gcc 를 적당한 위치에 다운로드 한 후 chmod +x 로 실행 가능하도록 만들어주면 설치가 완료된다. 윈도우즈 쓰시는 분들은 저장할 때 도스파일 형식으로 바뀌지 않도록 주의해주세요. wget 권장합니다.
  • 환경 변수 APP_OPTIM 을 release 또는 debug 으로 설정할 수 있다. APP_OPTIM이 정의되지 않은 경우, 명령행 옵션 -DNDEBUG 가 주어지면 release 모드로, 그렇지 않으면 debug 모드로 빌드 한다.
  • 환경변수 TARGET_PLATFORM 을 통해 플랫폼을 지정할 수 있다. 기본은 android-3 이고 android-4 를 지정할 수도 있다.
  • 환경변수 LOCAL_ARM_MODE 를 thumb 또는 arm 으로 설정할 수 있다. 정의되지 않은 경우 release 모드에서는 thumb 가, debug 모드에서는 arm이 선택된다.
  • 환경변수 ALLOW_UNDEFINED 를 yes로설정하면링크시에 –-no-undefined 옵션을제거한다.
  • 공유 라이브러리를 빌드하는 명령행 옵션은 -shared 다.
  • 버전은 호환되는 NDK 버전 뒤에 숫자를 붙여서 나타낸다. 즉 1.6.0 은 NDK 1.6과 호환되는 첫 번째 버전이다.
#!/usr/bin/env python
# coding=utf-8
#
# android-gcc 1.6.0
# http://www.flowdas.com/android-gcc
# Copyright (c) 2009, 오 동권(Dong-gweon Oh) prospero@flowdas.com
#
import sys, os

def cache(fn):
    def cached_fn():
        if not hasattr(fn, 'cache'):
            fn.cache = fn()
        return fn.cache
    return cached_fn

def substitute(command):
    return os.popen(command).read().strip()

@cache
def TARGET_PLATFORM():
    return os.environ.get('TARGET_PLATFORM', 'android-3')

@cache
def SYSROOT():
    for dir in os.environ.get('PATH', '').split(os.pathsep):
        if not dir: continue
        if os.path.exists(os.path.join(dir, target_compiler())):
            toolroot = os.path.abspath(os.path.join(dir, '..'))
            return os.path.abspath(os.path.join(toolroot, '../../../platforms/%s/arch-arm' % TARGET_PLATFORM()))
    sys.exit('Cannot determine SYSROOT')

@cache
def target_libgcc():
    return substitute('arm-eabi-gcc -mthumb-interwork -print-libgcc-file-name')

@cache
def CPPFLAGS():
    return [
        '-I%s/usr/include' % SYSROOT(),
        '-D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -DANDROID',
        ]

@cache
def CFLAGS_options():
    flags = []
    APP_OPTIM = os.environ.get('APP_OPTIM')
    if APP_OPTIM is None:
        if '-DNDEBUG' in sys.argv[1:]:
            release = True
        else:
            release = False
    elif APP_OPTIM == 'release':
        release = True

    elif APP_OPTIM == 'debug':
        release = False
    else:
        sys.exit('Unknown APP_OPTIM')
    LOCAL_ARM_MODE = os.environ.get('LOCAL_ARM_MODE')
    if LOCAL_ARM_MODE is None:
        thumb = release
    elif LOCAL_ARM_MODE == 'thumb':
        thumb = True
    elif LOCAL_ARM_MODE == 'arm':
        thumb = False
    else:
        sys.exit('Unknown LOCAL_ARM_MODE')
    if release:
        if '-DNDEBUG' not in sys.argv[1:]:
            flags.append('-DNDEBUG')
        flags.append('-fomit-frame-pointer')
        if thumb:
            flags.append('-Os')
        else:
            flags.append('-O2')
    else:
        flags.append('-O0 -fno-omit-frame-pointer')
    if thumb:
        flags.append('-mthumb -fno-strict-aliasing -finline-limit=64')
    else:
        flags.append('-marm -fstrict-aliasing -funswitch-loops -finline-limit=300')
    return flags

@cache
def CFLAGS():
    cflags = [
        CPPFLAGS(),
        '-march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -g',
        CFLAGS_options(),
        CXXFLAGS(),
        ]
    return cflags

@cache
def CXXFLAGS():
    if target_compiler() == 'arm-eabi-g++':
        return  '-fno-exceptions -fno-rtti'
    else:
        return []

@cache
def no_undefined():
    if os.environ.get('ALLOW_UNDEFINED') == 'yes':
        return ''
    else:
        return '-Wl,--no-undefined'

@cache
def LDFLAGS():
    return '-nostdlib -Bdynamic -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc  %(SYSROOT)s/usr/lib/libc.so %(SYSROOT)s/usr/lib/libstdc++.so %(SYSROOT)s/usr/lib/libm.so %(SYSROOT)s/usr/lib/crtbegin_dynamic.o -L%(SYSROOT)s/usr/lib' % {'SYSROOT' : SYSROOT()}

@cache
def target_libgcc():
    return substitute('arm-eabi-gcc -mthumb-interwork -print-libgcc-file-name')

@cache
def LDFLAGS_epilog():
    return [
        no_undefined(),
        '-Wl,-rpath-link=%(SYSROOT)s/usr/lib %(libgcc)s %(SYSROOT)s/usr/lib/crtend_android.o' % {'SYSROOT':SYSROOT(), 'libgcc':target_libgcc()},
        ]

@cache
def LDSHFLAGS():
    return '-nostdlib -Wl,-shared,-Bsymbolic -L%(SYSROOT)s/usr/lib' % {'SYSROOT':SYSROOT()}

@cache
def LDSHFLAGS_epilog():
    return [
        '-Wl,--whole-archive -Wl,--no-whole-archive %(SYSROOT)s/usr/lib/libc.so %(SYSROOT)s/usr/lib/libstdc++.so %(SYSROOT)s/usr/lib/libm.so' % {'SYSROOT':SYSROOT()},
        no_undefined(),
        '-Wl,-rpath-link=%(SYSROOT)s/usr/lib %(libgcc)s' % {'SYSROOT':SYSROOT(), 'libgcc':target_libgcc()},
        ]

@cache
def target_compiler():
    return os.path.basename(sys.argv[0]).replace('android-', 'arm-eabi-')

def cmd_build_empty():
    return [
        target_compiler(),
        sys.argv[1:],
        ]

def cmd_build_executable():
    return [
        target_compiler(),
        CFLAGS(),
        LDFLAGS(),
        sys.argv[1:],
        LDFLAGS_epilog(),
        ]

def cmd_build_c():
    return [
        target_compiler(),
        CFLAGS(),
        sys.argv[1:],
        ]

def cmd_build_E():
    return [
        target_compiler(),
        CPPFLAGS(),
        sys.argv[1:],
        ]

def cmd_build_S():
    return cmd_build_c()

def cmd_build_shared():
    argv = sys.argv[1:]
    argv.remove('-shared')
    return [
        target_compiler(),
        CFLAGS(),
        LDSHFLAGS(),
        argv,
        LDSHFLAGS_epilog(),
        ]

def cmd_build():
    mode = None
    for opt in ('-c', '-E', '-S', '-shared'):
        if opt in sys.argv[1:]:
            mode = opt
            break
    if mode is None:
        mode = '-empty'
        skip = False
        for arg in sys.argv[1:]:
            if arg.startswith('-'):
                if arg in ('-o', '-x'):
                    skip = True
            elif skip:
                skip = False
            else:
                mode = '-executable'
                break
    return eval('cmd_build_%s()' % mode[1:]),

def deepjoin(obj):
    if type(obj) == type(''): return obj
    return ' '.join(map(deepjoin, obj))

cmd = deepjoin(cmd_build())
cmd = cmd.replace('"', '"\\"')
os.system(cmd)