레이블이 makefile인 게시물을 표시합니다. 모든 게시물 표시
레이블이 makefile인 게시물을 표시합니다. 모든 게시물 표시

2011년 12월 26일 월요일

향상된 makefile

출처 : http://www.hanb.co.kr/network/view.html?bi_id=351

저자: 제니퍼 베스퍼만, 역 이호재 

이번 기사에서는 꽤 복잡한 makefile을 분석할 것입니다. 이 makefile은 실제 프로젝트에서 사용된 것입니다. 프로젝트에서만 적용된 일반적이지 않은 내용은 생략하고 전반적인 makefile 분석에 대해 다루어 보고자 합니다. 

이 기사는 「make 소개」와 관련된 연작 기사이며 거기서 다룬 주제를 여기서도 다룹니다. makefile에 대한 소개와 간단한 makefile 작성 및 사용법을 알고 싶다면 「make 소개」부터 읽어보시기 바랍니다. 

「make 소개」와 이번 기사 모두 개발자를 위한 make 툴에 대해 다루고 있습니다. 시스템 관리자에게 있어서도 make는 역시 유용한 도구가 될 수 있습니다. makefile에 대해서 살펴보는 동안, 설정 스크립트, 설치 스크립트, 자동 업데이트 도구를 재생성하는데 있어 이러한 기술들을 적용할 수 있는 방법에 대해 생각해 봅시다. 

자신의 파일을 사용하도록 허락해준 미치 데이비스(Mitch Davis)에게 이 자리를 빌어 감사의 말을 전합니다. 수정된 makefile은 필자가 몇몇 라인을 제거 하였기 때문에 정상적으로 동작하지는 않지만, 다양한 테크닉을 알아보기에는 충분할 것입니다. 물론, makefile은 원래부터 많은 사람들이 사용하고 있습니다. 

머릿말 주석
# TODO: Add a section for installation

# Makefile for (the project)
# Version 3.0
# Jennifer Vesperman
# 14 August 2001
#
# Version 4.0
# Mitch Davis
# 10 January 2002
makefile에 헤더를 포함하고 향상시키고자 하는 것을 기록해 놓으면 정말 유용하게 사용할 수 있습니다. 필자의 makefile을 통해 우리가 하고자 하는 일이 무엇인지 알 수 있습니다 . 

위치 정의 


# Set up the compiler directories
xmlc_j:=            /usr/local/lib/xmlc2.0.1/xmlc.jar
install_d:=         /usr/local/apps/jakarta-tomcat-3.2.2/webapps/example
servlet_d:=         $(tomcathome)/lib
class_d:=           $(install_d)/WEB-INF/classes
class_example_d=    $(class_d)/example
# (additional directory definitions snipped.)
컴파일에 필요한 경로는 변수로 설정할 수 있습니다. 이를 통해 설치가 변경되더라도 쉽게 수정할 수 있습니다. 그리고 다른 종류의 컴퓨터에서 설정을 자동으로 변경하기 위해 조건문과 셸 함수를 사용할 수 있습니다. 조건문과 셸 함수는 뒤에서 자세히 다루겠습니다. 


컴퓨터에 종속적인 변수를 설정하기 위해 조건문 사용하기
ifeq ($(shell uname -n),install)
  # For compiling on install machine
  install_d:= /usr/local/jakarta-tomcat-3.2.2/webapps/example
endif
재귀적으로 확장되는 변수 


변수에는 두 가지 종류가 있습니다. 재귀적으로 확장되는 변수 (Recursively expanded variable)와 간단히 확장되는 변수(Simply expanded variable)가 그것입니다. 재귀적으로 확장되는 변수는 이름과 값 사이에 "="를 사용하고 변수가 필요할 때 그 값이 결정됩니다. 이 변수들은 재귀적으로 정의 될 수 없습니다. 왜냐하면 무한 루프에 빠지게 되기 때문입니다. (한번 테스트 해보시고 다음부터는 이런 방법을 사용하지 마십시오.) 함수는 make가 변수 값을 필요로 할 때마다 수행될 것입니다.
  • OK: class_example_d = $(class_d)/example
    (정의문에서 다른 변수를 참조한 경우)
  • Not OK: class_d = $(class_d)/example
    (재귀적 정의: 정의문에서 자신을 참조한 경우)
간단히 확장되는 변수 

