숨겨진 1인치의 의존성을 찾아서 - make 본문

Programming

숨겨진 1인치의 의존성을 찾아서 - make

halatha 2009. 9. 21. 17:39
출처: 열씨미와 게을러의 리눅스 개발 노하우 탐험기
2009/08/17 - [Programming] - diff, find, md5sum, patch
2009/08/17 - [Programming] - find, grep, ctags, cscope, global
2009/08/17 - [Programming] - shared library, strace, ldconfig, ldd, gcc, strings, od, nm, c++filt, readelf
2009/09/14 - [Programming] - configure
2009/09/21 - [Programming] - 자동화된 빌드 시스템 구축
2009/09/21 - [Programming] - 자동화된 빌드 시스템 구축 (2)

Makefile.1
make -f Makefile.1
1. foobar 생성 시각 check -> foobar가 없거나 foobar 생성 시각이 foobar.o나 common.o보다 이르면(즉, foobar.o와 common.o object file compile이 foobar execution file 생성보다 나중에 진행되었다면) foobar.o, common.o object file을 이용해 foobar execution file 생성
2. 재귀적으로 foobar.o와 common.o object file에 대한 check 진행
불필요한 file은 재 compile 하지 않는다

Makefile.2 - 확장자 규칙 사용
$(CC) - make가 알고있는 C compiler로 치환
$< - 의존 목록에 들어있는 선행 file을 구성하는 개별 원시 파일 name

Makefile.3 - 패턴 규칙 사용
$@ - compile 결과 만들어질 object file name
$(CFLAGS) - C compiler에 넘길 flag set

의존성 check
gcc -M option: 의존성 check
Makefile 뒷부분에 자동으로 의존성 file을 덧붙여 줄 수도 있고, macro 선언이 필요한 경우 해당 선언을 넣을 수도 있다(위의 예는 HACK macro 선언을 넣은 것)
다음과 같이 Makefile에 포함시킬 수도 있다
$^ - 선행 file을 구성하는 모든 원시 file name
-include 앞의 '-' - depend file이 없더라도 오류를 발생시키지 말라는 뜻

makedepend
makedepend [option] [source code list]
-D(macro definition), -I(directory path), -f(Makefile name), --(직후 option 무시)
주로 Makefile에서 'make depend'를 통해 의존성 검사를 수행하도록 사용
make depend command: "# DO NOT DELETE"라는 문자열을 Makefile 가장 뒷부분에 넣고 그 다음부터 gcc -M option으로 분석한 정보와 유사한 파일 의존성 정보를 기록
만일 이미 # DO NOT DELETE 문자열이 있으면 기존 파일 의존성 정보를 모두 제거하고 새로 기로가므로 여러 번 실행해도 괜찮다
build 과정에서 매번 내릴 필요는 없으며, file이 추가되거 의존성 관계가 바뀐 경우에만 수행하면 된다

ccache
내용 기반으로 build를 하는 방법
1. gcc의 pre compile header: gcc 3.4 이후 version에서는 Visual C와 유사한 pre compile header 기능이 있어, .gch로 pre compiled version을 저장하고 compile시 이것을 사용해 속도를 향상시킴
2. ccache
C/C++ compiler cache로 gcc -E option을 이용해 선행 처리가 끝난 text 형식의 C file hash value를 구하고, 다음 번 compile시 hash value가 다른 file만 compile해 compile 속도를 5~10배 향상시킴
make clean;make를 반복 수행할 때(즉 clean build를 자주할 때) 특히 효과가 좋음
초기 compile 시 hash value를 구하기 위한 시간만 조금 더 필요

ex)
$ tar xvpfz ccache-2.4.tar.gz
$ cd ccache-2.4
$ ./configure
$ make
$ cp ccache /usr/local/bin
$ ln -s /usr/local/bin/ccache /usr/local/bin/gcc
$ ln -s /usr/local/bin/ccache /usr/local/bin/g++
$ ln -s /usr/local/bin/ccache /usr/local/bin/cc
$ export PATH=/usr/local/bin:`echo $PATH`
$ which gcc
/usr/local/bin

항상 ccache를 symbolic link 시켜 놓은 directory path가 일반 gcc path보다 앞에 와야 한다
ccache의 결과는 기본적으로 $HOME/.ccache에 저장. 다른 곳에 저장하기 원하면 CCACHE_DIR 환경 변수에 설정

