Rules
make 의 메인 기능은 rule 입니다. 앞서 소개한 make 에서 제공되는 변수, 함수기능, 대입연산 은 rule 을 작성하는데 활용됩니다. makefile 을 작성한 후에 프롬프트 상에서 make 명령을 실행하면 최종적으로 실행되는 것은 shell script 명령입니다. 이것은 결과적으로 보면 shell script 파일을 실행하는 것과 차이가 없는 것인데요. 하지만 make 이 다른 점은 사용가 작성한 rule 에 따라서 어떤 rule 의 shell 명령을 실행할지, 실행할 경우는 어떤 순서에 따라 실행할지가 결정되어 실행된다는 것입니다.
makefile 에 임의의 단어를 입력한 후 실행하면 기본적으로 rule 정의로 인식하는 것을 볼 수 있습니다.
sh$ cat Makefile
hello
sh$ make # 기본적으로 rule 정의로 인식하여
Makefile:2: *** missing separator. Stop. # ':' separator 문자가 없다는 오류발생
------------------------------------------
sh$ cat Makefile
hello :
sh$ make # ':' separator 문자는 존재하지만
make: Nothing to be done for 'hello'. # 실행할 명령이 없으므로
-----------------------------------------
sh$ cat Makefile
hello :
@echo hello make rule !
sh$ make
hello make rule !
Rule 기본 구조
rule 은 targets 과 prerequisites 을 작성하는 타겟 라인과 recipe 로 구성됩니다.
targets 과 prerequisites 은 :
separator 문자를 이용해 구분하고
prerequisites 들은 다시 다른 rule 의 target 이 될 수 있습니다.
rule 은 두 가지 형태로 작성할 수 있는데
(2) 번과 같이 prerequisites 뒤에 타겟 라인의 끝을 나타내는;
문자를 붙인 후에
하나의 라인에 연이어서 recipe 를 작성할 수도 있고,
(1) 번과 같이 tab 문자를 이용해 recipe 라인을 별도의 라인에 분리해 작성할 수도 있습니다.
make 이 실행되면 global 영역에서 변수, 함수, 대입연산 이 처리됨과 동시에 rule 의 타겟 라인도 처리됩니다.
rule 이 말해주는 것은 두 가지입니다.
- target 파일을 갱신해야 되는지 말아야 되는지 ( target 라인 )
- target 파일 갱신이 필요할 경우 어떻게 갱신해야 되는지 ( recipe 라인 )
여기서 target 파일의 갱신이 필요할 때는 다음과 같이 두 가지 경우입니다.
- target 파일이 존재하지 않을 때
- target 파일과 prerequisites 에 있는 파일들의 last-modification time 을 비교했을 때 하나라도 target 파일보다 최신이 있을 경우
make 은 작성된 순서에 따라 rule 들을 읽어들이는데 이때 만약에 앞선 rule 과
동일한 이름의 타겟을 갖는 rule 이 중복되어 나타나면 앞선 rule 이 override 됩니다
( 이때 warning 메시지로 알려줍니다 ).
makefile 이 실행될 때 처음 실행되는 rule 을 default goal 이라고 하는데
따로 .DEFAULT_GOAL
변수를 설정하지 않았다면 rule 작성시 제일 위에 위치한 rule 이 default goal 이 되어 실행됩니다.
.
으로 시작하는 타겟은 default goal 이 되지 않습니다. 하지만 타겟 이름에/
문자가 포함되면 가능합니다.
Rule 은 기본적으로 파일을 대상으로 한다.
rule 의 타겟 라인에 존재하는 targets 과 prerequisites 은 모두 파일을 나타냅니다. 따라서 파일이 존재하지 않으면 파일을 생성하기 위해 해당 rule 을 찾아서 recipe 를 실행합니다. 만약에 매칭되는 rule 을 발견하지 못하면 오류로 make 실행이 종료됩니다.
다음은 rule 에 prerequisites 이 존재하지 않을 경우 테스트입니다.
sh$ cat Makefile
foo :
@echo 1111111
-------------------
sh$ make # 타겟 파일이 존재하지 않으면 파일 생성을 위해 recipe 가 실행된다.
1111111
sh$ touch foo # 임의로 foo 파일 생성
sh$ make # 룰에 prerequisites 이 없으므로 타겟 파일이 존재하면 'up to date' 상태가 된다.
make: 'foo' is up to date.
다음은 rule 에 prerequisites 이 존재할 경우 테스트입니다.
# 현재 디렉토리에 main.o main.c main.h 파일이 모두 존재하지 않는다면
main.o : main.c main.h
@echo gcc -c -o $@ $<
----------------------------
sh$ make # main.c 파일 생성을 위한 rule 이 존재하지 않으므로 종료된다.
make: *** No rule to make target 'main.c', needed by 'main.o'. Stop.
sh$ touch main.c # 임의로 main.c 파일 생성
sh$ make # main.h 파일 생성을 위한 rule 이 존재하지 않으므로 종료된다.
make: *** No rule to make target 'main.h', needed by 'main.o'. Stop.
sh$ touch main.h # main.h 파일 생성
# prerequisites 에 해당하는 main.c main.h 파일이 모두 존재하므로
# 타겟 파일에 해당하는 main.o 를 생성하기 위해 해당 recipe 가 실행된다.
sh$ make
gcc -c -o main.o main.c
다음은 prerequisites 에 해당하는 main.c 의 rule 이 존재하는 경우입니다. make 은 기본적으로 rule 이 실행되면 타겟 파일의 실제 생성 여부에 상관없이 진행을 계속합니다.
# 현재 디렉토리에 main.o main.c 파일이 모두 존재하지 않는다고 하면
main.o : main.c
@echo target : $@
main.c :
@echo target : $@
---------------------
sh$ make
target : main.c # main.o, main.c 파일이 모두 존재하지 않으므로 prerequisites 에
target : main.o # 해당하는 main.c 룰이 먼저 실행되고 main.o 룰이 실행된다.
sh$ touch main.c # main.c 파일 생성
sh$ make # main.o 파일만 존재하지 않으므로
target : main.o
sh$ touch main.o # main.o 파일 생성
sh$ make # main.o, main.c 파일이 모두 존재하므로 rule 이 실행되지 않는다.
make: 'main.o' is up to date.
sh$ touch main.c # prerequisites 에 해당하는 main.c 파일을 최신으로 갱신하면
sh$ make # main.o 파일을 갱신해야 되므로 recipe 가 실행된다.
target : main.o
디렉토리도 파일이다.
따라서 파일과 동일하게 targets 및 prerequisites 으로 사용될 수 있습니다.
foo : BUILD
@echo target : $@
BUILD :
mkdir -p $@
##### 실행 결과 #####
sh$ ls
Makefile
sh$ make
mkdir -p BUILD # BUILD 파일이 존재하지 않으므로 recipe 가 실행된다.
target : foo
sh$ ls
BUILD/ Makefile
sh$ make # BUILD 파일이 존재하므로 더이상 recipe 가 실행되지 않는다.
target : foo
up to date 타겟과 파일이 아닌 가짜 타겟
rule 을 타겟 파일을 생성하기 위한 용도가 아닌 단순히 recipe 에 존재하는 shell script 명령을 실행하기 위한 용도로 사용해야될 경우가 있습니다. 이때는 타겟 파일이 존재하던 않던 상관없이 항상 해당 rule 의 recipe 가 실행돼야 하는데요. 여기에는 다음과 같은 방법들이 사용됩니다.
타겟 파일이 존재하지 않으면 파일을 생성해야 되기 때문에
항상 recipe 가 실행됩니다. 이때 만약에 실행할 recipe 가 없으면 해당 타겟은
up to date
상태가 됩니다.
# 현재 디렉토리에 foo 파일이 존재하지 않을 경우
foo : foo : ;
@echo fooooo
#### 실행 결과 #### #### 실행 결과 ####
fooooo make: 'foo' is up to date.
이것을 활용하면 특정 타겟이 항상 실행되게 할 수 있습니다.
다음 첫 번째 예제를 보면 touch 명령으로 foo
타겟 파일을 생성한 후에는
다시 recipe 가 실행되지 않는 반면에 bar :
타겟을 사용할 경우는
bar
는 foo
보다 항상 up to date
상태가 되므로 foo
파일을 생성한 후에도
계속해서 recipe 가 실행되는 것을 볼 수 있습니다.
foo : bar
foo : @echo fooooo
@echo fooooo
bar :
#### 실행 결과 #### #### 실행 결과 ####
sh$ make sh$ make
fooooo fooooo
sh$ touch foo sh$ touch foo
sh$ make sh$ make
make: 'foo' is up to date. fooooo # 계속해서 실행된다.
bar :
와 같은 역할을 하는 타겟을 보통 이름으로 FORCE :
를 많이 사용하는데요.
이 방법은 실제 예전에 make 에서 사용하던 방식입니다.
하지만 이 방법의 단점은 만약에 현재 디렉토리에 FORCE
파일이 존재하고
타겟 파일인 foo
가 보다 최신이라면 해당 룰은 실행되지 않게 되겠죠.
그래서 이와 같은 단점을 보완하기 위해 나온 것이 .PHONY
타겟 입니다.
phony 는 사전에서 찾아보면 가짜라는 의미를 가지고 있는데
말 그대로 파일이 아닌 가짜 타겟을 지정할 때 사용합니다.
.PHONY:
타겟으로 등록을 하면 update 체킹도 하지 않고 타겟 파일이 존재하던 않던
상관없이 항상 recipe 가 실행됩니다.
# FORCE 타겟에 의해 foo 타겟은 항상 실행된다.
foo : bar zoo FORCE
.PHONY: clean @echo fooooo
clean :
rm -f $(objects) FORCE :
.PHONY: FORCE
PHONY 타겟을 사용하는 것과 FORCE 타겟을 사용하는 것은 둘 다 많이 사용합니다.
prerequisites 에 FORCE 타겟이 존재하면 이 룰은 항상 실행된다는 것을 쉽게 알 수 있습니다.
또한 PHONY 타겟에는 %
문자를 이용하는 pattern 은 등록하지 못하므로 만약에
특정 pattern rule 이 항상 실행되게 하려면 FORCE 타겟 방법을 사용해야 합니다.
Subroutine 으로서의 타겟
.PHONY 타겟을 이용하면 rule 을 일종의 subroutine 을 실행하는 용도로 활용할 수 있습니다.
그런데 여기에는 디렉토리와 관련해서 한가지 문제점이 있는데요.
다음 예제는 오브젝트 파일을 별도의 빌드 디렉토리에 저장하려고 한 것입니다.
따라서 컴파일 하기 전에 먼저 디렉토리를 생성해야 하는데요.
이때는 $(OBJS) : $(BUILD_DIR)
와 같이 의존성을 추가하면 $(OBJS)
파일들이 빌드 되기 전에
먼저 $(BUILD_DIR)
타겟이 실행되게 할 수 있습니다.
그런데 여기서 한가지 문제는 컴파일 결과로 오브젝트 파일이 빌드 디렉토리에 생성될 때마다 디렉토리의 timestamp 도 함께 update 가 된다는 것입니다. 따라서 make 실행이 완료되면 마지막 파일은 디렉토리와 timestamp 가 같게되겠지만 처음 두 파일은 디렉토리보다 old 상태가 되겠죠. 따라서 다음에 다시 make 명령을 실행하면 old 파일들을 다시 빌드를 시작합니다. 이것은 prerequisites 에 있는 파일들은 항상 target 과 timestamp 를 비교하기 때문인데요. 이와 같은 문제를 해결하기 위한 것이 order-only prerequisites 입니다.
# 테스트하려면 먼저 touch 명령으로 foo.c bar.c baz.c 파일을 생성하세요.
BUILD_DIR := BUILD
OBJS := $(addprefix $(BUILD_DIR)/,foo.o bar.o baz.o)
all : $(OBJS)
$(OBJS) : $(BUILD_DIR) # $(OBJS) 파일들을 빌드하기전에 먼저 $(BUILD_DIR) 타겟 실행
$(BUILD_DIR)/%.o : %.c
gcc -c -o $@ $<
$(BUILD_DIR) :
mkdir -p $@
############ 실행 결과 ############
sh$ make
mkdir -p BUILD # $(BUILD_DIR) 타겟 실행
gcc -c -o BUILD/foo.o foo.c # foo.o: time 47:17, BUILD: time 47:17
gcc -c -o BUILD/bar.o bar.c # bar.o: time 47:18, BUILD: time 47:18
gcc -c -o BUILD/baz.o baz.c # baz.o: time 47:19, BUILD: time 47:19
sh$ make # 빌드가 완료되었지만 foo.o bar.o 파일의 timestamp 는
gcc -c -o BUILD/foo.o foo.c # BUILD 디렉토리 보다 old 상태가 되므로 다시 빌드가 된다.
gcc -c -o BUILD/bar.o bar.c
. . . . . # gcc 는 컴파일시 타겟 파일이 존재하면 먼저 삭제한 후 다시 생성합니다.
Order-only Prerequisites
Order-only prerequisites 은 타겟 라인의 마지막에 |
문자를 추가한 후에 작성합니다.
order-only prerequisites 은 normal prerequisites 과달리 update 체킹에 사용되지 않습니다.
아래 두 번째 예제를 예로 들면 bar 가 foo 보다 최신이면 foo 의 recipe 가
실행되지만 zoo 가 foo 보다 최신이라고 해서 foo 의 recipe 가 실행되지는 않습니다.
order-only prerequisite 인 zoo 의 recipe 가 실행될 경우는 zoo 타겟 파일이 존재하지 않을 때입니다. zoo 의 recipe 가 실행되어 타겟 파일이 존재하면 이후부터는 zoo 의 recipe 는 실행되지 않습니다. 만약에 zoo 파일의 존재 여부에 상관없이 항상 실행되게 하려면 .PHONY 타겟에 등록하면 됩니다.
실행되는 순서는 먼저 normal prerequisites 이 실행이 되고 완료되면
foo 타겟이 실행되기 전에 order-only prerequisites 이 실행됩니다.
다시 말해서 prerequisites 순서대로 실행이 되는데 이것은 single thread 일 경우이고
make -j8
와 같이 병렬 실행을 하게 되면 prerequisites 들은( order-only 포함 ) 모두 동시에 실행되고
실행이 완료되면 마지막으로 foo 타겟이 실행됩니다.
위에서 발생하는 문제는 $(BUILD_DIR)
가 normal prerequisites 이라서 update 체킹을 하게 되어
발생하는 문제이므로 $(OBJS) : $(BUILD_DIR)
타겟 라인을 $(OBJS) : | $(BUILD_DIR)
로 변경한 후
다시 make 명령을 실행해보면 위와 같은 문제가 발생하지 않는 것을 알 수 있습니다.
# 먼저 BUILD 디렉토리를 삭제하고 # order-only prerequisites 실행순서
# 다음 라인으로 변경한 후 실행합니다.
$(OBJS): | $(BUILD_DIR) foo : bar | zoo
@echo fooooo
####### 실행 결과 #######
bar :
sh$ make @echo barrrr
mkdir -p BUILD
gcc -c -o BUILD/foo.o foo.c zoo :
gcc -c -o BUILD/bar.o bar.c @echo zooooo
gcc -c -o BUILD/baz.o baz.c
##### 실행 결과 #####
sh$ make
make: Nothing to be done for 'all'. barrrr
zooooo
fooooo
Multiple Target Lines
recipe 가 존재하는 rule 이 동일한 타겟 이름을 가지고 중복이 되면
warning 메시지가 출력되고 앞선 rule 이 override 됩니다.
이때 앞선 rule 의 타겟 라인 설정값은 유지가 되지만 recipe 는 실행되지 않습니다.
다음을 보면 overriding warning 메시지와 함께 두 번째 rule 에 해당하는 222 ...
recipe 가
실행되고 $^
값은 앞선 rule 의 타겟 라인 설정값인 main.h 가 포함되는 것을 볼 수 있습니다.
main.o : main.c main.h
@echo 111 $@ : $^
main.o : main.c
@echo 222 $@ : $^
.DEFAULT: ;
##### 실행 결과 #####
Makefile:5: warning: overriding recipe for target 'main.o'
Makefile:2: warning: ignoring old recipe for target 'main.o'
222 main.o : main.c main.h # 앞선 룰의 main.h 가 포함된다.
rule 의 recipe 를 하나만 남기면 정상적으로 실행됩니다.
main.o : main.c main.h
main.o : main.c
@echo 222 $@ : $^
.DEFAULT: ;
######## 실행 결과 ########
222 main.o : main.c main.h
위에서 살펴보았듯이 recipe 가 포함되는 rule 은 중복이 허용되지 않지만
타겟 라인은 여러 개를 사용할 수 있습니다.
또한 여러 개의 타겟 라인에 동일한 이름의 prerequisites 이 중복되어 나타날 수도 있는데
이때는 $^
automatic 변수의 경우는 중복이 제거되고 $+
변수는 그대로 포함됩니다.
타겟 라인에는 다음과 같이 의존성만 설정할 수 있는 것이 아니고 target-specific 변수도 설정해 사용할 수 있습니다.
- 의존성 설정 ( rule 정의에서 dependencies 와 prerequisites 은 같은 의미입니다. )
- target-specific 변수 설정
- order-only prerequisites 설정
Prerequisites 들이 합쳐지는 순서
multiple 타겟 라인에 의해 설정된 prerequisites 들이 최종적으로 하나의 룰로 합쳐질 때는 recipe 가 있는 룰의 prerequisites 이 제일 앞에 오고 나머지는 작성 순서대로 append 됩니다. 여기에 order-only prerequisites 은 포함되지 않습니다.
prog : aaa.o libxxx.a
prog : bbb.o libyyy.a ccc.o libxxx.a
prog : main.o
@echo gcc $+ -o $@
.DEFAULT: ;
###### 실행 결과 ######
sh$ make
gcc main.o aaa.o libxxx.a bbb.o libyyy.a ccc.o libxxx.a -o prog
Multiple Targets
보통 예제에는 타겟 파일이 하나로 나오는데 실제는 타겟 파일도 prerequisites 처럼 여러개를 사용할 수 있고 다음과 같이 중복도 가능합니다.
aaa.o : aaa.c aaa.h # aaa.o 에만 해당
aaa.o : CPPFLAGS := -DFOO
bbb.o : bbb.c bbb.h bar.h # bbb.o 에만 해당
bbb.o : CPPFLAGS := -DBAR
aaa.o bbb.o : zoo.h # aaa.o bbb.o 공통 부분
aaa.o bbb.o : CFLAGS := -g -Wall
aaa.o bbb.o :
@echo $@ : $^ $(CPPFLAGS) $(CFLAGS)
prog : aaa.o bbb.o
.DEFAULT: ;
.DEFAULT_GOAL := prog
############ 실행 결과 #############
aaa.o : -DFOO -g -Wall aaa.c aaa.h zoo.h
bbb.o : -DBAR -g -Wall bbb.c bbb.h bar.h zoo.h
하지만 다음과 같이 recipe 를 갖는 타겟이 중복되는 것은 안됩니다.
aaa.o : aaa.c aaa.h
command ...
bbb.o : bbb.c bbb.h bar.h
command ...
aaa.o bbb.o : zoo.h # aaa.o 타겟과 bbb.o 타겟과 중복
command ...
-------------------------
aaa.o : aaa.c
command ...
aaa.o : aaa.m # aaa.o 타겟 중복
command ...
rule 에 타겟이 여러개 일경우 make 명령 실행시 타겟을 지정하지 않으면 기본적으로 첫 번째 타겟이 사용됩니다.
foo bar zoo :
@echo target : $@
----------------------
sh$ make
target : foo
sh$ make bar
target : bar