Pattern Rules

rule 을 작성할 때 타겟 위치에는 multiple targets 이 올 수 있지만 그렇다고 prerequisites 자리에도 모든 타겟의 prerequisites 들을 위치시킬 수는 없습니다. 왜냐하면 다음과 같이 룰을 작성하면 각 타겟의 의존성으로 모든 prerequisites 들이 설정되고 foo.o 타겟을 생성할 때는 foo.c foo.h 만 사용하고 bar.o 타겟을 생성할 때는 bar.c bar.h 만 사용할 수가 없기 때문입니다.

foo.o bar.o zoo.o : foo.c foo.h bar.c bar.h zoo.c zoo.h
    gcc -c -o $@ ???

따라서 정상적으로 룰이 실행되려면 다음과 같이 각 타겟에 대한 타겟 라인을 분리해서 작성해야 합니다. 그런데 이 방법은 소스파일의 개수가 많아지면 동일한 패턴의 타겟 라인이 계속 추가되고 중복되는 결과를 갖게 됩니다. 그래서 이와 같은 경우 중복을 제거하고 간단히 하기 위해 나온 것이 % 문자를 이용하는 pattern rule 입니다.

foo.o : foo.c foo.h
bar.o : bar.c bar.h
zoo.o : zoo.c zoo.h   # 소스파일의 개수가 많아지면 동일한 패턴의 타겟 라인이 중복된다.

foo.o bar.o zoo.o :
    @echo gcc -c -o $@ $<

.DEFAULT: ;

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

sh$ make foo.o
gcc -c -o foo.o foo.c

sh$ make bar.o
gcc -c -o bar.o bar.c

sh$ make zoo.o
gcc -c -o zoo.o zoo.c

다음은 위와 동일하게 동작하는 rule 을 pattern rule 을 이용해 작성한 것인데요. pattern rule 은 소스파일의 개수가 아무리 많아져도 타겟 라인을 추가할 필요가 없습니다.

MAKEFLAGS += -rR     # builtin rule 과 변수를 disable 하기 위한것

%.o : %.c %.h
    @echo gcc -c -o $@ $<

%.c %.h : ;   # 테스트를 위한 것으로 explicit rule 에서 '.DEFAULT: ;' 와 같은 역할을 합니다.

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

sh$ make foo.o                # 동일하게 동작한다.
gcc -c -o foo.o foo.c

sh$ make bar.o
gcc -c -o bar.o bar.c

sh$ make zoo.o
gcc -c -o zoo.o zoo.c
explicit rule 과 implicit rule

rule 을 작성할 때 첫 번째와 같이 타겟에 직접 파일명을 사용하는 것을 explicit rule 이라고 하고, 두 번째와 같이 pattern 이나 suffix 를 이용해 작성하는 것을 implicit rule 이라고 합니다.

Target-specific 변수도 사용 가능

pattern rule 에서도 동일하게 target-specific 변수를 사용할 수 있습니다. 다음을 보면 global 영역에 CFLAGS 변수가 설정되어 있지만 %.o 타겟에서 설정한 값이 사용되는 것을 볼 수 있습니다.

MAKEFLAGS += -rR

CFLAGS := -g
%.o : CFLAGS := -O2
%.o : %.c
    @echo $@ : $^ : $(CFLAGS)

%.c : ;

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

sh$ make foo.o
foo.o : foo.c : -O2

Pattern rule 과 explicit 타겟 라인을 같이 사용

pattern rule 을 사용할 때 explicit 타겟 라인을 이용하면 특정 타겟에 대해서만 설정을 변경할 수 있습니다. 다음을 보면 foo.o 타겟이 pattern rule 과 매칭이 되어 실행될 때 explicit 타겟 라인에서 설정한 값이 적용되는 것을 볼 수 있습니다.

MAKEFLAGS += -rR

%.o : CFLAGS := -g
%.o : %.c
    @echo $@ : $^ : $(CFLAGS)

