Special Targets

rule 을 정의할 때 사용하는 target : prerequisites 형식을 사용하는 타겟으로 특별한 의미를 가집니다. 타겟 중에는 prerequisites 을 사용하는 타겟이 있고 사용하지 않는 타겟이 있습니다.

.DEFAULT:

prerequisites 을 빌드 하는데 필요한 rule 을 찾지 못했을 경우 default 로 실행할 rule 을 설정합니다.

.DEFAULT 타겟의 prerequisites 은 사용되지 않습니다.

prog: foo bar      # prerequisites 에 해당하는 foo, bar 를 만드는데 필요한 rule 이 없다.
    @echo target: $@

.DEFAULT:          # default rule 이 실행된다.
    @echo default: $@

######  실행 결과  #######

default: foo       # '$@' 값은 각 타겟으로 설정됩니다.
default: bar
target: prog

다음은 match-anything rule 인 %.DEFAULT 룰을 비교한 것인데 % 룰의 경우에는 foo 타겟을 빌드 하는데 필요한 recipe 가 없을 경우에도 실행되지만 .DEFAULT 룰의 경우는 prerequisites 에 해당하는 bar 타겟에 대해서만 실행되는 것을 볼 수 있습니다.

foo : bar                                      foo : bar

% :                                            .DEFAULT :
    @echo % : $@                                   @echo default : $@

#### 실행 결과 ####                               #### 실행 결과 ####

% : bar                                         default : bar
% : foo  # foo 타겟에 대해서도 실행된다.

.DELETE_ON_ERROR:

이것은 .PRECIOUS 타겟과 비슷하지만 차이점은 signal 에의해 비정상 종료할 때가 아닌 recipe 실행 중에 shell 명령이 오류로 종료하여 make 실행이 중단될 경우를 말합니다. 원래는 이 경우에도 타겟 파일이 자동으로 삭제되어야 하지만 역사적인 이유로 직접 설정해야 한다고 합니다.( 다시 말해서 default 는 삭제가 되지 않습니다. )

.DELETE_ON_ERROR 타겟을 Makefile 에 추가하면 recipe 실행 중에 오류로 make 이 종료될때 타겟이 update 되었는지 체킹 한 후 update 되었을 경우 해당 타겟 파일을 삭제합니다.

.DELETE_ON_ERROR 타겟의 prerequisites 은 사용되지 않습니다.

.DELETE_ON_ERROR:

foo :
    touch $@
    false
    @echo target: $@

##########  실행 결과  ##########

touch foo                              # 타겟 파일을 update 
false
make: *** [Makefile:7: foo] Error 1
make: *** Deleting file 'foo'          <--- 오류로 종료시 타겟파일이 update 되었으면 삭제한다.

.EXPORT_ALL_VARIABLES:

모든 변수들을 export 합니다. 기본적으로 변수명이 대, 소문자, 숫자, _ 로 구성된 변수만 export 됩니다.
원래 old make 에서는 이것이 디폴트 동작 방식이었다고 합니다. export 지시자를 단독으로 사용하는 것도 동일하게 동작하지만 old make 에서는 오류가 나므로 이 타겟을 사용하면 됩니다.

.IGNORE:

recipe 에서 명령이 오류로 종료하여 make 실행이 종료되는 것을 ignore 할때 사용합니다. 이 타겟을 이용하면 rule 별로 ignore 설정을 할 수 있습니다. prerequisites 을 사용하지 않으면 명령 라인에서 -i( --ignore-errors ) 옵션을 사용한 것과 같습니다.

.IGNORE: foo bar         # foo, bar 타겟에만 적용

.IGNORE:                 # 모든 타겟에 적용
----------------------------------------------------

.IGNORE: foo

foo :
    touch $@
    false
    @echo target: $@

##########  실행 결과  ##########

touch foo
false
make: [Makefile:6: foo] Error 1 (ignored)    # shell 명령의 오류가 ignored 되고
target: foo                                  # 마지막 명령도 실행된다.

.INTERMEDIATE:

실행 중에 implicit rule 에의해 생성되는 중간 파일을 intermediate file 이라고 합니다. 다음을 보면 hello.y 로부터 hello.c 가 생성되고 hello.c 로부터 hello.o 가 생성되고 마지막으로 hello.o 로부터 hello 가 생성됩니다. 매칭되는 hello.y 는 현재 존재하는 파일이고 hello 와 hello.o 는 rule 에 explicit 하게 정의되어 있으므로 intermediate file 이 되지 않지만 hello.c 같은 경우는 중간에 implicit rule 에의해 생성되는 intermediate file 이 됩니다. 기본적으로 intermediate file 은 make 이 종료될 때 자동으로 삭제가 됩니다.