Makefile을 변경하지 않은 상태에서 의존성 파일을 별도로 만드는 경우
subst: string 일부를 치환하는 함수로 $(SRCS)에 들어있는 file name에서 .c를 .o와 .d로, $@에 들어있는 file name에서 .o를 .d로 바꾸고 있다
ifneq: make 이후 생성 target이 clean이 아닌 경우 의존성 파일(여기서는 foobar.d와 common.d)을 include하되, file이 없는 경우에는 무시하도록 -기호를 붙였다. clean일 경우 include하지 않는 이유는 file이 삭제되거나 file name이 변경되는 것 때문에 make utility가 clean을 하지 못하고 의존성에 문제가 있다고 중단되는 현상을 막기 위한 trick이다
make-depend function definition: makedepend 대신 gcc를 이용해 더 정교하게 의존성 파일을 만든다. -M option 대신 -MM option을 적용해 system header file list는 제거하고 실제로 사용자가 작성한 header file list만 포함되도록 한다. 실제로 system header file이 변경될 가능성은 극히 희박하므로 평상시에는 사용자 정의 header file만 포함하는 것으로 충분하다. -MF option은 의존성 정보를 담을 file name을 지정하고, -MP option은 sed script를 사용하지 않고서도 gcc가 알아서 phony target을 만들어 내도록 유도한다. phony target을 만들어 내야 하는 이유는 'target.o: header.h'만 지정하는 경우 header.h object에 대한 규칙이 없어 make가 오류를 발생시키기 때문이다. -MT option은 의존성 파일을 만들어낼 대상을 지정하는데, 이 option이 없다면 gcc가 object file 생성 directory에 상대 경로를 포함하지 못한다

기타 ccache option
-s: 통계 요약 출력
-z: 통계 정보 삭제
-c: cache 삭제
-C: cache 완전 삭제

ccache 설치시 symbolic link를 걸지 않고 make option만으로 해결하는 방법: make CC='ccache gcc'라고 make를 호출하면서 C compiler를 명시적으로 지정

병렬/분산 make
make --jobs=2와 같이 --jobs option을 이용해 동시에 갱신할 target 개수를 지정
분산 make는 pmake 기능을 결합해 GNU make 3.77 이후 version에서 지원하지만 NFS file system과 같은 shared directory를 써야 한다는 제약으로 현재는 distcc와 같은 분산 C/C++ compiler와 함께 동작시키는 방법이 최선
ccache처럼 'make --jobs=8 CC=distcc'로 명시적으로 C compiler를 지정하면 된다

결론
1. Makefile의 의존성을 이해하자
2. Makefile에서 확장자/패턴 규칙을 통해 훨씬 더 간단한 의존성 규칙을 만들 수 있다
3. C 선행 처리 결과로 들어가는 header file등은 make utility가 의존성을 찾지 못한다. 이 경우 makedepend나 mkdep와 같은 의존성 utility나 gcc -M option을 이용해 의존성 정보를 명시적으로 생성해야 한다
4. mkdep, gcc -M, makedepend는 모두 compiler option(macro definition, include file path)를 받아들이므로, 조건부 compile이 필요한 경우에도 문제없이 사용 가능하다. 이 중에서 gcc -M option을 활용하는 방법이 compiler의 특성을 가장 잘 반영한다
5. 반복적인 clean build 속도 향상을 위해서는 ccache와 같은 compiler cache를 활용한다

참고
Managing Projects with GNU Make, 3rd edition, Robert Mecklenburg, O'Reilly 2004
make: http://en.wikipedia.org/wiki/Make_%28software%29
GNU make manual: http://www.gnu.org/software/make/manual/html_node/index.html
makedepend: http://en.wikipedia.org/wiki/Makedepend
makedepend manual: http://linux.die.net/man/1/makedepend
comiplercache homepage: http://www.erikyyy.de/compilercache/
compilercache project homepage: http://ccache.samba.org
ccache manual page: http://ccache.samba.org/ccache-man.html
GCC pre compile header: http://en.wikipedia.org/wiki/Precompiled_header
GCC pre compile document: http://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
NTP: http://en.wikipedia.org/wiki/Network_Time_Protocol
mkdep manual: http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man1/mkdep.1.html
분산 make project homepage: http://distcc.samba.org/
Comments