Make 기초사용법
Make 기초사용법 ¶
1.1. 시작하기전에 ¶프로그래밍을 배우려면 우선 "make"의 기본용법이라도 알고 시작하자는 주장을 해봅니다. 그 만큼 매우 중요한 유틸리티입니다. 나름대로 간편한 문법과 가독성이 높은 문법 때문에 요즘 대부분의 거대한 공개 프로젝트들은 "make"를 널리 사용하고 있습니다. 꼭 프로그래밍의 세계에 입문을 하고자 하시는 분들께서는 "make"의 사용법을 적어도 기본문법이라도 알고 계셔야 합니다. 그래서 필자는 간략하게나마 도움이 되고자 이렇게 문서로 남깁니다. 부디 멋진 GNU 개발자가 되시길 바랍니다.
1.2. Make 의 목적 ¶우리가 일반적으로 개발하고 관리하는 하나의 프로젝트는 C source를 컴파일하여 Object 파일을 만들고 이러한 Object 몇개를 묶어서 링크(Link)과정을 거쳐서 최종 목적인 실행파일을 생성합니다. 이때 사용하는 명령은 다음과 같겠죠. 아~ 무지 길다~!
bash# cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c
bash# ls test.c test.o bash# ld -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o test.o bash# ./test Hello world ! bash# _ 1.3. 기본규칙 ¶"make"는 크게 Target, Depend, Command, Macro 로 구성되어 있습니다. 우선 기본적으로 Target, Depend, Command 의 구성을 살펴보겠습니다.
<Target>: <Depend> ?... [[;] <Command>]
<탭문자><Command> 여기서 "Target"은 생성하고자 하는 목적물을 지칭하며 Depend 는 Target을 만들기 위해서 필요한 요소를 기술하게 됩니다. 그리고 Command 는 일반 Shell 명령이 옵니다. 이때 Command는 Depend 의 파일생성시간(또는 변경된 시간)을 Target과 비교하여 Target 보다 Depend의 파일이 시간이 보다 최근인 경우로 판단될때에만 실행됩니다. 물론 이것에 대한 예외적인 규칙이 있습니다만 일단 무시하고 받아들이세요. 이제 간단히 다음과 같이 "Makefile" 이라는 파일명으로 다음과 같이 작성하여 "make"의 행동을 기술합니다. 단, 주의할것은 Command 는 반드시 앞에 <TAB>문자가 와야 합니다. 물론 예외상황이 있기는 하지만 나중에 그에 대한 내용을 다루기로 하고 일단 ld, cc 명령 앞에 반드시 <TAB>문자로 입력하세요. 그리고 너무 긴 줄은 백슬래쉬(\)와 개행을 통하여 나눌수 있습니다.
test: test.o
ld -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 \ -o test \ /usr/lib/crt1.o \ /usr/lib/crti.o \ /usr/lib/crtn.o \ test.o test.o: test.c cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c
bash# make test
cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c ld -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o test.o bash# _
bash# make test
make: `test'는 이미 갱신되었습니다. bash# _
bash# touch test.c
bash# make test cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c ld -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o test.o bash# _ 1.4. Macro ¶매크로는 다음과 같이 "=" 문자의 왼편에는 Macro의 대표이름(Label)을 기술하고 오른편에는 그 내용을 적습니다. 이때 "=" 문자에 인접한 양쪽의 공백(Space)문자는 무시됩니다. 이것은 매우 기본적인 Macro를 예기하는것이며 이 밖에도 몇가지 확장된 Macro 가 있습니다. 하지만 일단 그것은 나중에 예기하도록 하겠습니다.
<Macro name> = <Macro 내용>
CC = cc
LD = ld CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o test: test.o $(LD) $(LDFLAGS) -o test $(STARTUP) test.o test.o: test.c $(CC) $(CFLAGS) -o test.o test.c 1.5. 기본 확장자 규칙 ¶우리는 보통 C source를 목적파일로 컴파일합니다. 이것은 확장자가 통상 ".c"에서 ".o"를 만들어 내는 규칙이 생성될법 합니다. "make"는 이점에 착안하여 다음과 같은 확장자 규칙을 이용할수 있습니다. 물론 이것은 한가지 방법일 뿐이며 이 밖에도 몇가지 방법이 있습니다.
.c.o:
$(CC) $(CFLAGS) -o $@ $<
CC = cc
LD = ld CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o test: test.o $(LD) $(LDFLAGS) -o $@ $(STARTUP) $^ .c.o: $(CC) $(CFLAGS) -o $@ $< 1.6. 가짜 target ¶이제 지금까지 작성한 "Makefile"을 사용해보면 한가지 불편한 점을 느낀 분들이 계실겁니다. "test.c"로 "test.o"를 만들고 "test.o"로 "test"를 만들도록 작성하였지만 소스파일의 수정이 없는 경우 항상 "make"는 더이상의 컴파일 및 링크과정을 하지 않는 점입니다. 때문에 간혹 다시 깨끗히 빌드하고 싶을때 "touch test.c"를 수행하거나 "rm test.o test" 를 수행해야 합니다. 이것 역시 매우 불편합니다. 이를 해소하고자 다음과 같이 작성될수 있겠습니다.
CC = cc
LD = ld RM = rm -f CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o clean: $(RM) test.o test test: test.o $(LD) $(LDFLAGS) -o $@ $(STARTUP) $^ .c.o: $(CC) $(CFLAGS) -o $@ $<
CC = cc
LD = ld RM = rm -f CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o .PHONY: all clean all: test clean: -$(RM) test.o test test: test.o $(LD) $(LDFLAGS) -o $@ $(STARTUP) $^ .c.o: $(CC) $(CFLAGS) -o $@ $< 1.7. 응용 ¶이제 다음과 같이 4개의 파일을 작성하여 하나의 프로젝트를 작성하였다고 합시다.
hello.c
#include <stdio.h>
void HelloWorld(void) { fprintf(stdout, "Hello world.\n"); }
#include <stdio.h>
#include "hello.h" int main(void) { HelloWorld(); return(0); }
extern void HelloWorld(void);
CC = cc
LD = ld RM = rm -f CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -v -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o BUILD = test OBJS = test.o hello.o .PHONY: all clean all: $(BUILD) clean: ; $(RM) *.o $(BUILD) test: $(OBJS) ; $(LD) $(LDFLAGS) -o $@ $(STARTUP) $^ # 의존관계 성립 hello.o: $($@:.o=.c) $($@:.o=.h) Makefile test.o: $($@:.o=.c) hello.h Makefile # 확장자 규칙 (컴파일 공통 규칙) .c.o: ; $(CC) $(CFLAGS) -o $@ $< 위에서 "$($@:.o=.c)" 라는 이상한 문자열이 좀 마음에 안들겁니다. 하지만 이것은 매우 함축적인 매크로이며 많이들 애용하고 있는 겁니다. 대략 다음과 같은 형식을 사용합니다. "$(<문자열>:<우측으로부터 매칭될 문자열>=<치환될 문자열>)" 이것은 즉, "$@" 부분은 자신의 Target인 "hello.o" 또는 "test.o"를 말합니다. 그리고 거기서 우측으로부터 ".o"가 발견되면 ".c"로 치환하라는 뜻입니다. 즉, "$(hello.o:.o=.c)" 또는 "$(test.o:.o=.c)"로 확장되고 여기서 다시 각각 "hello.c" 와 "test.c"로 치환되어 결국 해당 소스를 지칭하게 되는 셈입니다. 그리고 Command 부분이 <TAB>이 쓰이지 않고 한줄에 ";"(세미콜론)으로 분리되어서 해당 라인에 직접 Command 가 쓰이는 것을 확인하실수 있을겁니다. 무지 거대한 "Makefile"을 간략히 보이게 하기 위해서 이렇게도 사용할수 있다는 것을 예로 보인것입니다. 의존관계를 성립하는 부분은 Command 가 없는것을 볼수 있는데 이것은 비슷한 다른 Target에서 Command 가 결합되어 수행될수 있고 여기서는 ".c.o: ; ..." 부분의 Command 가 결합됩니다. 여기서 의존관계를 최대한 자세하게 기술하였는데 만약 "hello.h" 가 변경된다면 "hello.o"와 "test.o"는 다시 빌드될것입니다. 또한 "Makefile" 도 수정되면 다시 빌드될것이라는 것이 예상됩니다. 이처럼 의존관계를 따로 기술하는 이유는 차후에 여러분들이 사용하시다보면 이유를 알게 될겁니다. 의존관계라는게 서로 굉장히 유기적으로 걸리는 경우가 많기 때문에 보다 보기 편하게 하는 이유도 있고 차후에 의존관계가 변경되었을때 쉽게 찾아서 변경을 할수 있도록 하는것도 한가지 이유입니다. 아주 조금만 공통적인 의존관계를 정리해서 작성한다면 다음과 같이도 작성할수 있습니다. 바로 Target이 한번에 2개 이상이 정의될수도 있다는 겁니다.
CC = cc
LD = ld RM = rm -f CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -v -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o BUILD = test OBJS = test.o hello.o .PHONY: all clean all: $(BUILD) clean: ; $(RM) *.o $(BUILD) test: $(OBJS) ; $(LD) $(LDFLAGS) -o $@ $(STARTUP) $^ # 의존관계 성립 $(OBJS): $($@:.o=.c) hello.h Makefile # test.o hello.o: $($@:.o=.c) hello.h Makefile # 확장자 규칙 (컴파일 공통 규칙) .c.o: ; $(CC) $(CFLAGS) -o $@ $< 1.8. 다른 Makefile의 포함 ¶make를 작성하다 보면 굉장히 큰 규모의 프로젝트에서는 Makefile이 매우 커져서 다루기 어려울 수 있습니다. 이때 Makefile을 적절히 나누어 다른 파일로 저장하고 include 구문으로 이를 포함하여 해석할 수 있도록 할 수 있습니다.
include <Makefile-names...>
include 구문에는 변수를 사용할 수 있으며 파일과 파일 사이는 공백으로 구분합니다. 그리고 포함하고자 하는 Makefile이 없을수도 있는 것을 허용하기 위해서는 대쉬(-)를 include 앞에 붙여 사용하도록 하면 있는 경우는 포함하고 그렇지 않은 경우는 무시하도록 할 수도 있습니다.
EXT_MAKEFILE:=build.mk
-include *.mk $(EXT_MAKEFILE) 1.9.1. GNU Make에서 기본적으로 예약되어 사용되는 환경변수 ¶
1.9.2. Windows에서의 GNU Make 사용 ¶
1.9.3. 복잡한 치환 ¶
1.10.1. 공백을 포함한 디렉토리명 또는 파일명에 대한 문제 ¶
1.10.2. GNU make v3.81 이후 버젼과 그 이전의 버젼간 기능의 제약 ¶
|
Even a hawk is an eagle among crows. |












![[http]](/imgs/http.png)
또는