.INTERMEDIATE 타겟의 prerequisites 으로 패턴은 사용할 수 없습니다.

sh$ touch hello.y           # 먼저 hello.y 파일을 생성
-------------------------------------------------------

%.c : %.y
    @echo making $@ from $<
    @touch $@

%.o : %.c
    @echo making $@ from $<
    @touch $@

hello : hello.o
    @echo making $@ from $<
    @touch $@

#########  실행 결과  #########

sh$ make
making hello.c from hello.y
making hello.o from hello.c
making hello from hello.o
rm hello.c                 <---- intermediate 파일인 hello.c 는 자동 삭제된다.

hello.o 같이 explicit 하게 지정된 파일이라도 .INTERMEDIATE 타겟에 등록하면 자동으로 삭제됩니다.

# 위 Makefile 에 다음 라인을 추가하고 실행
.INTERMEDIATE: hello.o

#########  실행 결과  #########

sh$ make
making hello.c from hello.y
making hello.o from hello.c
making hello from hello.o
rm hello.o hello.c        <---- hello.o 파일도 자동으로 삭제된다.

.LOW_RESOLUTION_TIME:

stat shell 명령을 이용하면 특정 파일의 Modify timestamp 정보를 볼 수 있습니다. make 은 이정보를 이용하여 파일들을 비교한 후에 타겟 파일을 update 할지 판단합니다. 이 정보는 보통 초 단위 이하까지 표시되는 high resolution time 이 사용되는데요. 그런데 특정 시스템에서 사용되는 명령 중에는 시간을 설정할때 초 단위까지만 사용하는 low resolution time 을 사용한다고 합니다. 그러면 파일을 비교할때 초 단위 이하 값이 잘려나가서 실제는 최신 상태임에도 불구하고 약간 old 상태가 되는데요. 이와 같은 문제가 발생할 경우 해당 파일을 .LOW_RESOLUTION_TIME 타겟에 등록하면 초 단위 비교를 하게 되어 문제를 해결할 수 있습니다.

archive 파일 멤버의 경우는 항상 low resolution time 을 사용하므로 따로 이타겟에 등록할 필요는 없습니다.

.LOW_RESOLUTION_TIME: dst
dst: src
    cp -p src dst

.NOTPARALLEL:

이 타겟이 설정되어 있으면 make 명령 라인에서 -j 옵션을 사용해도 Makefile 이 serially 실행됩니다. 이것은 타겟이 설정된 해당 Makefile 단위로 적용되는 것으로 sub-make 에는 영향을 주지 않습니다.

.NOTPARALLEL 타겟의 prerequisites 은 사용되지 않습니다.

.ONESHELL:

recipe 에서는 명령이 실행될 때 라인 단위로 shell 프로세스가 생성되어 실행됩니다. .ONESHELL 타겟을 설정하면 recipe 에있는 모든 명령들이 하나의 shell 프로세스에서 실행되고 here document 같은 multi-line 명령도 사용할 수 있게됩니다. 이 타겟은 룰 별로는 설정할 수 없고 한번 설정하면 전체 makefile 에 적용됩니다. @, -, + prefix 문자를 사용할 경우는 첫 라인에 한번만 위치시키면 됩니다.

.ONESHELL 타겟의 prerequisites 은 사용되지 않습니다.

foo : bar                                     foo : bar
    @echo $@ shell PID: $$$$                      @echo $@ shell PID: $$$$
    @echo $@ shell PID: $$$$                      echo $@ shell PID: $$$$

bar :                                         .ONESHELL:
    @echo $@ shell PID: $$$$                  
    @echo $@ shell PID: $$$$                  bar :   # heredoc 을 사용할 수 있다.
                                                  @cat <<END
                                                  $@ shell PID: $$$$ 
                                                  $@ shell PID: $$$$
                                                  END

#########  실행 결과  #########                 #########  실행 결과  #########
bar shell PID: 27442                          bar shell PID: 27551
bar shell PID: 27443  각기 다른 PID             bar shell PID: 27551  
foo shell PID: 27444                          foo shell PID: 27552  # rule 별로 동일한 PID
foo shell PID: 27445                          foo shell PID: 27552

.PHONY:

phony 는 사전에서 찾아보면 가짜라는 뜻을 가지고 있는요. 말 그대로 파일이 아닌 가짜 타겟을 지정하는데 사용됩니다. .PHONY 타겟으로 지정을 하면 update 체킹도 하지 않고 타겟 파일이 존재하던 안하던 상관없이 항상 rule 이 실행되게 됩니다.

