2009년 12월 19일 토요일

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

대규모 프로젝트에서는 재귀적 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에 관련 사용법을 속시원히 설명한 책은 "유닉스-리눅스 프로그래밍 필수 유틸리티"- 한빛 미디어를 추천한다.