간단히 확장되는 변수는 이름과 값 사이에 ":="를 사용하고 make가 이 변수를 만날 때 바로 값이 평가됩니다. 재귀적 정의도 가능하며, 이때 오른쪽에 있는 변수 값은 이 변수의 이전 값이 됩니다. 간단히 확장되는 변수는 GNU make의 고유한 기능이며 다른 버전의 make에는 존재하지 않을 수도 있습니다.
  • OK: class_example_d := $(class_d)/example
  • OK: class := $(class)/example
    (이전에 class가 정의되어 있다면 OK)
환경 변수
# These exports are for xmlc, which is 
# a java program which calls javac.

# javac를 호출하는 xmlc라는 프로그램을 위한 설정

export JAVAC:=  $(java_h)/bin/javac
export JAVA:=   $(java_h)/bin/java
때때로 makefile 내에서 환경 변수를 설정해 주어야 합니다. 이것은 보통 특별 버전의 컴파일러를 사용해야만 하는 프로젝트가 있을 경우에만 그렇습니다. 이때에는 변수 값을 신중히 선택하십시오. EDITOR 변수를 바꾸어서 다른 개발 팀원들을 당혹스럽게 만들 수 있기 때문입니다. 


Make 함수
# Set up the classpaths for Javac

# Javac를 위한 classpaths 설정

classpath:= \
        $(cookie_d)/cookiebuster.jar \
        $(example_j) \
        $(hsqldb_d)/hsqldb.jar \
        $(xmlc_j) \
        $(xml_j) \
        $(jetty_d)/com.mortbay.jetty.jar

# Convert the spaces to colons.  This trick is from 
# the make info file.

# 빈칸을 콜론으로 바꿉니다. 이 트릭은 make info 파일에 있습니다.

empty:=
space:= $(empty) $(empty)
classpath:=     $(subst $(space),:,$(classpath))
classpath 변수는 표준이고 간단히 확장되는 변수입니다. 이 변수의 정의는 여러 라인에 걸쳐 있습니다. 여기서 미치(Mitch)는 path를 구분하는 빈칸을 콜론으로 바꾸기 위해 make 함수를 이용했습니다. 그는 이렇게 정의하는 것이 한 라인에서 콜론으로 구분지어 정의하는 것보다 훨씬 더 알아보기에 좋다고 생각합니다. 물론 classpath 내에 빈공간이 있다고 하더라도 이를 사용해서는 안됩니다.
러닝 리눅스 3판
참고 도서
make에는 유용한 내장 함수들이 많이 있습니다. 함수를 부르는 문법은 $(함수 인자) 입니다. 함수는 함수를 둘러싸고 있는 문장들이 평가될 때 같이 평가됩니다. 간단히 확장되는 변수 안에 있는 함수는 make가 처음으로 그 변수를 만날 때 평가됩니다. 또한 재귀적으로 확장되는 변수 안에 있는 함수는 make가 그 변수의 값을 필요로 할 때 평가됩니다. 그리고 규칙에 있는 함수는 make가 그 부분의 규칙을 실행할 때 평가됩니다. 


조건문
# If there's already a CLASSPATH, put it on the front

# 만약 CLASSPATH가 이미 존재한다면 가장 앞쪽에 붙힙니다.

ifneq ($(CLASSPATH),)
        classpath:=     $(CLASSPATH):$(classpath)
endif

# Re-export the CLASSPATH.

# CLASSPATH를 다시 export합니다.

export CLASSPATH:=$(classpath)
Make에는 조건문이 있으며 종종 변수를 설정하는데 쓰입니다. 사용 할 수 있는 조건문으로는 ifeqifneqifdefifndef가 있습니다. else 절은 옵션이고 인자는 괄호([ ])나 작은 따옴표 또는 큰 따옴표 사이에 올 수 있습니다. 


예제에서는 classpath 환경 변수가 정의 되어 있는지 알아보기 위해 ifneq 보다는 ifdef를 사용하는 것이 더 좋습니다. 

이 makefile에서 사용된 문법은 아래와 같습니다.
ifeq (arg1, arg2) 
        statement/s
else
        statement/s
endif
셸 함수와 기타 make 함수 


Make는 자바 컴파일에 있어서는 이상적인 도구가 아닙니다. 자바 컴파일러는 의존성 분석을 좋아하는데, 이는 make가 오작동을 일으키게 할 수 있습니다. 의존성을 분석할 때, 자바는 소스 파일의 정보로부터 작업을 하는 반면 make는 타겟의 정보로부터 작업을 합니다. 자바 구조상 새로운 소스 파일이 우연히 생길 수 있지만 make는 새로운 소스 파일을 수동으로만 추가할 수 있습니다. 

