GNU Make 와 gcc 를 이용한 auto-dependency Makefile 만들기
Autotools, CMake, qmake 같은 빌드 도구나 Open Watcom 의 wmake 같은 경우에는 자체적으로 auto-dependency 를 지원하거나 이를 위한 지시자를 제공한다. 하지만 GNU Make 의 경우에는 이러한 기능을 자체적으로 지원하지도 않고, 이를 위한 지시자를 제공하지도 않는다.
하지만, 다행히도 gcc 를 이용해서 이 기능을 구현할 수 있다. 우선 이에 필요한 gcc 의 기능부터 살펴보자.
-M 옵션 : 해당 소스 파일의 의존성을 GNU Make 의존성 규칙에 맞추어 출력한다. 시스템 헤더 파일들도 포함된다.
예를 들어, 다음과 같은 hello.c 를 생각해보자.
이를 실행한 결과는 이렇다.
-MM 옵션 : -M 옵션과 비슷한데, 시스템 헤더 파일은 제외한다.
다음은 실행 결과이다.
-MP 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 헤더 파일들에 대한 phony 타겟을 만든다. 이는 헤더 파일이 지워졌을 때, 오류가 발생하는 것을 방지한다.
다음은 실행 결과이다.
-MT 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 타겟의 이름을 바꾼다.
다음은 실행 결과이다.
-MF 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 출력 결과를 해당 장치 또는 파일로 보낸다.
이 때 실행 결과는 표준 출력 장치에 나타나는 것이 아니라 hello.d 라는 파일에 저장된다.
바로 이 옵션들을 통해서 auto-dependecy Makefile 파일을 만들 수 있다. 문제는 이 내용을 Makefile 파일에서 어떻게 활용할 것인가이다.
의존성 파일부터 만들어 보자. 의존성 파일을 만들기 위한 기본적인 형태는 다음과 같다.
이제는 이렇게 만들어진 의존성 파일을 포함시켜야 한다. GNU Make는 이를 위해 include 지시자를 지원한다.
이때 hello.d 가 없다면 주어진 생성 규칙을 통해서 새로이 만들거나 갱신한다. 그럼에도 불구하고 hello.d 를 만들 수 없다면, GNU Make 는 오류를 보고하고, 중지한다.
마지막으로 실행 파일의 의존성 규칙을 추가해주면 된다.
물론 다음과 같은 기본적인 내용이 Makefile 앞 부분에 더 추가되어야 한다.
완성된 Makefile 의 모습은 이렇다.
이제 make 를 실행하면, hello.c 자체가 수정되거나 hello.c 가 포함하는 헤더 파일이 수정되면 새롭게 빌드된다.
그런데 문제는 hello.d 파일 자체는 한 번 만들어지면 hello.c 가 수정되지 않는 이상, 다시 갱신되지 않는다. 왜냐하면 hello.d 파일에 대한 의존성은 hello.c 밖에 없기 때문이다. 이 문제는 hello.d 의 의존성을 hello.o 의 의존성과 동일하게 만들어주면 된다. 위 8번째 줄을 아래와 같이 바꾸자.
GNU Make 의 다중 대상 기능을 이용한 것으로. 타겟이 "hello.o" 에서 "hello.o hello.d" 로 된다.
GNU Make 를 실행하다보면 hello.d 가 지워진 경우에, 다음과 같은 오류 메세지가 출력된다.
이 오류 메세지가 나오더라도 중단되지는 않지만, 뭔가 꺼림칙하다. 어떻게 없앨 수 없을까 ? 바로 -include 를 쓰면 된다.
이 예제의 경우에는 소스 파일이 하나에 불과해서 이 정도이지만, 소스 파일이 여러 개일 경우에는 해당 소스 파일마다 일일이 추가해줘야 한다. 그리고 다른 프로젝트에 쓰기에도 수정해야 할 것이 많다. 이 얼마나 불편한가 ? 이를 개선해 보자.
우리가 고려할 것은 실행 파일과, 소스 파일이다. 다른 모든 것들은 이들로부터 변형될 수 있다. 이들을 위한 변수를 도입하자. PROGNAME 과 SRCS 에 실행파일과 소스 파일을 담자.
이제 의존성 파일 변수와 실행 파일을 만들기 위한 목적 파일 변수를 도입하자.
이 내용들을 반영하면 위 Makefile 은 다음과 같이 쓰여질 수 있다.
위 내용을 보면 특정 이름 대신에 변수를 사용한 것 외에도 몇 가지 달라진 점이 있는데, 하나는 16번째 줄에 있는 암묵적 규칙이고, 14번째 줄에 있는 $^ 이다. 암묵적 규칙은 여러개의 파일에 대해 동일한 규칙을 적용하기 위한 것이고, $^ 은 모든 의존 파일들을 뜻한다. 반면에 $< 은 첫번째 의존 파일을 뜻한다.
이렇게 해 두면, 다른 프로젝트에 적용을 한다든지 또는 소스가 변경되었다든지 할 때, PROGRAM 과 SRCS 만 바꾸어주면 된다. 소스 파일을 더 추가하고 싶다면 빈 칸을 사이에 두고 파일 이름을 적어주면 된다. 예를 들어 hello.c 외에 world.c 가 추가되었다면 2번째 줄을 이렇게 바꾸면 된다.
이외에도 여러가지 컴파일 플래그나 링커 플래그, 컴파일러나 링커를 위한 여러 변수들을 도입하면 훨씬 더 유연한 Makefile 이 될 것이다.
그리고 기회가 된다면 추후에 여러 개의 실행 파일과 DLL 등을 한꺼번에 빌드할 수 있는 Makefile 에 대해서도 글을 쓰도록 하겠다.
하지만, 다행히도 gcc 를 이용해서 이 기능을 구현할 수 있다. 우선 이에 필요한 gcc 의 기능부터 살펴보자.
-M 옵션 : 해당 소스 파일의 의존성을 GNU Make 의존성 규칙에 맞추어 출력한다. 시스템 헤더 파일들도 포함된다.
예를 들어, 다음과 같은 hello.c 를 생각해보자.
1 2 3 4 5 6 7 8 9 | // hello.c #include <stdio.h> int main( void ) { printf("Hello, world\n"); return 0; } |
- gcc -M hello.c
이를 실행한 결과는 이렇다.
- hello.o: hello.c f:/lang/gcc/usr/include/stdio.h \
- f:/lang/gcc/usr/include/sys/cdefs.h \
- f:/lang/gcc/usr/include/sys/gnu/cdefs.h \
- f:/lang/gcc/usr/include/features.h f:/lang/gcc/usr/include/sys/_types.h \
- f:/lang/gcc/usr/include/machine/_types.h \
- f:/lang/gcc/usr/include/386/_types.h
-MM 옵션 : -M 옵션과 비슷한데, 시스템 헤더 파일은 제외한다.
- gcc -MM hello.c
다음은 실행 결과이다.
- hello.o: hello.c
-MP 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 헤더 파일들에 대한 phony 타겟을 만든다. 이는 헤더 파일이 지워졌을 때, 오류가 발생하는 것을 방지한다.
- gcc -M -MP hello.c
다음은 실행 결과이다.
- hello.o: hello.c f:/lang/gcc/usr/include/stdio.h \
- f:/lang/gcc/usr/include/sys/cdefs.h \
- f:/lang/gcc/usr/include/sys/gnu/cdefs.h \
- f:/lang/gcc/usr/include/features.h f:/lang/gcc/usr/include/sys/_types.h \
- f:/lang/gcc/usr/include/machine/_types.h \
- f:/lang/gcc/usr/include/386/_types.h
- f:/lang/gcc/usr/include/stdio.h:
- f:/lang/gcc/usr/include/sys/cdefs.h:
- f:/lang/gcc/usr/include/sys/gnu/cdefs.h:
- f:/lang/gcc/usr/include/features.h:
- f:/lang/gcc/usr/include/sys/_types.h:
- f:/lang/gcc/usr/include/machine/_types.h:
- f:/lang/gcc/usr/include/386/_types.h:
-MT 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 타겟의 이름을 바꾼다.
- gcc -M -MT hello_MT.o hello.c
다음은 실행 결과이다.
- hello_MT.o: hello.c f:/lang/gcc/usr/include/stdio.h \
- f:/lang/gcc/usr/include/sys/cdefs.h \
- f:/lang/gcc/usr/include/sys/gnu/cdefs.h \
- f:/lang/gcc/usr/include/features.h f:/lang/gcc/usr/include/sys/_types.h \
- f:/lang/gcc/usr/include/machine/_types.h \
- f:/lang/gcc/usr/include/386/_types.h
-MF 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 출력 결과를 해당 장치 또는 파일로 보낸다.
- gcc -M -MF hello.d hello.c
이 때 실행 결과는 표준 출력 장치에 나타나는 것이 아니라 hello.d 라는 파일에 저장된다.
바로 이 옵션들을 통해서 auto-dependecy Makefile 파일을 만들 수 있다. 문제는 이 내용을 Makefile 파일에서 어떻게 활용할 것인가이다.
의존성 파일부터 만들어 보자. 의존성 파일을 만들기 위한 기본적인 형태는 다음과 같다.
- hello.d : hello.c
- gcc -M -MP -MF $@ $<
이제는 이렇게 만들어진 의존성 파일을 포함시켜야 한다. GNU Make는 이를 위해 include 지시자를 지원한다.
- include hello.d
이때 hello.d 가 없다면 주어진 생성 규칙을 통해서 새로이 만들거나 갱신한다. 그럼에도 불구하고 hello.d 를 만들 수 없다면, GNU Make 는 오류를 보고하고, 중지한다.
마지막으로 실행 파일의 의존성 규칙을 추가해주면 된다.
- hello.exe : hello.o
물론 다음과 같은 기본적인 내용이 Makefile 앞 부분에 더 추가되어야 한다.
- .SUFFIXES : .c .o .exe
- .PHONY : all
- all : hello.exe
완성된 Makefile 의 모습은 이렇다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | .SUFFIXES : .c .o .exe .PHONY : all all : hello.exe hello.d : hello.c gcc -M -MP -MF $@ $< include hello.d hello.exe : hello.o gcc -o $@ $< |
이제 make 를 실행하면, hello.c 자체가 수정되거나 hello.c 가 포함하는 헤더 파일이 수정되면 새롭게 빌드된다.
그런데 문제는 hello.d 파일 자체는 한 번 만들어지면 hello.c 가 수정되지 않는 이상, 다시 갱신되지 않는다. 왜냐하면 hello.d 파일에 대한 의존성은 hello.c 밖에 없기 때문이다. 이 문제는 hello.d 의 의존성을 hello.o 의 의존성과 동일하게 만들어주면 된다. 위 8번째 줄을 아래와 같이 바꾸자.
- gcc -M -MP -MT "$(@:.d=.o) $@" -MF $@ $<
GNU Make 의 다중 대상 기능을 이용한 것으로. 타겟이 "hello.o" 에서 "hello.o hello.d" 로 된다.
GNU Make 를 실행하다보면 hello.d 가 지워진 경우에, 다음과 같은 오류 메세지가 출력된다.
- Makefile:10: hello.d: No such file or directory
이 오류 메세지가 나오더라도 중단되지는 않지만, 뭔가 꺼림칙하다. 어떻게 없앨 수 없을까 ? 바로 -include 를 쓰면 된다.
이 예제의 경우에는 소스 파일이 하나에 불과해서 이 정도이지만, 소스 파일이 여러 개일 경우에는 해당 소스 파일마다 일일이 추가해줘야 한다. 그리고 다른 프로젝트에 쓰기에도 수정해야 할 것이 많다. 이 얼마나 불편한가 ? 이를 개선해 보자.
우리가 고려할 것은 실행 파일과, 소스 파일이다. 다른 모든 것들은 이들로부터 변형될 수 있다. 이들을 위한 변수를 도입하자. PROGNAME 과 SRCS 에 실행파일과 소스 파일을 담자.
- PROGNAME := hello.exe
- SRCS := hello.c
이제 의존성 파일 변수와 실행 파일을 만들기 위한 목적 파일 변수를 도입하자.
- PROGNAME_DEPS := $(SRCS:.c=.o)
- DEPS := $(SRCS:.c=.d)
이 내용들을 반영하면 위 Makefile 은 다음과 같이 쓰여질 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | PROGRAM := hello.exe SRCS := hello.c PROGRAM_DEPS := $(SRCS:.c=.o) DEPS := $(SRCS:.c=.d) .SUFFIXES : .c .o .exe .PHONY : all all : $(PROGRAM) $(PROGRAM) : $(PROGRAM_DEPS) gcc -o $@ $^ %.d : %.c gcc -M -MP -MT "$(@:.d=.o) $@" -MF $@ $< -include $(DEPS) |
위 내용을 보면 특정 이름 대신에 변수를 사용한 것 외에도 몇 가지 달라진 점이 있는데, 하나는 16번째 줄에 있는 암묵적 규칙이고, 14번째 줄에 있는 $^ 이다. 암묵적 규칙은 여러개의 파일에 대해 동일한 규칙을 적용하기 위한 것이고, $^ 은 모든 의존 파일들을 뜻한다. 반면에 $< 은 첫번째 의존 파일을 뜻한다.
이렇게 해 두면, 다른 프로젝트에 적용을 한다든지 또는 소스가 변경되었다든지 할 때, PROGRAM 과 SRCS 만 바꾸어주면 된다. 소스 파일을 더 추가하고 싶다면 빈 칸을 사이에 두고 파일 이름을 적어주면 된다. 예를 들어 hello.c 외에 world.c 가 추가되었다면 2번째 줄을 이렇게 바꾸면 된다.
- SRCS := hello.c world.c
이외에도 여러가지 컴파일 플래그나 링커 플래그, 컴파일러나 링커를 위한 여러 변수들을 도입하면 훨씬 더 유연한 Makefile 이 될 것이다.
그리고 기회가 된다면 추후에 여러 개의 실행 파일과 DLL 등을 한꺼번에 빌드할 수 있는 Makefile 에 대해서도 글을 쓰도록 하겠다.
댓글
댓글 쓰기