.PHONY 타겟의 prerequisites 으로 pattern 은 사용할 수 없습니다.
.PHONY 타겟으로 지정하더라도 makefile 실행시 같은 rule 이 중복 실행되지는 않습니다.

.PHONY: all foo bar   # all, foo, bar 는 가짜 타겟으로 항상 실행된다.

all : bar foo         

foo : bar             # phony 타겟으로 지정하더라도 bar 가 두번 실행되지는 않는다.
    @echo fooooo

bar :
    @echo barrrr

####  실행 결과  ####

barrrr
fooooo

.PRECIOUS:

recipe 가 실행 중에 signal 에의해 비정상 종료될 경우 ( 예를 들면 ctrl-c 에의해 ) make 은 먼저 타겟 파일이 update 되었는지 체킹 한 후에 update 된 상태라면 해당 타겟 파일을 삭제합니다. 이것이 필요한 이유는 만약에 빌드 중간에 부분적으로 타겟 파일이 수정된 상태에서 종료가 돼버리면 해당 타겟 파일은 사용할 수 없는 상태임에도 불구하고 최신으로 update 된 상태가 되므로 나중에 다시 make 을 실행할 때 해당 룰이 실행되지 않게 되기 때문입니다. .PRECIOUS: 타겟에 등록하면 이와 같은 signal 에의한 삭제와 implicit rule 에서 intermediate 파일의 삭제 처리가 disable 됩니다.

.PRECIOUS 타겟의 prerequisites 으로 패턴을 사용할 수 있습니다.

foo :
    touch $@
    sleep 10
    @echo target: $@

######  실행 결과  ######

touch foo                              # 타겟 파일을 update
sleep 10                               # sleep 상태에서 ctrl-c 로 종료
^Cmake: *** Deleting file 'foo'        # 타겟 파일이 update 된 상태이므로 삭제된다.
make: *** [Makefile:5: foo] Interrupt
----------------------------------------------------------------

# 이번에는 Makefile 에 다음을 추가하고 실행
.PRECIOUS: foo 

######  실행 결과  ######

touch foo
sleep 10
^Cmake: *** [Makefile:6: foo] Interrupt   # 타겟 파일이 삭제되지 않는다.

.PRECIOUS 의 prerequisites 으로 패턴을 사용할 수 있습니다.

.PRECIOUS: %.o        # %.o 패턴을 사용

%.o :
    touch $@
    sleep 10
    @echo target: $@

######  실행 결과  ######

sh$ make foo.o
touch foo.o
sleep 10
^Cmake: *** [Makefile:5: foo.o] Interrupt

.SECONDARY:

explicit rule 에서 파일을 .SECONDARY 로 등록하면 implicit rule 에서 intermediate 파일처럼 동작합니다. 다시 말해서 chain 중간에 파일이 없다고 해서 새로 생성하지 않습니다. 다음 왼쪽의 일반적인 경우는 foo.c 파일을 삭제하면 foo.c 와 foo.o 가 새로 생성되지만 오른쪽 예제에서처럼 foo.c 를 .SECONDARY 로 등록하면 새로 생성되지 않습니다.

.SECONDARY 타겟의 prerequisites 으로 패턴은 사용할 수 없습니다.

                                          .SECONDARY: foo.c   # foo.c 를 SECONDARY 로 등록

foo.o: foo.c                              foo.o: foo.c
    @echo ">>> $@ from $<"                    @echo ">>> $@ from $<"
    touch $@                                  touch $@

foo.c: foo.y                              foo.c: foo.y
    @echo ">>> $@ from $<"                    @echo ">>> $@ from $<"
    touch $@                                  touch $@

foo.y:                                    foo.y:
    @echo ">>> $@"                            @echo ">>> $@"
    touch $@                                  touch $@

#######  실행 결과  #######                 #######  실행 결과  #######
sh$ make foo.o                            sh$ make foo.o        
>>> foo.y                                 >>> foo.y
touch foo.y                               touch foo.y
>>> foo.c from foo.y                      >>> foo.c from foo.y
touch foo.c                               touch foo.c
>>> foo.o from foo.c                      >>> foo.o from foo.c
touch foo.o                               touch foo.o

sh$ rm -f foo.c                           sh$ rm -f foo.c