각각의 새로운 파일이 make 스크립트에 추가될 때 변수 선언으로 추가되는 것이 가장 이상적입니다. 그렇지만 이 프로젝트는 실무에서 행해진 것이기 때문에 make는 소스 디렉토리를 검색해서 타겟 리스트를 자동으로 생성하도록 되어 있습니다. 자바의 특징과 이 프로젝트의 특성상 이는 네 개의 변수와 셸 함수와 make 함를 필요로 합니다. 

이 makefile의 이전 버전에서는 외부 셸 스크립트를 가지고 타겟을 생성했고, 리스트를 make 변수처럼 파일로 임포트했습니다. 그리고 난 후 make 변수를 makefile로 임포트했습니다. 미치는 필자의 makefile을 개선하였기 때문에 타겟은 훨씬 더 깔끔한 방법으로 생성됩니다.
# 소스를 담고 있는 디렉토리를 검색하고
# 생성된 .class 파일에 맞는 이름을 생성합니다.
# example/util/*에 있는 클래스들이 먼저 컴파일되게 합니다.
CLASSFILES_view:=
  $(patsubst %.java,$(class_d)/%.class,
  $(shell find example/util -name '*.java'))

# 유틸이나 뷰가 아닌 소스를 찾습니다.
# grep -v 대신 -apth -prune을 사용하십시요.
# find(1) man page의 예제를 참조하시기 바랍니다.
CLASSFILES_nonview:=    
  $(patsubst %.java,$(class_d)/%.class,
  $(shell find example -path example/util -prune -o 
  -path example/views -prune -o -name '*.java' -a -print))

# xmlc에 의해 .html 파일로부터 생성된 class를 찾습니다.
# 단, mockup 디렉토리에 있는 것은 제외합니다.
CLASSFILES:=    
  $(CLASSFILES_view) $(CLASSFILES_nonview)
VIEWFILES:=     
  $(patsubst %.html,$(class_d)/%.class,$(shell find 
  example -path example/views/mockups -prune -o -name 
  '*.html' -a -print))
이 makefile은 표준 make 함수뿐만 아니라 셸 함수도 포함하고 있습니다. 셸 함수 문법은 $(쉘 명령어)입니다. 이는 셸 함수 결과를 리턴합니다. 이때 엔터는 제거됩니다. 


patsubst 함수는 $(patsubst 패턴, 변경하고자 하는 값, 텍스트)의 문법을 가지고 있습니다. 이 함수는 패턴 룰이 하는 것처럼 % 기호를 사용합니다. % 기호는 패턴과 변경하고자 하는 텍스트에서 일치하는 문자열을 가리킵니다(† 역자 주: %.html을 %.class로 바꾼다는 것은 aa.html일 경우 aa.class로 바꾼다는 의미). 

명령어로서 Makefile 변수와 자동 변수
# .html과 .java 파일을 어떻게 컴파일 할 것인가
htmlcompile=$(XMLC) -d $(class_d) -class $(subst /,.,
  $(basename $<)) $<
javacompile=javac -sourcepath . -d $(class_d) 
  $(filter %.java,$?)
이는 재귀적으로 확장되는 변수들로 변수가 실질적으로 필요할 때 계산됩니다 . 여기서 변수들은 재귀적으로 확장되어야 하는데 이는 변수들이 전제조건 리스트에 의존적인 자동 변수들을 포함하고 있고 룰에서 명령어 문자열로 사용될 것이기 때문입니다.(자동 변수에 대해서는 「make 소개」에 잘 설명되어 있다.) 


$<와 $?는 자동 변수입니다. $<는 첫 전제조건의 이름으로 확장됩니다. $?는 타겟보다 더 새로운 전제조건을 빈칸으로 구분한 리스트로 확장됩니다. 

$(filter %.java,$?) 는 필수적입니다. 왜냐하면 javacompile이 룰에서 사용될 때, 룰의 전제조건 중 하나는 자바 파일이 컴파일되어 들어갈 디렉토리이기 때문입니다. 필터는 디렉토리를 제거하고 .java파일만을 남겨 놓을 것입니다. 

변수 선언에서 make 함수의 사용을 주위해서 살펴보시기 바랍니다. 