foo.o: foo.c foo.h bar.h zoo.h
foo.o: CFLAGS := -O2

.DEFAULT: ;
%.c : ;

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

sh$ make bar.o
bar.o : bar.c : -g

sh$ make foo.o
foo.o : foo.c foo.h bar.h zoo.h : -O2

Rule 이 선택되는 우선순위

makefile 내에 explicit rule 과 implicit rule 이 동시에 존재하면 매칭이 중복될 수 있는데 이때는 explicit rule 이 우선순위가 높습니다.

%.o :
    @echo 11111

foo.o :
    @echo 22222

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

sh$ make           # %.o 룰이 제일 위에 위치하지만 explicit 룰이 실행된다.
22222

sh$ make foo.o     # foo.o 는 %.o 룰과도 매칭이 되지만 explicit 룰이 우선순위가 높다.
22222

sh$ make bar.o     # bar.o 타겟의 explicit 룰은 존재하지 않으므로 %.o 룰이 실행된다.
11111

explicit rule 과달리 pattern rule 은 recipe 를 갖는 룰의 타겟 이름이 중복돼도 됩니다. 따라서 다음과 같이 룰을 정의해 사용하는 것도 가능합니다. 첫 번째 예제의 경우 컴파일 하는데 .c 소스파일이 존재하면 11111 룰이 실행되고 .m 소스파일이 존재하면 22222 룰이 실행됩니다. 두 번째와 같이 작성하는 것은 의미가 없으므로 앞선 룰이 override 되는데 이때도 warning 은 발생하지 않습니다.

%.o : %.c                               %.o : %.c
    @echo 11111                             @echo 11111

%.o : %.m                               %.o : %.c
    @echo 22222                             @echo 22222

다음과 같은 식으로 작성하는 것도 가능한데 이때는 작성 순서가 중요합니다. 왼쪽 예제를 예로 들면 현재 .c , .h 파일이 모두 존재하면 첫 번째 룰이 선택되고, .c 파일만 존재하면 두 번째 룰이, 모두 존재하지 않으면 마지막 룰이 선택되어 실행됩니다. 하지만 오른쪽과 같이 작성하면 prerequisites 과의 매칭에 상관없이 항상 첫 번째 룰이 선택되게 됩니다.

MAKEFLAGS += -rR                         MAKEFLAGS += -rR

%.o : %.c %.h                            %.o :
    @echo 11111                              @echo 11111

%.o : %.c                                %.o : %.c
    @echo 22222                              @echo 22222

%.o :                                    %.o : %.c %.h
    @echo 33333                              @echo 33333

위 첫번째 예제를 가지고 다음과 같이 테스트를 해보면 foo.c , foo.h 파일이 생성됨에 따라 각기 다른 rule 이 선택되어 실행되는 것을 볼 수 있습니다.

sh$ make foo.o         # %.o 룰이 선택된다.
33333

sh$ touch foo.c        # foo.c 파일 생성

sh$ make foo.o         # %.o : %.c 룰이 선택된다.
22222

sh$ touch foo.h        # foo.h 파일도 생성

sh$ make foo.o         # %.o : %.c %.h 룰이 선택된다.
11111

매칭되는 룰이 여러개 존재하면 stem 이 짧은 룰이 선택된다.

% 패턴 문자와 매칭되는 부분을 stem 이라고 하는데 매칭되는 pattern rule 이 여러개 존재할 경우는 stem 이 짧은 룰이 선택됩니다. 다음 예제를 보면 명령 라인에서 설정한 foo/bar/zoo.o 타겟은 세 개의 룰에 모두 매칭이 되지만 마지막 룰의 stem 이 가장 짧기 때문에 선택되게 됩니다.

MAKEFLAGS += -rR

%.o : %.c                      # stem : foo/bar/zoo
    @echo 11111 : $*           # '$*' 는 stem 값을 나타내는 automatic 변수