sh$ make foo.o                            sh$ make foo.o 
>>> foo.c from foo.y                      make: 'foo.o' is up to date.
touch foo.c                               # foo.c 는 secondary 이므로
>>> foo.o from foo.c                      # foo.o foo.c 가 새로 생성되지 않는다.
touch foo.o

또한 .SECONDARY 타겟은 implicit rule 에서 intermediate file 이 자동으로 삭제되는 것을 방지합니다.

# intermediate 파일인 hello.c 를 SECONDARY 로 등록
.SECONDARY: hello.c

%.c : %.y
    @echo making $@ from $<
    @touch $@

%.o : %.c
    @echo making $@ from $<
    @touch $@

hello : hello.o
    @echo making $@ from $<
    @touch $@

#########  실행 결과  #########

sh$ touch hello.y

sh$ make
making hello.c from hello.y
making hello.o from hello.c
making hello from hello.o      <--- intermediate 파일인 hello.c 가 자동으로 삭제되지 않는다

.SECONDEXPANSION:

이 타겟이 설정된 위치 이후로 존재하는 prerequisites 들은 두 번 확장됩니다. ( targets 은 포함되지 않습니다. ) .SECONDEXPANSION 이 갖는 기능은 두 가지 입니다.

첫째는 타겟 라인은 recipe 가 실행되기전 global 영역에서 처리되므로 기본적으로 prerequisites 에서 automatic 변수를 사용할 수 없는데요. 하지만 .SECONDEXPANSION 을 이용하면 가능합니다.

.SECONDEXPANSION 타겟의 prerequisites 은 사용되지 않습니다.

main_SRCS := main.c try.c test.c
lib_SRCS := lib.c api.c

.SECONDEXPANSION:

# 1. $$(patsubst %.c,%.o,$$($$@_SRCS)) --> $(patsubst %.c,%.o,$($@_SRCS)) 
# 2. $(patsubst %.c,%.o,$($@_SRCS)) --> main.o try.o test.o 
main lib: $$(patsubst %.c,%.o,$$($$@_SRCS))
    @echo $^

.DEFAULT: ;
###########  실행 결과  ###########

sh$ make main
main.o try.o test.o

sh$ make lib
lib.o api.o

둘째는 타겟 라인은 global 영역에서 변수들과 마찬가지로 작성 순서대로 처리됩니다. 그러므로 해당 타겟 라인 이전에 정의된 변수는 사용할 수 있지만 이후에 정의된 변수는 prerequisites 에 사용할 수 없습니다. 하지만 .SECONDEXPANSION 을 이용하면 global 영역에서 마지막으로 설정된 변수값을 사용할 수 있습니다.

# AA 변수 사용가능             # AA 변수 사용불가             # AA 변수 사용가능
                                                         .SECONDEXPANSION:
AA := foo bar zoo            main : $(AA)                main : $$(AA)    # $$(AA)
                                 @echo $@ : $^               @echo $@ : $^
main : $(AA)                                             
    @echo $@ : $^            AA := foo bar zoo           AA := foo bar zoo 

.DEFAULT: ;                  .DEFAULT: ;                 .DEFAULT: ;
#### 실행 결과 ####            #### 실행 결과 ####           #### 실행 결과 ####

main : foo bar zoo           main :                      main : foo bar zoo

.SILENT:

recipe 에서 명령이 실행되기 전에 명령문이 출력되는 것을 disable 할때 사용합니다. 이 타겟을 이용하면 rule 별로 silent 설정을 할 수 있습니다. prerequisites 을 사용하지 않으면 명령 라인에서 -s( --silent ) 옵션을 사용한 것과 같습니다.

.SILENT: foo bar         # foo, bar 타겟에만 적용

.SILENT:                 # 모든 타겟에 적용

.SUFFIXES:

suffix rules 을 정의할 때 사용하고자 하는 확장자를 이 타겟에 등록해야 합니다. suffix rules 은 예전에 implicit rules 을 정의할 때 사용하던 방법으로 지금은 obsolete 으로 구버전 호환성을 위해서만 존재합니다. 현재는 implicit rules 을 정의할 때 pattern rules 이 사용됩니다.

make 에 디폴트로 등록되어 있는 suffixes 는 SUFFIXES 변수를 통해 알아볼 수 있습니다.
-r ( --no-builtin-rules ) 옵션을 사용하면 디폴트 suffixes 가 삭제됩니다.

.SUFFIXES: .md .html      # .md .html 확장자를 등록해 사용

.md.html :
    prog --in $< --out $@
---------------------------------------------------

# 다음과 같이하면 default suffixes 값이 모두 삭제됩니다.
.SUFFIXES: