NAS에서 Thumbs.db 파일을 제거하기 위해 셸에서 아래와 같은 명령을 실행했다. (곧바로 rm 명령을 실행하기는 위험하기 때문에 우선 파일 경로를 제대로 찾아내는지를 확인하기 위한 스크립트이다)

$ for f in `find . | grep Thumbs.db`; do echo "$f"; done

경로에 빈 칸이 있을 경우를 대비해서 따옴표로 감싸줬는데, 빈 칸이 전부 line break되어 나왔다.

예를 들어 /space in path/Thumbs.db와 같은 경로가 있을 경우

/space
in
path/Thumbs.db

이런 식으로 출력되는 것이다.

그런데 생각해보니 bash의 배열은 공백으로 구분되기 때문에 당연한 결과였다.

for f in `find . | grep Thumbs.db` 이 부분은 for f in /space in path/Thumbs.db나 마찬가지이고, 배열의 원소인 /space, in, path/Thumbs.db가 차례로 들어갔던 것이다.

그리고 저런 형태로 스크립트를 구성하면 find-grep의 결과가 출력될 때마다 do-done 사이의 명령이 실행되는 것이 아니라, find-grep이 전부 실행되고 난 다음 do-done 사이의 명령이 실행된다.

그동안 너무 빠른 시스템에서 적은 수의 파일만 다루다보니 차이를 잘 몰랐는데, 이런 경우는 find -name -exec를 사용하는 것이 낫다.

$ find . -name Thumbs.db -exec rm "{}" \;

Ubuntu 16.04 네트워크 매니저의 기본 VPN 플러그인은 PPTP이다. IKEv2 방식의 VPN을 사용하기 위해 network-manager-strongswan과 같은 패키지들을 설치해도 strongswan이 나타나지 않는다.[1]

링크에 나와있듯 1.4.0에서는 수정되었다고 하는데, Ubuntu Package archive에는 아직 반영되지 않아 update, upgrade를 실행해도 여전히 1.3.1이 최신이라고 나온다. https://wiki.strongswan.org/projects/strongswan/wiki/NetworkManager 페이지의 Building from source 섹션을 참고해서 소스를 다운받아 컴파일한다. make를 실행하다보면 위키에 나와있지 않은 의존성들이 빠진 경우가 있는데 그때그때 설치해주고 다시 make를 실행한다. (없다고 나오는 패키지들 이름 뒤에 -dev를 붙인 패키지들을 설치해준다.)

make가 성공적으로 실행되고 나면 make install을 실행해야하는데, 위키의 설명과는 달리 sudo를 붙여서 실행해야한다.

모든 과정을 완료하고 나면 새로운 연결을 추가할 때 아래와 같이 IPsec/IKEv2 (strongswan)을 고를 수 있게 된다.

첫 커밋을 생성하고 나면 나중에 버전관리에서 제외시키고 싶은 파일을 .gitignore에 추가해도 계속 변경상태가 git status에 나타난다.

이런 상황을 몇 번 겪어서 어떻게 해야하는지 대략적으로는 알고 있지만, 자주 쓸 일이 없다보니 매번 다시 검색하고, 이게 그 때 썼던 방법이 맞는지 확인하는 작업이 귀찮아서 확실히 기록을 남기고자 포스트를 작성했다.

$ git rm -r --cached .
$ git add .
$ git commit -m ".gitignore 수정"

git rm 명령은 파일을 삭제하고, 그 삭제된 상태를 스테이징한다[1]. 워킹 카피는 그대로 두고 git이 더이상 추적하지 않도록만 하려면(인덱스 영역에서만 삭제하려면) --cache 옵션을 사용하면 된다. -r 옵션은 다른 명령어들과 마찬가지로 recursive를 의미한다. 즉, git rm -r --cached .현재 디렉토리와 하위 모든 파일을 워킹 카피는 그대로 둔 채 인덱스 영역에서만 삭제하라는 의미이다.

이 명령을 실행하고 나면 모든 파일이 deleted 상태로 바뀐다. 그러나 ls 명령으로 확인해보면 실제로 파일이 삭제되진 않은 상태이니 놀랄 필요 없다. 이어서 git add .을 실행하면 .gitignore에서 제외시킨 파일만 deleted 상태임을 알 수 있다. 이제 git commit을 해주면 파일이 삭제되었다는 정보가 커밋된다.

주의점: 다른 사람이 git pull 등으로 이 커밋을 받으면 그 사람 컴퓨터에서는 워킹 카피가 삭제된다. 로컬에서 다시 생성하기 어려운 파일이라면 미리 알린 다음에 적용해야한다.

NDK를 사용하는 프로젝트에서 여러 개의 네이티브 라이브러리가 필요한 경우가 있다. 처음에는 jni/ 하위 디렉토리에 라이브러리 소스코드를 전부 받아두고 Android.mk 파일을 적절히 구성해서 한번에 빌드해보려고 시도했었다.

jni/
    Android.mk
    (project source code...)
    libraryA/
        Android.mk
        (library a source code...)
    libraryB/
        Android.mk
        (library b source code...)

jni/Android.mk는 하위 디렉토리를 탐색하고, jni/libraryA/Android.mkjni/libraryB/Android.mk는 그 디렉토리의 Makefile을 바탕으로 빌드(make)를 실행하면 되지 않을까 하는 생각이었다.

결론부터 말하자면 그런 거 안된다. 소스코드를 바로 받아서 그대로 둔 상태에서 Android.mk만 만든다고 해서 빌드가 되지는 않는다. (정확히 말하자면, 하려면 할 수야 있겠지만 수고에 비해서 얻을 것이 없어보인다.) 이는 빌드 시의 환경설정 단계 때문인데 자세한 내용은 다음과 같다.

대부분의 C/C++ 라이브러리들은 다중 플랫폼 지원을 위해 autoconf, automake, libtool 등을 사용하여 build time에 자동으로 환경을 구성하고, 그 환경에 따라 동적으로 Makefile 및 config.h 파일 등을 생성하게 된다. 크로스 컴파일 환경이 아니라면 아래와 같이 간단하게 환경 설정과 빌드 및 설치가 가능하다.

$ ./configure && make && make install

그러나 ndk-build는 크로스 컴파일이고, 크로스 컴파일을 하려면 make install은 당연히 의미가 없고 (빌드하는 머신과 설치되는 머신이 다르고 시스템에 직접 설치하는 것이 아니라 앱의 패키지에 포함되어야한다) configure 역시 수작업으로 변수들을 지정해주어야한다 (이런 작업들을 물론 Android.mk로 하려면 할 수야 있겠지만…). configure가 실행되지 않으면 Makefile이 생성되지 않아 빌드를 할 수 없다.

다행히 유명한 라이브러리들은 Android용 바이너리를 빌드하기 위한 방법을 문서로 제공하고 있다.

예: libjpeg-turbo/BUILDING.md

위 문서의 내용은 사실은 안드로이드 공식 문서에 나와있는 Standalone 빌드 방식을 사용해서 빌드하는 방법이다.

그리고 라이브러리 빌드와는 별개의 문제이지만 디렉토리 구조 또한 빌드가 꼬이기 쉬운 구조이다.

Standalone 방식으로 빌드해서 헤더파일과 .a, .so 파일을 아래와 같은 Android.mk 설정으로 포함시키는 것이 가장 스트레스가 적다.

jni/
    Android.mk
    my_jni_code/
        Android.mk
        (project source code...)
    libraryA/
        Android.mk
        liba.h
        liba.a

my_jni_code/라는 디렉토리가 추가되었다. 프로젝트의 java 코드와 직접 연관있는 소스 코드는 jni/my_jni_code/로 옮겨둔다. 라이브러리 소스코드들이 프로젝트 코드의 하위 디렉토리에 있으면 Android.mk에서 또다시 하위 디렉토리를 include하도록 해야하는데, 이렇게 하면 빌드가 꼬이게 된다. NDK 빌드는 recursive하게 설계되지 않았다 (사실은 굳이 그렇게 할 필요가 없다). 따라서 각 모듈들이 동일한 디렉토리 레벨에 존재하도록 하고 jni/Android.mk는 아래 한 줄만 포함하도록 한다.

include $(call all-subdir-makefiles)

그러면 ndk 빌드 시스템이 자동으로 의존성을 파악하고 각각의 모듈을 빌드하게 된다.

jni/libraryA/Android.mk 파일은 prebuilt library 설정을 사용

LOCAL_PATH := $(call my-dir)
include ($CLEAR_VARS)

LOCAL_MODULE    := lib-a
LOCAL_SRC_FILES :=\
        liba.a \

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.

include $(PREBUILT_STATIC_LIBRARY)

jni/my_jni_code/Android.mk 에서는 다른 디렉토리 신경쓸 필요 없이 아래와 같이 모듈을 참조하는 한 줄만 추가해주면 된다.

(기타등등 기본적인 설정들은 생략...)
LOCAL_STATIC_LIBRARIES := lib-a

파일 이름(liba.a)이 아니라 LOCAL_MODULE로 지정한 lib-a를 써준다. ndk 빌드 시스템이 자동으로 의존성을 파악하고 lib-a 모듈을 찾아서 그 모듈의 Android.mk 파일에 설정된 값들(헤더파일 경로 등)을 적용해준다.

When writing C/C++ code for Android application, it’s very common to use other native libraries rather than writing everything from scratch. My first attempt was putting all library source directories under jni/ directory, and compiling them with a single make command with properly configured Android.mk files.

jni/
    Android.mk
    (project source files...)
    libraryA/
        Android.mk
        (library A source files...)
    libraryB/
        Android.mk
        (library B source files...)

With this structure, I wanted that jni/Android.mk would first visit its sub directories, then jni/libraryA/Android.mk and jni/libraryB/Android.mk would compile their source codes into a shared/static module file, and then finally jni/Android.mk compiles project’s source codes into a .so file for Android Application.

Long story short, this can’t be.

Usually, 3rd party source codes cannot be compiled unless it is configured, and Android.mk is not a good place to run configuration. Most C/C++ libraries are cross-platform and require tools such as autoconf, automake, and libtool to dynamicaly generate Makefile and config.h according to the system environment. This is usually done by running a simple command like this;

$ ./configure && make && make install

However, ndk-build is basically a cross-compile, and that means a system that builds the library is not the same as a system that runs the compiled codes of the library. Therefore, running configure and make command is meaningless.

But without configuration, there would be no Makefile and build cannot be done. Now what?

In this case, configuration should be done manually.

Fortunately, well-kown libraries are distributed with a document that contains information on how to build the library for android, and Google also provides a guide on configuring toolchain

For example, libjpeg-turbo gives a general recipe script for Android NDK in its BUILDING.md file.

Another problem with that structure is that build can easily get broken. Android.mk is not designed to work recursively. (Actually it doesn’t have to. It will be explained later)

New structure

My solution is to build each libraries into a .a or .so following their guide document, and move them into directories like this;

jni/
    Android.mk
    my_jni_code/
        Android.mk
        (project source code...)
    libraryA/
        Android.mk
        liba.h
        liba.a
    libraryB/
        Android.mk
        libb.h
        libb.so

Note that my_jni_code directory is added, which contains source files written for the project, and library A and B are now at the same level with project file directory.

In this version, jni/Android.mk contains only one line;

include $(call all-subdir-makefiles)

NDK build system will automatically detects all the dependencies and builds an NDK module.

And let’s assume that we want to link library A statically, and library B dynamically.

jni/libraryA/Android.mk file would look like this;

LOCAL_PATH := $(call my-dir)
include ($CLEAR_VARS)

LOCAL_MODULE    := library-a
LOCAL_SRC_FILES :=\
        liba.a \

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.

include $(PREBUILT_STATIC_LIBRARY)

liba.a has been built with its build script for Android and copied into that place. In other words, library-a is a prebuilt static library

And the following is what jni/libraryB/Android.mk would look like;

LOCAL_PATH := $(call my-dir)
include ($CLEAR_VARS)

LOCAL_MODULE    := library-b
LOCAL_SRC_FILES :=\
        libb.so \

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.

include $(PREBUILT_SHARED_LIBRARY)

Finally, to reference those libraries in Android NDK module, add these lines to jni/my_jni_code/Android.mk

LOCAL_STATIC_LIBRARIES := library-a
LOCAL_SHARED_LIBRARIES := library-b

Note that the module names that were assigned to LOCAL_MODULE in their Andorid.mk file are used as reference, not file names like liba.a, libb.so, libraryA/liba.a nor libraryB/library-b. As mentioned above, NDK build system automatically detects module library-a and library-b, so without having to know where those libraries are located, my_jni_code just can reference them by their module name. This is why Android.mk does not have to be written to work recursively.

Summary

When use 3rd party native library in Anroid NDK module, build them separately and use as prebuilt shared/static library. Don’t include library compilation into NDK module build.