foo/%.o : foo/%.c              # stem : bar/zoo
    @echo 22222 : $*

foo/bar/%.o : foo/bar/%.c      # stem : zoo
    @echo 33333 : $*

%.c : ;

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

sh$ make foo/bar/zoo.o
33333 : zoo

패턴이 매칭되고 stem 이 정의되는 방식

/ 문자는 디렉토리를 나타내는 문자이므로 패턴에 / 문자가 없으면 그것은 단순 파일명만 가리키게 됩니다. 따라서 아래 왼쪽 예제의 두 번째 타겟인 foo.bar/zoo 의경우 매칭시 디렉토리 부분인 foo.bar/ 을 제외하고 zoo 만 가지고 매칭을 하게 됩니다. ( 그러므로 결과적으로 매칭이 안됩니다. )

패턴에 / 문자가 없을때 매칭되는 방식을 세 번째 타겟인 foo.bar/foo.zoo 를 가지고 설명하면 다음과 같습니다.

  1. 먼저 타겟에서 / 문자를 포함하는 디렉토리 부분을 제외합니다. ( foo.bar/foo.zoo -> foo.zoo )
  2. 파일명만 가지고 패턴과 매칭을 합니다. ( foo.zoo 는 foo.% 와 매칭이되고 % 는 zoo 가된다 )
  3. 매칭이 되면 % 에 해당하는 부분에 1 번에서 제외했던 디렉토리를 붙입니다. ( foo.bar/zoo )
# 패턴에 '/' 문자가 없는 경우                               # 패턴에 '/' 문자가 있는 경우

foo.% :                                                  foo/% :    
    @echo $@ : $*                                            @echo $@ : $*

#######  실행 결과  #######                                #######  실행 결과  ######
sh$ make foo.bar                                         sh$ make foo/bar
foo.bar : bar                                            foo/bar : bar

sh$ make foo.bar/zoo                                     sh$ make foo/bar/zoo
make: *** No rule to make target 'foo.bar/zoo'.  Stop.   foo/bar/zoo : bar/zoo

sh$ make foo.bar/foo.zoo
foo.bar/foo.zoo : foo.bar/zoo

% 패턴 문자가 제일 왼쪽에 위치할 경우는 예외로 이때는 디렉토리 경로도 매칭에 포함됩니다.

%zoo :
    @echo $@ : $*

#######  실행 결과  #######
sh$ make bar.zoo
bar.zoo : bar.

sh$ make bar/zoo
bar/zoo : bar/

sh$ make foo/bar/zoo
foo/bar/zoo : foo/bar/

% 패턴 문자는 변수, 함수 처리가 완료된 후에 처리된다.

타겟 라인에서 % 문자는 먼저 변수, 함수 처리가 완료된 후에 처리됩니다. 따라서 % 문자가 대체될 값을 함수에서 사용할 수는 없습니다. 다음 예제에서 $(notdir %) 함수의 경우 % 문자가 대체될 값의 notdir 가 반환되지 않습니다. 단순히 % 문자 자체의 notdir 값이 반환됩니다.

obj/%.o : src/%.c  | $(notdir %)
    @echo $@ : $^ : $|

Match-anything rules

타겟으로 % 문자를 사용하면 어떻게 될까요? 이때는 어떤 타겟도 다 매칭이 되어 recipe 가 실행됩니다.

MAKEFLAGS += -rR

% :
    @echo % : $@

#####  실행 결과  #####
sh$ make foo
% : foo

sh$ make foo/bar
% : foo/bar

sh$ make foo/bar/zoo.o
% : foo/bar/zoo.o

match-anything 룰은 기본적으로 explicit 타겟에 대해서만 매칭이 되고 패턴은 매칭되지 않습니다. 다음을 보면 bar 타겟이 explicit 일경우 (1) 는 % 룰과 매칭이 되어 recipe 가 실행되지만 %.bar 와같은 패턴일 경우 (2) 는 매칭이 되지 않아 종료되는 것을 볼 수 있습니다. 이때는 (3) 과같이 직접 %.bar 패턴 룰을 작성해야 됩니다.

