■ 형식
<Target> : <Dependencies>
<Recipe>
º Target : 빌드 대상 이름을 의미한다. 최종적으로 생성하는 파일명을 작성한다.
º Dependencies : 빌드 대상이 의존하는 Target 이나 파일 목록이다. 여기에 나열된 대상들을 먼저 만들고 Recipe 의 명령어를 실행한다.
º Recipe : 빌드 대상을 생성하는 명령이다. 여러 줄로 작성할 수 있고, 각 줄 시작에 반드시 Tab 문자로 Indent 가 삽입되어야 한다.
@ : Shell 명령어를 실행하되, 실행하는 명령어를 출력하지 않을 때 사용한다.
$ vi Makefile
hello :
echo hello
@echo this line printed only once
$ make hello
echo hello
hello
this line printed only once
■ Makefile 내장 규칙 (Built-in Rule)
Make 에서 자주 사용되는 빌드 규칙들은 내장되어 있다. 대표적으로 소스 파일(*.c) 을 컴파일해서 오브젝트 파일(*.o)로 만드는 규칙이 있다.
------------------------------------------------------------------
$ vi Makefile
app.out : main.o foo.o bar.o
gcc -o app.out main.o foo.o bar.o
-----------------------------------------------------------------------------
위와 같이 작성할 경우 Incremental build 를 위한 의존성 검사에서 헤더 파일의 변경을 감지하지 못하는 문제가 발생한다.
make 는 소스 파일의 마지막 변경 시점만 확인하지, 소스 코드 내부의 변경 사항을 하나씩 대조하지 않기 때문이다.
따라서 다음과 같이 각 Target 에 대한 Dependencies 까지 명시해주는 것이 바람직하다.
-----------------------------------------------------------------------------------
$ vi Makefile
app.out : main.o foo.o bar.o
gcc -o gcc.out main.o foo.o bar.o
main.o : foo.h bar.h main.c
foo.o : foo.h foo.c
bar.o : bar.h bar.c
------------------------------------------------------------------------
위와 같이 작성하면 헤더 파일만 변경되어도 의존성이 올바르게 탐지된다. 마지막 세 줄에 있는 Target 의 Recipe 는 모두 생략되었지만, Make 내부 규칙에 의해 컴파일이 수행된다.
■ 변수 선언
make 에서 변수를 선언하는 방법을 크게 2가지가 있다.
Recursively expanded variable(반복 확장 변수), Simply expanded variable(단순 확장 변수)
(1) Recursively expanded variable
= 기호를 사용해서 변수에 값을 할당하는 방법이다. 변수에 다른 변수를 참조하고 있다면, 다른 변수가 참조하고 있는 값을 참조한다.
---------------------------------------------
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo) <위의 실행결과로 Huh? 가 출력된다.>
----------------------------------------------------------------------------
< 장점 >
여러 변수를 사용해서 의도한 대로 make 를 작동 시킬 수 있다.
CFLAGS = $(include_dirs) -O
include_dirs = -lfoo -lbar
-- 변수 CFLAGS 에는 최종적으로 -lfoo -lbar -O 가 할당된다.
< 단점 >
변수 뒤에 다른 것을 추가할 수 없다. 이는 무한 반복에 빠지기 때문이다.
foo = $(foo) -o
all:;echo $(foo)
위와 같이 Makefile 을 작성하고 터미널에서 make 를 실행시키면 다음과 같은 오류 메세지가 출력된다.
Makefile:1: *** Recursive variable `foo' references itself (eventually). Stop.
이 처럼 make 는 무한 반복을 감지하면 오류를 발생시킨다.
(2) Simply expanded variable
재귀적 확장 변수의 단점을 보완하기 위한 변수이다. := 기호를 이용해서 변수에 값을 할당한다.
= 와는 달리 재귀적으로 작동하지 않으며, 변수에 대입된 값을 그대로 출력한다.
---------------------------------------------------
$ vi Makefile
foo := $(foo) bar
all:;echo $(foo)
$ make
echo bar
bar
--------------------------------------
변수를 참조하는 시점에 저장된 값이 아무 것도 없었기 때문에 변수 대신 어떠한 값도 들어가지 않았다.
또한, foo 변수를 스스로 참조하는 무한 반복에 빠지지 않았다. 이러한 특징 덕분에 변수 뒤에 다른 값을 추가할 수 있는 장점이 있다.
---------------------------------------
$ vi Makefile
x := hello
x := $(x) world
all:;echo $(x)
$ make
echo hello world
hello world
■ 자동 변수(Automatic variables)
$@ : Target 을 지칭한다.
$< : 첫 번째 Dependency 이름을 지칭한다.
$^ : 현재 Target이 의존하는 Dependencies 의 전체 목록 (공백으로 구분)
$? : 현재 Target이 의존하는 대상들 중 Target 보다 새롭게 변경된 Dependencies 의 전체 목록
■ 내장 변수 (Built-in Variable)
$(CC) : 컴파일러
$(CFLAGS) : 컴파일 옵션
■ 리링크(Relink)
리링크는 의존성이 변경되었을 때만 타겟을 생성하는 것을 의미한다. 즉, 의존성이 변경되지 않았다면 리링크가 수행되면 안된다.
------------------------------------------------
$ vi Makefile
bonus: $(OBJ_B)
$(AR) $(TARGET) $^
----------------------------------------
OBJ_B 가 존재하면 TARGET 을 생성한다. OBJ_B 가 변경되었는지 체크하지 않기 때문에 make bonus 를 실행하면 무조건 리링크한다.
-------------------------------------------------
$ vi Makefile
ifdef WITH_BONUS
OBJ = $(OBJ_O) $(OBJ_B)
else
OBJ = $(OBJ_O)
endif
...
bonus:
make WITH_BONUS=1 all
------------------------------------------------------
make bonus 를 하면 WITH_BONUS 라는 상수를 1로 지정하여 코드를 실행한다.
WITH_BONUS 가 1일 때는 OBJ 를 OBJ_O 와 OBJ_B 모두 변경사항을 체크하기 때문에 리링크가 방지된다.
■ 패턴 규칙(Pattern Rules)
% 기호는 와일드 카드와 비슷한 역할을 한다.
-------------------------------------------------
$ vi Makefile
%.o : %.c
$(CC) $(CFLAGS) -c $<
------------------------------------------------------------
오브젝트 파일(.o)는 .c 로 끝나는 파일에 의존한다는 것으로 해석할 수 있다.
----------------------------------------
$ vi Makefile
.c.o:
$(CC) $(CFLAGS) -c $<
-------------------------------------------
.c.o 는 Old-fashioned 표현이라고 공식 문서에서 언급하고 있다. 기능은 동일하다.
■ 함수
문법 : $(function arguments)
(1) 치환 함수
① subst
$(subst ee,EE,feet on the street)
실행 결과로 ‘fEEt on the strEEt’ 가 생성된다.
② 변수를 활용한 치환
$(변수명:pattern=replacement)
선언된 변수를 활용해서 문자열을 치환할 수도 있다.
--------------------------------------------------------------------
$ vi Makefile
SRCS := first.c second.c third.c
OBJS := $(SRCS:.c=.o)
# 치환 결과
# OBJS = first.o second.o third.o
---------------------------------------------------------------
OBJS 에는 SRCS 에서 .c 와 매칭되는 문자열을 .o 로 치환한다.
이는 patsubst 함수와 동일한 기능을 수행한다.
(2) 파일 이름 관련 함수
① addprefix
$(addprefix prefix,names...)
접미사를 names 앞에 추가하는 함수이다.
예)
# 실행 예제
$(addprefix src/,foo bar)
# 실행 결과
src/foo src/bar
■ 가짜 목표 파일 .PHONY
.PHONY 는 Target 위치에 작성하지만, 레시피 실행을 위한 이름일 뿐, 실제 파일 이름이 아니라는 것을 알려주기 위해 사용한다.
참고로 phony 는 사전적으로 ‘가짜’라는 의미이다. 맨 윗줄에 작성하는 것이 편하다.
(1) 실제 파일 이름과 충돌 해결
디렉토리에 파일명이 test 인 파일이 있다고 하면,
---------------------------------------------------------
test :
@echo test makefile
---------------------------------------------------------
실행하면 결과
$ make test
make: 'test' is up to date. # 출력 메시지
-----------------------------------------------------------
make 의 Target 과 디렉토리 내 파일명과 분리하기 위해서 .PHONY 를 사용한다.
------------------------------------------------
.PHONY : test
test :
@echo test makefile
-------------------------------------------------------
실행 결과는
$ make test
test makefile # 출력 메시지
(2) 성능 개선
Makefile 안에서 make 를 다시 실행시키는 경우 문제가 발생한다. 다음과 같이 디렉토리가 구성되었다고 하면,
-------------------------------------------------------------
/main
|_ Makefile
|_ /foo
|_ Makefile
|_ ... // other files
|_ /bar
|_ Makefile
|_ ... // other files
|_ /koo
|_ Makefile
|_ ... // other files
main 디렉토리의 Makefile 이다.
--------------------------------------------------
$ vi Makefile
SUBDIRS = foo bar koo
subdirs :
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
----------------------------------------------------
make subdirs 을 실행시키면 다음과 같은 문제가 생긴다.
- 하위 make 에서 에러가 발생해도 계속 빌드가 진행된다.
- 하나의 규칙만 실행되기 때문에 make 의 장점인 병렬 수행이 되지 않는다.
이러한 문제를 해결하기 위해 다음과 같이 작성할 수 있다.
-----------------------------------------------------------------------
SUBDIRS = foo bar koo
.PHONY : subdirs $(SUBDIRS)
subdirs : $(SUBDIRS)