패턴 룰
$(VIEWFILES): $(class_example_view_d)/%.class: 
  example/views/%.html $(class_d)
        $(htmlcompile)

$(CLASSFILES): $(class_example_d)/%.class: 
  example/%.java $(class_d)
        $(javacompile)
이 룰의 첫번째 라인은 세 부분으로 구성되어 있습니다. 이는 정적 패턴 룰(static pattern rules)이라 불리는 GNU make의 특별한 룰입니다. 「make 소개」를 보시면 패턴 룰에 대한 소개가 간단하게 되어 있습니다. 정적 패턴 룰의 문법은 다음과 같습니다.
targets: target-pattern: dependency-patterns commands
$(class_example_view_d) 디렉토리에 있는 .class 파일은 example/views/%.html 디렉토리에 있는 .class와 상응하는 .html 파일로부터 생성할 수 있습니다. 하지만 이는 .class 파일이 $(VIEWFILES) 리스트의 일부분일 때만 가능합니다. 파일 타입이 .A인 파일을 .B로 변경하는 방법에는 여러 가지가 있는데 그 선택은 변경하는 파일에 따라 의존적이라면 정적 패턴 룰을 사용하기 바랍니다. 


패턴 룰에서 중요한 부분은 바로 %입니다. 이는 타겟과 필요조건 파일의 같은 부분을 가리킵니다. 

간단한 룰
# Make가 알아야 할 것들
.SUFFIXES : .html .java .class
.PHONY : clean all show_classpath

# 중요 타겟 : make all
all: $(VIEWFILES) $(CLASSFILES)

$(class_d):
        mkdir $@

show_classpath:
        @echo Here is the CLASSPATH passed to javac:
        @echo $$CLASSPATH
# $$ 표기법에 주목

clean:
        -rm -rf $(class_d)
all 타겟은 패턴 룰에 의존적입니다. 다른 룰은 아래와 같은 형식으로 되어 있는 간단한 룰입니다.
target: prerequisites
        command
간단한 룰과 phony 룰은 이미 「make 소개」에서 설명했습니다. 


Caveats과 gotchas
  • 이번 기사에서 소개된 테크닉은 GNU make를 위한 것입니다. 다른 유닉스 시스템의 make 프로그램은 여기서 소개된 모든 기능을 제공하지 않을 수도 있습니다.
  • make 룰은 각 명령어의 처음에 탭을 주어야 합니다. 빈칸은 동작하지 않습니다.
  • make는 타겟으로부터 전제조건으로 거꾸로 동작합니다.
  • 컴파일러가 의존성을 검사하지 않는 언어에서 make는 더 훌륭히 동작합니다.
마지막 한마디 

지금까지 make와 관련된 고급 기술들을 살펴보았습니다. makefiles을 직접 작성할 때에는 간단한 것에서부터 시작하여 한번에 하나씩 새로운 기능을 추가해보기 바랍니다. 

하나의 파일이 다른 파일로부터 생성될 필요가 있는 곳이라면 어디에서든지 make는 사용할 수 있습니다. 소프트웨어 개발 뿐만 아니라 설정 스크립트와 업데이트 스크립트에도 시험삼아 make를 사용해보기 바랍니다. 

관련 링크 

좀 더 자세한 정보를 원한다면 다음을 참조하기 바랍니다.
제니퍼 베스퍼만(Jennifer Vesperman)은 생각하기 좋아하는 몽상가로 부모들은 인정하기 힘들었지만 척추에 실리콘을 붙인채 태어나야 했다. 그녀는 유저이자 옹호자로서 오픈 소스에 기여하고 있다. 현재는 Linuxchix.org의 책임자로 일하고 있다.

Makefile - HYYOO

출처 : http://hyyoo.egloos.com/327319

1. 자동 매크로 리스트
$?  현재 타켓보다 최근에 변경된 종속 항목 리스트(확장자 규칙에서 사용 불가)
$^ 현재 타겟의 종속 항목 리스트(확장자 규칙에서 사용 불가)
$@ 현재타겟의 이름
$< 현재 타겟보다 최근에 변경된 종소 항목 리스트(확장자 규칙에서만 사용 가능)
$& 현재 타겟보다 최근에 변경된 종속항목의 이름(확장자 제외) (확장자 규칙에서만 사용 가능)
$% 현재의 타킷이 라이브러리 모듈일 때 .o 파일에 대응되는 이름

gcc -o $@ $^

2. 패스를 가지는 타겟의 정의 항목

ex) /temp/target.o : /usr/local/c/target.c

 $(@F) : target.o 의미 $(<F): target.c 의미
 $(@D): /temp 의미 $(<D): /usr/local/c 의미

3. 확장자 규칙
 .SUFFIXES : .o .c
%.o : %.c
                $(CC) -DDEBUG -c -o $@ $<  

4. wildcard와 대입참조 기법
 SRCS = $(wildcard *.c)  .c를 확장자로 가지는 모든 파일 xxxx.c
 OBJECTS = $(SRC:.c=.o)   대입 참조 기법 xxxx.c를 xxxx.o로 대입 참조

5. 문자열 처리 patsubst
 OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
 세번째 인자로 오는  xxxx.c 파일들을 공백으로 구분된 문자열들 각각에 대하여 첫번째 인자인 %.c와 매칭되는 것을 %.o로 바꾼다.

 6. 더미 타겟
 주로 타겟은 생성될 파일인데, 더미 타겟의 경우 파일이 생성되지 않는 개념적인 타겟이므로 더미 타겟 or Phony Target이라 부른다.
 clean :
             rm -rf *.o xxx


 7. 함수의 사용

 기본: $(함수명 함수 인자들)

셀 명령 함수 $(shell 셸 명령어) - 셸 함수는 ㅁㅇ령 수행 결과를 매크로를 정의하고자 할 때 사용할 수 있는 유일한 방법이다. 

문자열 처리 함수 

$(subst 찾을 문자열, 변경할 문자열, 목표 문자열)
ex)  STR = $(subst like, LIKE, I like you)
       echo:
                @echo $(STR)
 ---------------------------------------
  I LIKE you

$(patsubst 패턴, 변경 문자열, 목표 문자여) :  패턴을 발견하면 변경 문자열로 변경한다. 패턴에는 % 기호가 사용될 수 있는데 % 기호는 공백과 탭을 제외한 모든 문자열을 의미한다.
ex) STR = $(patsubst %.c, %.o, memo.c main.c ABCD)
      echo :
                @echo $(STR)
------------------------------------------
memo.o main.o ABCD

매크로 이용 $(매크로명: 패턴=치환할 문자열) patsubst 함수와 동일한 효과
MACRO - memo.c main.c ABCD
STR = $(MACRO:%.c=%.o)

$(sort 문자열) 문자를 정렬하는 함수
ex) MACRO = bbb aaa ccc aaa ddd
      SR = $(sort $(MACRO))
      echo:
                @echo $(STR)
----------------------------------------
aaa bbb ccc ddd

$(strip 문자열)  공백 문자 제거
ex) CC = gcc [space Bar] [space Bar] [space Bar][Enter]
   @echo  $(strip $(CC)) 
----------------------------------------
gcc 

$(filter 패턴, 문자열) : 패턴과 일치하는 문자열만 걸러준다. 
FILES = memo.c head.h main.c asm.S diary.h
SRCS = $(filter %.c %.S $(FILES))
HEADS = $(filter %.h, $(FILES))

$(filter-out  패턴, 문자열) 패턴과 일치하지 않는 문자열만 걸러준다. 
$(findstring 찾을 문자열, 대상 문자열)
$(dir 문자열) 디렉토리만 추출
$(notdir 문자열) 디렉토리가 아닌 부분 추출
$(suffix 문자열) 확장자만 추출
$(basenmae 문자열) 점과 확장자를 제외한 부분을 추출
$(addsuffix 접미사, 문자열) 문자열의 각 단어 뒤에 접미사를 붙인다.
$(addprefix 접두사, 문자열) 문자열의 각각에 접두어를 붙인다. 

기타 유용한 함수
$(wildcard 패턴) 현재 디렉토리에서 패턴과 일치하는 파일 리스트를 뽑느다. 
ex) $(wildcard *.c) 사용되고 있는 현재 디렉토리에 있는 모든 .c 파일의 리스트를 리턴ㄷ

$(foreach 변수명, 대입 문자열, 확장 문자열)
ex) SRCS=$(foreach str, a b c, $(str).c)
변수명: str, 대입 문자열: a,b,c 확장문자열 $(str).c
변수명에 대입 문자열을 단어별로 대입하여 넣고 그 변수를 확장 문자열에서 사용
a.c b.c c.c 출력
사용 예)
SRCS = $(foreach dir, . memo calendar, $(wildcard $(dir)/*.c))
1) dir = .                     ./ 내의 dir
2) dir = memo
3) dir = calendar

함수 사용시 주의사항
콤마, 공백은 매크로로 사용할 것 comma =, space = $(null) $(null)
함수는 함수르 지원하는 make에서만 사용해야한다.

특수타겟
.DEFAULT  make가 요청된 타겟을 빌드할 룰을 기술 파일 또는 확장자 규칙에서 찾지 못하면 .DEFAULT 타겟에 기술된 명령어를 수행
.IGNORE    명령어 수행시 반환되는 오류 코드르 무시한다. make 명령시 i 옵션을 준 것과 동일하고 기술 파일 내에 모든 명령어 앞에 '-'
                 기호를 붙인것과 동일하다.
.PRECIOUS  .PRECIOUS 타겟에 기술된 파일들은 빌드 중단 신호 또는 명령 행에서 오류가 반환되더라도 지우지 않는다.
.SILENT     명령은 실행하지만 실행되는 명령을 화면에 출력하지 않는다. -s 옵션과 동일하고 모든 명령어 앞에 '@'를 붙인것과 동일하다.
.SUFFIXES   .SUFIXES 타겟에 정의된 확장자들은 중요 확장자들로써 확장자 규칙과 연관될 수 있다.
.EXPORT_ALL_VARIABLES 재귀적 make 사용법에 있어서 export 키워드를 단독으로 기술 파일에서 사용한 것과 마찬가지로 기술 파일내의 모든 매크로들을 하위 기술 파일에 전달한다. 

중소규모 프로젝트를 위한 Makefile 만들기

출처 : http://dooeui.blogspot.com/2009/12/makefile.html

대규모 프로젝트에서는 재귀적 Makefile을 사용함으로써 다양한 디렉토리 구조에 유연하게 대처할 수 있다. 하지만 중소규모의 프로젝트에서는 매 디렉토리마다 Makefile을 넣는것은 좀 번거로운 일이 아닐수 없다. 이에 아래와 같이 중소규모 프로젝트에 적용가능한 디렉토리 구조 및 Makefile을 만들어 보았다. Makefile은 ./와 ./obj 두군데만 있어 관리하기 용이하도록 하였으며, ./Makefile이 ./obj/Makefile을 호출하는 구조이다.

먼저 디렉토리 구조는 아래와 같다

./
 |---main.c
 |--Makefile

./include
 |--- func1.h
 |--- func1.h

./srcdir1
 |--- func1.c

./srcdir2
 |--- func2.c

./obj
 |--- *.obj files
 |--- Makefile
 |--- depend file

아래와 같이 동작되도록 Makefile을 작성할 것이다.
 - 실행 파일은 ./ 디렉토리에 생성되도록 한다.
 - object파일은 모두 ./obj 디렉토리에 생성되고 make clean시 모두 지워지도록 한다.
 - make depend로 dependency file 설정하여 수정된 파일만 컴파일 되도록 한다.

./Makefile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1 OBJDIR = ./obj
2
3 all:
4   cd $(OBJDIR) && make
5
6 clean:
7   cd $(OBJDIR) && make clean
8
9 depend:
10   cd $(OBJDIR) && make depend

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

./obj/Makefile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1 CC = gcc
2 LD = gcc
3
4 INCDIR = ../include
5 SRCDIR1 = ../srcdir1
6 SRCDIR2 = ../srcdir2
7
8 VPATH = $(SRCDIR1) $(SRCDIR2)
9
10 CFLAGS = -O2 -I$(INCDIR)
11
12 TARGET = testapp
13
14 SRCS = $(foreach dir, . $(SRCDIR1) $(SRCDIR2), $(wildcard $(dir)/*.c))
15 SRCS := $(notdir $(SRCS))
16
17 OBJS = $(SRCS:.c=.o)
18
19 all: $(TARGET)
20
21 $(TARGET) : $(OBJS)
22   $(LD) $^ -o$(TARGET) $(LIBS)
23   mv $(TARGET) ../
24
25
26 %o:%c
27   $(CC) $(CFLAGS) -c $< -o $@
28
29 clean:
30   -rm -rf $(OBJS)
31   -rm -f ../$(TARGET)
32
33 depend: $(SRCS)
34   $(CC) -M $(CFLAGS) $^ > $@
35
36 -include depend

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Line:14  모든 디렉토리에서 c파일을 골라낸다.
Line:15  c파일 리스트에서 경로명을 제거한다.
Line:17  각 c파일에 해당하는 o파일 리스트를 만든다.
Line:22  $^ 는 현재 타겟의 종속 항목 리스트 전체로 치환된다.  
Line:26  $< 는 확장자 규칙에서만 사용되며 사용되며 현재 타겟보다 최근에 변경된 종속 항목 중 하나로 대체된다. 아래에서 makefile 내부 매크로 관련 정보를 더 찾아볼 수 있다.    http://haneul0318.springnote.com/pages/14554?print=1

Line:34  모든 파일의 의존성리스트를 만든다.
Line:36 depend파일을 include하도록 하였으므로 이 Makefile이 불릴때마다 depend target에 자동으로 실행된다. 이때 obj디렉토리에 depend 파일이 만들어지며, 이 파일은 Makefile의 일부로 간주되게 된다. 이 파일로 인하여, 최근에 수정된 c파일이 자동으로 새로 컴파일 되며, h파일의 경우 해당 h파일을 사용하는 모든 c파일이 다시 컴파일 되게 된다. 하지만 변경되지 않은 파일들은 새로 컴파일 되지 않아 매번 모든 파일을 새로 컴파일하는 수고를 덜 수 있다. 명령문 앞에 "-"를 붙이면 오류발생시 무시하고 나머지 명령을 계속 진행한다. "-"가 없을 경우에는 오류 발생시 make가 중단된다.

아래는 위 메이크 파일 실행시 결과이다.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

dooeui@dooeui-laptop:~/test$ make
cd ./obj && make
make[1]: Entering directory `/home/dooeui/test/obj'
gcc -O2 -I../include -c ../srcdir1/file1.c -o file1.o
gcc -O2 -I../include -c ../srcdir1/main.c -o main.o
gcc -O2 -I../include -c ../srcdir2/file2.c -o file2.o
gcc file1.o main.o file2.o -otestapp
mv testapp ../
make[1]: Leaving directory `/home/dooeui/test/obj'

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Makefile관련 추가 정보는 아래에서 찾을 수 있다.

  http://wiki.kldp.org/KoreanDoc/html/GNU-Make/GNU-Make.html#toc1

도서 중에서 make에 관련 사용법을 속시원히 설명한 책은 "유닉스-리눅스 프로그래밍 필수 유틸리티"- 한빛 미디어를 추천한다.

2011년 12월 21일 수요일

Building Open Source libraries with Android NDK


출처 : http://warpedtimes.wordpress.com/2010/02/03/building-open-source-libraries-with-android-ndk/


Building Open Source libraries with Android NDK

Filed under: Android,Technology — divkis01 @ 12:50 am
Tags: 
Having scrambled through a NDK documentation and a lot of hit and trials and experimentation, finally I could figure out how could one build (even though partially) Open Source libraries with Android NDK.

Some background on how Autotools work:

The way autootool and friends work is this:
You write a configure.ac file, which is read in by Autoreconf and generates a configure script. Configure script test the build system for headers, libraries etc. and generates a config.h which contains definitions like #define HAVE_XXX 1 and also generates Makefiles after reading in the Makefile.in files which in turn is generated using Automake by reading in Makefile.am.
configure.ac —> input to –> autoreconf –> generates -> configure script –> checks host system for headers, libraries etc. -> generates -> config.h
Also
Makefile.am –> input to automake –> generates –> Makefile.in –> input to –> configure script –> generates Makefile
The code uses the generated config.h in the following fashion:
#ifdef HAVE_XXXX
#include <some-header>
#endif
….
….
#ifdef HAVE_XXXX
Call some function which is declared in the header
#else
Provide some other mechanism or report error to user or do whatever you want
#endif

The problem?

Most Open Source libraries use GNU autotools and its friends for building.
The first problem is that the Autools generate some configuration headers based on build time probe of the system. By build time probe I mean checking for things like if a header, library or a tool is present or not. In cross compiling scenario some of these probe should be done on the target system and not on the build system.
Second, the build system for most cross compiler tools have their own quirks which need passing some extra flags.
Third, in case of Android, it provides its own build system which are essentially some Makefile definitions and rules. This is provided so that people can build their code easily without having to deal with usual cross compiling issues. Thus there is a gap that while your autotools would generate the Makefiles while Android build system requires its own styled Makefiles in the form of Android.mk. One cannot simply generate Android.mk files using autotools.
Fourth, even if one gets to write Android specific Makefiles, the build would most probably fail as during the build it would look for a file config.h included in the fashion shown below, while no such file would exist as it is generated by the configure script.
#ifdef HAVE_CONFIG_H
#include “config.h”
#endif
Simply copying a config.h file from a run of configure script on another build system wouldn’t really work as the header files and other libraries present on Android may not match with the header and libraries present on the build system. Thus config.h would probably differ if it is somehow generated for Android with the one generated on a build system.
So how does one build an open source library for Android?

Solution:

The way I have managed to work around this trouble is to run the configure script with right cross compilation variables so that a config.h matching my Android system gets built and then writing Android.mk files which would simply use the Android build system.
The way I figured out the right flags was by building the Android source tree which displayed what flags are being used for building and then taking the cues from there, I passed the right flags to the configure script.
It looks something like this for building Android-3 target API on a linux host:
export ANDROID_ROOT=/home/divkis01/mydroid
The command above is sets the path where the Android sources are checked out from git respository.
NOTE: It is not necessary to check out the ANDROID sources and you can replace the ANDROID_ROOT with NDK_ROOT in all the commands below, along with proper path to the NDK cross compiler.
export PATH=$PATH:$ANDROID_ROOT/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/
The command above is necessary so that configure can find out the path to the cross compiler which is needed to build some test programs during the configure run process. Please note that you can also set the path to the NDK compiler root
./configure –host=arm-eabi CC=arm-eabi-gcc CPPFLAGS=”-I$ANDROID_ROOT/build/platforms/android-3/arch-arm/usr/include/” CFLAGS=”-nostdlib” LDFLAGS=”-Wl,-rpath-link=$ANDROID_ROOT/build/platforms/android-3/arch-arm/usr/lib/ -L$ANDROID_ROOT/build/platforms/android-3/arch-arm/usr/lib/” LIBS=”-lc “
The command above has several points that should be well understood.
–host=arm-eabi –> This tells the configure script if the cross compilation is being done or not. It is also used as a prefix to some of the cross compiler tools like strip, nm etc.
CC=arm-eabi-gcc –> This tells the compiler that should be used for building
CPPFLAGS –> This tells the location where the header files should be searched which were specified in configure.ac with AC_CHECK_HEADER macro
CFLAGS=”-nostdlib” passes the option to build some test programs during configure process run. If you don’t pass this the compiler would link the standard C library of the host system which wouldn’t be compatible with the C library of the target system. You will end up getting error something like this, if you don’t pass this option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: crt0.o: No such file: No such file or directory
LIBS=”-lc” –> This option tells that it should explicitly link to a library called libc.so which is present in the location specified using the -L in the LDFLAGS option. If you are wondering that usually to build a C executable one doesn’t need to provide -lc as libc is automatically linked, then why do we need to specify this here? The answer lies in -nostdlib flag, which instructs not to link with the standard C library on the build system.
You will end up getting error something like this, if you don’t pass this option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: crt0.o: No such file: No such file or directory
collect2: ld returned 1 exit status
LDFLAGS = –> This option is also passed to build some test programs during configure process run.If you don’t pass the -Wl,-rpath-link option, then linker does not know where do the libraries dependent on the library specific using LIBS reside. If you don’t pass the -L option then the linker doesn’t know where do the libraries specified in LIBS reside.
You will end up getting error something like this, if you don’t pass the -Wl,-rpath-link option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: warning: libdl.so, needed by /home/divkis01/mydroid/development/ndk/build/platforms/android-3/arch-arm/usr/lib//libc.so, not found (try using -rpath or -rpath-link)
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 00008184
/home/divkis01/mydroid/development/ndk/build/platforms/android-3/arch-arm/usr/lib//libc.so: undefined reference to `dl_unwind_find_exidx’
You will end up getting error something like this, if you don’t pass the -L option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-
eabi/bin/ld: cannot find -lc
collect2: ld returned 1 exit status
Once you run the configure script with these flags and options, it will generate the appropriate config.h which is compatible / in sync with your target system. Now you can go ahead and start writing the Android.mk files to build your sources.

Other troubles:

This solves only part of the problem as Autotools not only help in building but also in installing. Given it doesn’t make sense to install the build for a target system on host except for the headers. This can be done by augmenting the Android.mk files or writing some shell scripts to do this manually.

Conclusion:

Autotool is good only on GNU systems and using it for cross compiling can be really tedious, confusing, error prone or even impossible. The method described here is a hack and should be used at your own risk.
Let me know if this post was helpful for you.