foo : bar            (1)       %.foo : %.bar        (2)         %.foo : %.bar        (3)
    @echo target : $@              @echo target : $@                @echo target : $@

% :                            % :                              %.bar :
    @echo % : $@                   @echo % : $@                     @echo %.bar : $@

#####  실행 결과  #####          #####  실행 결과  #####            #####  실행 결과  #####
sh$ make foo                   sh$ make a.foo                   sh$ make a.foo 
% : bar                        make: *** No rule to make        %.bar : a.bar
target : foo                   target 'a.foo'.  Stop.           target : a.foo

Terminal 과 non-terminal

Pattern rule 이 double-colon 을 이용해서 작성되면 그것은 terminal 이됩니다. terminal 룰은 더이상 prerequisites 에 있는 패턴 룰을 실행하지 않습니다. 하지만 만약에 prerequisites 에 있는 패턴과 매칭되는 파일이 존재하면 그것은 explicit 파일이 되므로 룰이 실행됩니다. : 를 이용해 작성하는 non-terminal 은 일반 룰과 동일하게 매칭되는 룰이 존재하면 실행됩니다.

pattern rule 에서 사용되는 double-colon 은 double-colon rules 과 전혀 다른 것입니다.

# terminal                                     # non-terminal
%.foo :: %.bar                                 %.foo : %.bar
    @echo 11111                                    @echo 11111

%.bar :                                        %.bar :
    @echo 22222                                    @echo 22222

#####  실행 결과  #####                          #####  실행 결과  #####
sh$ make a.foo                                 sh$ make a.foo 
make: *** No rule to make                      22222
target 'a.foo'.  Stop.                         11111

sh$ touch a.bar    # %.bar 에 매칭되는 파일 생성

sh$ make a.foo     # 매칭되는 파일이 존재하면
11111              # '%.foo :: %.bar' 룰이 실행된다.

Chains of implicit rules

rule 이 서로 의존성을 가지고 chain 처럼 연결되어 있을 경우 explicit rule 과 implicit rule 이 처리되는 방식이 다릅니다. explicit 룰의 경우는 말 그대로 생성해야될 파일을 explicit 하게 지정하는 것이므로 파일이 존재하지 않을 경우 생성이 강요되지만 패턴을 사용하는 implicit 룰의 경우는 explicit 하게 지정되지 않은 파일은 자동으로 삭제됩니다.

여기서 explicit 하게 지정되는 파일에 해당하는 것은 rule 의 타겟 라인에서 지정하는 파일명 외에 명령 라인에서 타겟으로 지정되는 경우, 패턴과 매칭되는 파일이 현재 디렉토리에 존재하는 경우도 해당됩니다.

다음 예제를 보면 explicit 룰의 경우는 make foo.o 명령 실행 후에 foo.c, foo.o, foo.y 파일이 모두 존재하지만 implicit 룰의 경우는 명령 라인에서 지정한 이름인 foo.o 파일 외에는 make 에의해 자동으로 삭제되는 것을 볼 수 있습니다. 이렇게 빌드 중간에 임시적으로 생성되었다 삭제되는 파일을 intermediate 파일이라고 합니다.

implicit 룰의 경우 chain 중간에 존재하지 않는 파일이 intermediate 파일이면 다음에 make 명령 실행시 다시 생성하지 않습니다. 오른쪽 예제에서 마지막으로 실행한 명령을 보면 현재 foo.c foo.y 파일이 존재하지 않는데도 불구하고 룰을 실행하지 않는 것을 볼 수 있습니다. 만약에 explicit rule 이라면 무조건 다시 생성하겠죠.

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

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

foo.y:                                     %.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
                                           rm foo.c foo.y   # <--- make 에의해 자동 삭제
sh$ ls                                     
foo.c  foo.o  foo.y  Makefile              sh$ ls
                                           foo.o  Makefile
sh$ make foo.o                                               # foo.c foo.y 파일이 없지만
make: 'foo.o' is up to date.               sh$ make foo.o    # 다시 생성하지 않는다.
                                           make: 'foo.o' is up to date.

이와 같은 chain of rules 과 관련해서 make 에서 제공하는 특수 타겟들이 있습니다. .PRECIOUS: 또는 .SECONDARY: 타겟의 prerequisites 으로 파일을 등록하면 intermediate 파일이 자동으로 삭제되는 것을 방지할 수 있습니다. .INTERMEDIATE: 타겟은 반대로 explicit 지정된 파일도 intermediate 파일처럼 삭제되게 할 수 있습니다. 좀 더 자세한 내용은 Special Targets 메뉴를 참조하세요.

Built-in rules and variables

make 은 사용자 편의를 위해서 프로젝트에서 자주사용되는 rule 들과 변수설정을 기본적으로 내장하여 제공하고 있습니다. 파일명을 직접 사용하는 explicit rule 로는 물론 안되고 suffix rule 이나 pattern rule 같은 implicit rule 을 이용해 제공합니다. 따라서 이것을 이용하면 makefile 없이도 컴파일이 가능한 일이 발생합니다. 만약에 사용자가 동일한 룰을 재정의하면 사용자 룰이 사용됩니다.

make -p ( print internal database ) 명령을 실행해보면 builtin rules 과 변수들을 볼 수 있습니다.

sh$ echo "int main() {}" > hello.c

sh$ make hello.o                # makefile 이 없는데도 컴파일이 된다.
cc    -c -o hello.o hello.c

sh$ make hello
cc   hello.o   -o hello

builtin rule 을 정의할 때 기본적으로 사용되는 변수 이름들이 있는데 이것을 활용하면 옵션도 전달할 수 있습니다. 다음 예제에서 사용된 CFLAGS 변수는 C 컴파일러에 옵션을 전달할 때 사용되고 ( C++ 일 경우는 CXXFLAGS ), CPPFLAGS 변수는 전처리기 관련 옵션 ( C, C++ 공통 ), LDFLAGS 은 ld 링커 관련 옵션( -L/nonstandard/dir ), LDLIBS 는 링커에 전달되는 라이브러리( -lpthread ) 관련 설정을할 때 사용됩니다. 앞에 EXTRA_ 가 붙은 변수들은 가령 명령 라인에서 CFLAGS 변수를 설정하면 makefile 에서 설정한 CFLAGS 값이 override 되어 사용할 수 없게 되는데 이때 EXTRA_CFLAGS 변수를 사용하면 기존 CFLAGS 값을 유지하면서 추가로 CFLAGS 값을 설정할 수 있습니다.

보통 변수를 사용할 때는 전처리기 옵션을 포함하여 모두 CFLAGS 에 작성하고 C, C++ 공통으로 옵션을 설정할 때 CPPFLAGS 변수를 사용합니다. [ 관련페이지 참조 : Variables Used by Implicit Rules ]

# builtin 변수들이 '=' recursive 연산자를 사용하여 정의되어 있다.
sh$ make -p | grep 'COMPILE.c '
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
----------------------------------------------------------------

# 따라서 다음과 같이 명령 라인에서 값을 전달할 수 있습니다.
sh$ make hello.o CFLAGS:="-g -Wall" CPPFLAGS:=-DDEBUG
cc -g -Wall -DDEBUG  -c -o hello.o hello.c

sh$ make hello LDFLAGS=-L/usr/local/lib LDLIBS=-lpthread
cc -L/usr/local/lib  hello.o  -lpthread -o hello

LDLIBS 변수에 설정하는 라이브러리는 순서에 영향을 받기 때문에 LDFLAGS 변수에 함께 설정하면 안됩니다.

sh$ cat hello.c
#include <pthread.h>
int main() {
    pthread_join(1,NULL);
}

# CFLAGS 변수에 전처리기 옵션을 함께 설정해도 된다.
sh$ make hello.o "CFLAGS := -g -Wall -DDEBUG"
cc -g -Wall -DDEBUG   -c -o hello.o hello.c

# 링커에 전달되는 라이브러리는 순서에 영향을 받으므로 LDLIBS 변수에 설정해야 한다.
sh$ make hello "LDFLAGS := -L/usr/local/lib -lpthread"
cc -L/usr/local/lib -lpthread  hello.o   -o hello
/usr/bin/ld: hello.o: in function 'main':
/home/mug896/tmp/make/vpath/src/hello.c:3: undefined reference to 'pthread_join'
collect2: error: ld returned 1 exit status
make: *** [<builtin>: hello] Error 1

# LDLIBS 변수를 사용하여 -lpthread 라이브러리가 hello.o 오브젝트 파일 뒤로 이동
sh$ make hello LDFLAGS:=-L/usr/local/lib LDLIBS:=-lpthread
cc -L/usr/local/lib  hello.o  -lpthread -o hello

builtin 룰과 변수들은 make 에 익숙한 사용자에게는 편리한 기능이 될수있지만 make 을 학습하는 사용자에게는 make 의 기본기능을 익히는데 도움이 되지 않습니다 ( 나도 모르게 builtin 룰에 의해 처리 되므로 ). 또한 매칭되는 룰이 없을 경우 모든 implicit rule 을 검색하므로 처리 속도 또한 느려집니다.

sh$ touch Makefile    # makefile 생성

# '-d' 옵션은 make 의 debugging 정보를 출력합니다.
sh$ make -d | wc -l                         sh$ make -d -rR | wc -l
419                                         14

따라서 다음과같이 builtin 룰과 변수를 disable 하고 직접 필요한 룰과 변수를 설정해 사용하는것이 좋습니다.

# 명령 라인에서 설정
# -r, --no-builtin-rules                         # makefile 내에서 설정
# -R, --no-builtin-variables                     
sh$ make -rR                                     MAKEFLAGS += -rR

Suffix Rules

suffix rule 은 make 초기에 implicit rule 을 작성하던 방법인데 지금은 보다 기능이 뛰어난 pattern rule 이 사용되므로 구버전 호환성을 위해서만 존재하는 기능입니다. suffix rule 을 정의하고자 할때는 먼저 .SUFFIXES: 타겟에 확장자를 등록해야 합니다. 사용하려는 확장자가 이미 디폴트로 설정되어 있으면 다시 등록할 필요는 없습니다.

make 실행시 디폴트로 설정되는 suffixes 값들은 SUFFIXES 변수를 통해 알아볼 수 있습니다.

suffix rule 은 확장자 2 개를 사용하는 double-suffix rule 과 1 개를 사용하는 single-suffix rule 이 있는데 모두 공통으로 prerequisites 은 사용되지 않습니다. 만약에 prerequisites 을 설정하면 suffix rule 로 기능하지 않습니다. C 소스 파일을 컴파일 하는 suffix rule 을 동일한 기능을 하는 pattern rule 과 함께 작성해 보면 다음과 같습니다.

double-suffix rules

double-suffix rule 은 확장자를 공백없이 붙이고 앞이 source 확장자가 되고 뒤가 target 확장자가 됩니다.


.source.target :

-------------------
.c.o :
    $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

# 위와 같은 기능을 하는 pattern rule
%.o : %.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

single-suffix rules


.source :

-----------------
.c :
    $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@

# 위와 같은 기능을 하는 pattern rule
% : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@

테스트할 때 suffix rule 은 매칭되는 소스파일이 존재해야 룰이 실행됩니다.