logo

한국어

make 기본 문법

관리자 2014.06.08 12:49 조회 수 : 133

출처 : http://blog.daum.net/english_100/8


4. 규칙 작성하기

 

makefile 에서 규칙은 언제 어떻게 파일을 재작성할 것인지를 알려준다. 그 안에는 target이 있고 (대부분의 경우 한개의 규칙 속에는 한개의 target이 존재한다.) target에 대한 prerequisite 그리고 recipe가 있다.

여러 규칙들의 순서는 default goal을 결정할 때 외에는 중요치 않다 (default goal 이란 make에 아무것도 명기하지 않고 입력했을 때 실행될 target). default goal 은 makefile 내에서 최초로 등장하는 규칙의 타깃이 된다. 첫 규칙이 복수의 타깃을 갖으면 그중 첫 타깃이 디폴트가 된다. 여기에 두가지 예외가 있는데 첫째는 ‘.’으로 시작하는 타깃은 한개이상의 ‘/’을 포함하지 않는 이상 디폴트가 될 수 없다. 그리고 패턴 규칙으로 정의된 타깃 또한 티폴트가 될 수 없다. (패턴 규칙의 정의와 재정의 참조).

그러므로 일반적으로 첫 번째 규칙은 makefile 내의 모든 프로그램을 컴파일하도록 작성한다.


4.1 규칙의 예

 

예제 :

foo.o : foo.c defs.h    # module for twiddling the frobs

cc -c -g foo.c

이 규칙의 타깃은 foo.o이고 prerequisite은 foo.c와 defs.h 이다. recipe에는 ‘cc -c -g foo.c’라는 한개의 명령이 있다. recipe는 recipe임을 알리기 위해 탭으로 시작한다.

이 규칙은 두가지를 말해준다.

- foo.o가 철이 지났는지 안지났는지를 결정하는 방법 : 파일이 존재하지 않으면 철지난것임. foo.c나 defs.h가 더 최근것이면 철지난 것임

- foo.o를 갱신하는 방법 : 위예제에서는 cc에 의해서 이루어짐. defs.h가 recipe에 명기되지 않았지만 우리는 defs.h가 prerequisite에 존재함으로 foo.c가 defs.h를 포함하고 있을것으로 짐작할 수 있음.


4.2 규칙의 신택스

 

일반적인 규칙은 다음과 같은 형태를 갖거나 :

targets : prerequisites

recipe

...

또는 다음과 같은 형태를 갖는다.

targets : prerequisites ; recipe

recipe 

...

targets는 여백문자로 구분된 파일 이름들이다. 와일드카드 문자가 사용될 수 있으며 a(m) 형태의 이름은 기록파일 a에 들어있는 멤버 m을 의미한다 (Archive Members as Targets 참조).

recipe 행들은 탭 문자로 시작하고 그 중 첫 번째 recipe는 prerequisite 다음 행에 팁문자를 시작으로 나타내거나 또는 prerequisite과 같은 행에 세미콜론 다음에 나타낼 수 있다.

달 러 기호는 변수 참조의 시작점으로 이용된다. 하지만 target이나 prerequisite에 달러 기호를 쓰고 싶다면 달러 두개 ‘$$’를 쓰면 된다 (How to Use Variables 참조). secondary expansion을 가능하게 한 상태에서 prerequisite에 달러 기호를 쓰고 싶으면 달러 네 개 ‘$$$$’를 써야한다.

긴 행을 backslash-newline을 이용해 여러 행으로 나눌수 있지만 make에 있어 행 길이에 대한 한계가 주어져 있지 않기 때문에 필요치 않을 수도 있다.

규칙은 make에게 두가지를 알려준다 : target이 언제 철지난 고물이 되나? 갱신이 필요하다면 어떻게 수행해야 되나?

철 이 지난 고물인지 아닌지는 prerequisite 의 관점에서 구분할 수 있다. prerequisite은 공백문자로 구분된 파일 이름들이고 와일드카드나 archive member 또한 허용된다. target이 존재하지 않거나 rerequisite 중의 어떤 것 보다 오래되었으면 이는 철지난 고물이다. 타깃파일이 prerequisite를 기반으로 계산되기 때문에 prerequisite에 변화가 생기면 기존의 타깃 파일은 더 이상 유효한 것이 아니다.

어떻게 갱신할 것인가는 recipe에 의해 명기된다. 이는 쉘에 의해 실행될 한개 이상의 명령행으로 이루어지고 몇몇 부가 특징이 있다 (Writing Recipes in Rules 참조).


4.3 Prerequisites의 종류

 

GNU make가 이해하는 prerequisite의 타입은 실제로 두가지가 있는데 이는 앞에서 설명한 보통 prerequisite와 order-only prerequisite이다. 보통 prerequisite는 두가지를 설명하는데 하나는 recipe가 기동되는 순서를 내포한다는 것이다. 한 타깃의 모든 prerequisite을 위한 recipe들은 타깃 자체의 recipe가 기동되기 전에 완료된다. 둘째는 의존관계를 내포한다는 것이다. 어떤 한 prerequisite이라도 타깃보다 새것이면 그 타깃은 구식으로 간주되어 갱신되어야 한다. 만약 타깃의 prerequisite가 갱신되면 그 타깃 또한 갱신되는게 정상이다.

하지만 경우에 따라 우리는 prerequisite의 순서가 그에 연관된 규칙들의 기동 순서를 내포할 뿐 그들 규칙이 실행되었다 해도 타깃은 갱신되지 않기를 바라는 경우가 있다. 이런 경우 order-only prerequisite을 정의하게 되는데 이는 prerequisite 리스트에서 파이프 기호(|)를 이용해 기술되며 파이프기호 왼쪽에 보통 prerequisite, 오른쪽에 order-only prerequisite가 놓이게 된다.

targets : normal-prerequisites | orede-only-prerequisites

정 상 prerequisite 부분은 빈공간으로 남겨둘 수도 있다. 그리고 한 타깃에 대해 여러행의 prerequisite 들을 선언할 수도 있고 새로운 prerequisite를 뒤에 덧붙일 수도 있다. 이때 정상 prerequisite는 정상 prerequisite 리스트 뒤에 order-only prerequisite는 order-only prerequisite 리스트 뒤에 붙여 넣으면 된다. 만약에 같은 파일을 정상과 order-only에 동시에 선언하면 어찌 될까? 당연히 정상 prerequisite에 우선권이 있다.

타 깃들이 서로 다른 디렉토리에 위치하고, make가 실행되기 전에 어떤 디렉토리는 존재하지 않는 그런 예를 생각해 보자. 이 경우 우리는 타깃들이 생성되어 디렉토리에 쓰여지기 전에 그 디렉토리가 먼저 생성되기를 원한다. 하지만 파일이 변화할 때마다 디렉토리의 타임스탬프 변하고 이는 다시 모든 타깃을 재생성하게 하는 상황을 야기한다. 이런 상황을 관리하기위해 order-only prerequisites를 이용하게 된다. 디렉토리를 모든 타깃에 대한 oreder-only prerequisite으로 만들면 된다.

     OBJDIR := objdir

     OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

    

     $(OBJDIR)/%.o : %.c

             $(COMPILE.c) $(OUTPUT_OPTION) $<

    

     all: $(OBJS)

    

     $(OBJS): | $(OBJDIR)

    

     $(OBJDIR):

             mkdir $(OBJDIR)


이 예제에서 ‘.o’ 파일이 생성되기 전에 디렉토리가 존재하지 않으면 먼저 objdir 디렉토리를 생성한다. 그러나 $(OBJDIR)이 order-only로 선언되어 있으므로 이로 인해 ‘.o’ 파일을 재생성하는 일은 발생하지 않는다.


4.4 파일 이름에 와일드카드 문자 사용하기

 

하 나의 파일 이름에 와일드카드 문자를 사용함으로써 여러개의 파일을 나타낼 수가 있다. make가 사용하는 와일드카드 문자는 본쉘과 동일하게 ‘*’ ‘?’ ‘[...]’등이 있다. 예를 들어 *.c는 디렉토리내에서 이름이 ‘.c’로 끝나는 모든 파일 리스트를 나타낸다.

파 일이름 앞에 붙은 ‘~’ 문자는 아주 중요한 기능을 한다. 단독으로 쓰이거나 뒤에 슬래쉬가 붙는 경우 이는 홈디렉토리를 표시한다. 예를 들어 ~/bin은 /home/you/bin을 나타내는 것이다. 만약 ‘~’ 뒤에 문자열이 온다면 이는 그 문자열의 이름을 갖는 사용자의 홈디렉토리를 표시한다. 예를 들어 ~john/bin은 /home/john/bin으로 확장되는 것이다.

와 일드카드 펼침은 타깃이나 prerequisite에서 make에 위해 자동으로 수행되고 recipe에서는 와일드카드 펼침이 쉘에 의해 이루어진다. 다른 문장에 있어서는 와일드카드 함수로 명확히 요청할 때만 확장이 이루어진다. 와일드카드의 특성은 backslash를 이용해 없을 수 있다. 예를 들어 foo\*bar는 단순히 foo 와 * 그리고 bar로 이루어진 파일이름을 말한다.


4.4.1 와일드카드의 예


와일드카드는 recipe에서 사용가능 한데 이 때는 쉘에 의해 펼쳐진다. 예를 들어보자 :

        clean :

                rm -f *.o

prerequisite에서도 상용될 수 있는데 다음 예제에서 보면 ‘make print’에 의해서 지난번 프린트한 이후에 변화된 모든 ‘*.c’ 파일들이 프린트된다.

        print : *.c

                lpr -p $?

                touch print

이 규칙에서 print는 빈 타깃파일로 사용되었다 (빈 타깃 파일 참조). 자동변수 ‘$?’는 변화가 있는 파일만 프린트하기 위해 사용되었다 (자동 변수 참조).

와일드카드는 정의될 때 펼쳐지는 것이 아니다.

        objects = *.o

여 기서 변수값은 실제 ‘*.c’라는 문자열이다. 하지만 타깃이나 prerequisite에서 objects를 사용하게 되면 그때 펼침이 발생하게 된다. 이 값이 recipe에서 사용된다면 이는 쉘이 이 recipe를 실행시킬 때 쉘에 의해 펼침이 이루어진다. objects에 펼침상태로 값을 설정하기 위해서는  다음과 같이 사용하면 된다 (wildcard 함수 참조).

        objects := $(wildcard *.o)


4.4.2 와일드카드 사용의 위험 요소


여기 초보적인 와일드카드 사용 예를 보자. 디렉토리내의 모든 오브젝트 파일을 이용해 실행파일 foo를 만들어 내고자 하지만 의도대로 되지 않을 수도 있다.

        objects = *.o

        foo : $(objects)

                cc -o foo $(CFLAGS) $(objects)

objects의 값은 실제로 ‘*.o’ 이다. 와일드카드 펼침은 foo 규칙 내에서 이르어지며 이때 가각의 존재하고 있는 ‘.o’ 파일들이 foo의 prerequisite이 되어 필요한 경우 재컴파일을 수행한다. 하지만 모든 *.o 파일을 지워버리면 어찌될까? 그러면 와일드카드에 상응하는 파일이 존재하기 않게 되고 foo는 이상한 이름의 *.o라는 이름의 파일에 종속된다. 결국 해당하는 파일이 없으므로 make는 뭘해야되는지 몰라 오류를 발생시킬것이다. 이것은 우리가 원하는 결과가 아니다. 실제로 와일드카드를 이용해 원하는 결과를 얻을 수는 있지만 wildcard 함수와 문자열 치환 등의 복잡한 기술이 필요하다.


4.4.3 wildcard 함수


와일드카드 펼침은 규칙안에서 자동으로 이루어진다. 하지만 변수값 설정이나 함수 인자에 사용될 때는 정상적으로 이루어지지 않는다. 이런 경우에도 와일드카드 펼침이 이루어지길 바란다면 wildcard 함수를 사용하면 된다.

        $(wildcard pattern ...)

이 표현은 makefile 내 어디든 사용가능하며 주어진 패턴에 상응하는 존재하는 모든 파일들의 이름들로 대치된다.

디렉토리 내의 모든 C 소스파일의 리스트를 얻는 wildcard 함수는 다음과 같다 :

        $(wildcard *.c)

C 소스파일 리스트에 있는 .c 파일을 .o 파일로 치환하는 일을 다음과 같이 표현할 수 있다.

        $(patsubst %.c,%.o,$(wildcard *.c))

여기서 patsubst라는 새로운 함수에 대해서는 문자열 치환을 위한 함수 참조.

디렉토리 내의 모든 C 소스파일을 컴파일해서 링크하는 makefile을 다음과 같이 작성할 수 있다.

        objects := $(patsubst %.c,%.o,$(wildcard *.c))

        foo : $(objects)

                cc -o foo $(objects)

여기서 C프로그램을 컴파일 하기위해서는 암시적 규칙을 사용하므로 이를 위한 명시적 규칙은 필요치 않다.


4.5 Prerequisite을 위해 디렉토리 탐색하기

 

대형 시스템에 있어서 소스파일은 이진파일들과 분리해 다른 디렉토리에 넣는 것이 바람직하다. make의 디렉토리 탐색 특성은 prerequisite을 위해 여러 디렉토리를 자동으로 탐색하게 한다.


4.5.1 VPATH : 모든 prerequisite을 위한 탐색 경로


make 변수 VPATH는 make가 찾아 헤매야하는 모든 디렉토리 리스트를 함유하고 있다. 이런 디렉토리는 대부분 현재 디렉토리에 없는 prerequisite들이 있을것으로 간주되지만 실제로 make는 prerequisite 뿐만 아니라 타깃을 찾는데도 이 VPATH를 이용한다.

만 약 prerequisite나 타깃에 명기된 파일이 현재 디렉토리에 존재하지 않으면 make는 VPATH에 나열된 모든 디렉토리를 탐색해 그 이름의 파일을 찾아낸다. 그 들중 한곳에서 파일이 발견되면 그 파일은 정상적으로 prerequisite을 사용된다.

VPATH 값에서 디렉토리 이름은 콜론이나 공백문자로 나눠지고 디렉토리의 나열 순서는 make가 탐색해 가는 순서가 된다.

예를 들어 :

        VPATH = src:../headers

는 src와 ../headers 등 두 디렉토리를 포함하고 있고 이 순서로 make의 탐색이 이루어진다. 이 VPATH 값에 대해 다음의 규칙은

        foo.o : foo.c

현재 디렉토리에서 foo.c를 못찾았고 src 디렉토리에서 찾아냈을 경우

        foo.o : src/foo.c

와 같이 해석이 될것이다.


4.5.2 vpath 지시자


VPATH 변수와 비슷하지만 더욱 선택적인 것이 vpath 지시자이다 (소문자 주의). 이는 특정 패턴에 상응하는 파일이름을 찾아갈 경로를 명기한다. 이를 통해 어떤 디렉토리 들에서는 이런 파일이름 클래스를 찾고 다른 디렉토리들에서는 또 다른 파일이름 클래스를 찾도록 할 수 있는것이다.

vpath 지시자는 3가지 형태가 있다 :

vpath pattern directories

        pattern에 상응하는 파일이름을 위해 directories를 찾아 헤맨다.

        directories는 탐색할 디렉토리 리스트이고 콜론이나 공백문자로 구분되어 VPATH 변수와 유사하다.

vpath pattern

        pattern에 관련된 경로를 clear out 한다.

vpath

        앞에서 설정된 vpath 지시자의 탐색 경로를 모두 삭제한다.

vpath의 pattern은 ‘%’를 포함하는 문자열이다. 문자열은 찾고자하는 prerequisite 파일 이름과 일치해야하는데 여기서 ‘%’는 0개 이상의 어떤 문자들과도 상응할 수 있다 (패턴 규칙의 정의와 재정의 참조). 예를 들어 %.h는 .h로 끝나는 모든 파일에 상응된다.

prerequisite이 현재 디렉토리에 존재하지 않는 상황에서 vpath 지시자의 pattern이 그 prerequisite과 상응한다면 이 지시자에 있는 directories는 VPATH 변수에 있는 디렉토리들이 검색되었던 같은 방식으로 검색된다.

        vpath %.h ../headers

의 예는 make로 하여금 현재 디렉토리에서 파일이 발견되지 않을 경우 ../headers라는 디렉토리에서 이름이 .h로 끝나는 모든 파일을 검색하게 한다.

만 약 여러개의 vpath 패턴이 prerequisite 파일명과 일치한다면 make는 일치한 모든 vpath 지시자의 디렉토리를 검색을 수행한다. 이때 수행하는 순서는 그들이 makefile 내에 출현하는 순서가 된다. 동일 패턴에 대해 복수 지시자가 있을 경우 이들은 모두 독립적이다.

다음 예는

        vpath %.c foo

        vpath % blish

        vpath %.c bar

foo에서 .c로 끝나는 파일을 찾고 다음에 blish, 그다음에 bar에서 찾는 반면

         vpath %.c foo:bar

        vpath % blish

는 foo에서 .c로 끝나는 파일을 찾은 다음 bar, blish의 순서로 찾아간다.


4.5.3 디렉토리 탐색이 이루어지는 방식


디렉토리 탐색에 의해 prerequisite을 찾을 때 그 종류(일반적 또는 선택적)에 관계없이 파일이 위치한 디렉토리의 경로명은 make가 우리에게 보여주지 않고 버려버리는 경우도 있다.

make가 디렉토리 탐색을 통해 찾아낸 경로를 가지고 있을지 버릴지를 결정하는 알고리즘은 다음과 같다 :

1. makefile 안에 명시된 디렉토리 내에 타깃 파일이 존재하지 않을 때 디렉토리 탐색이 수행된다.

2. 디렉토리 탐색이 성공적이면 그 경로를 보유하고 파일은 잠정적으로 타깃으로 저장한다.

3. 이 타깃의 모든 prerequisite들을 시험한다.

4. prerequisite들에 대한 처리가 끝나면 그 타깃에 대한 재작성이 필요한지 아닌지 알게 된다.

a. 타깃에 대한 재작성이 필요없다면 디렉토리 탐색에서 얻은 파일경로가 prerequisite 리스트를 위해 사용된다. 간단히 말하면 make가 타깃의 재작성이 필요없다면 디렉토리 탐색으로 얻어진 경로명을 사용한다.

b. 만약 타깃의 재작성이 필요하다면 디렉토리 탐색에서 얻어진 경로명은 버려버리고 makefile에 명기된 파일들을 가지고 타깃을 재작성한다.

이 알고리즘이 일견 복잡해 보이나 실재로는 바로 우리가 원하는 현실적 방법이다.

GPATH는 VPATH와 거의 비슷


4.5.4 디렉토리 탐색을 이용해 recipe 작성하기


prerequisite 이 디렉토리 탐색을 통해 다른 디렉토리에서 발견되었을 때 그 규칙의 recipe를 변화시킬 수는 없고 그저 쓰여진대로 실행될 뿐이다. 그래서 make가 발견한 그 디렉토리에서 prerequisite를 찾도록 recipe를 주의해서 작성해야 한다.

이 문제는 ‘$’같은 자동 변수를 이용해서 풀 수 있다 (자동 변수 참조). 예를 들어 ‘$^’는 규칙내의 모든 prerequisite 리스트(그들이 발견된 디렉토리 이름 포함)를 나타낸다. 그리고 ‘$@’은 타깃을 나타낸다.

        foo.o : foo.c

                cc -c $(CFLAGS) $^ -o $@

종종 prerequisite에는 헤더파일이 포함되기도 한다. 하지만 recipe 내에서는 언급하고 싶지 않을 때 자동변수 ‘$<’를 사용한다. 이는 첫 번째 prerequisite을 의미한다.

VPATH = src:../headers

foo.o : foo.c defs.h hack.h

cc -c $(CFLAGS) $< -o $@


4.5.5 디렉토리 탐색과 암시적 규칙


VPATH 나 vpath에 명기된 디렉토리를 통한 탐색은 암시적 규칙을 고려해서 이루어진다 (암시적 규칙 참조). 예를 들어 foo.o에 대한 명시적 규칙이 없을 때 make는 foo.c 파일이 있는 경우 이를 컴파일하기 위해 암시적 규칙을 고려하게 된다. 헌데 이 파일이 현재 디렉토리에 없는 경우 적당한 디렉토리들을 찾아 헤맨다. 이렇게 해서 foo.c를 찾으면 컴파일을 위한 암시적 규칙을 적용하게 된다.


4.5.6 라이브러리를 링크를 위한 디렉토리 탐색


디렉토리 탐색은 링커와 함께 사용되는 라이브러리에도 특별한 방식으로 적용된다. 이 특별한 특성은 ‘-lname’과 같은 prerequisite 이름을 사용할 때 작동한다. prerequisite 이름이 ‘-lname’ 형태를 갖을때 make는 특별히 libname.so 파일을 찾아 일을 처리한다. 그리고 이런 파일이 없는 경우 현재 디렉토리에서 libname.a를 찾고 다시 vpath, VPATH가 지정한 디렉토리를 검색한 후 /lib, /usr/lib, prefix/lib 등의 순으로 찾아 헤맨다.

만약 시스템 내에 /usr/lib/libcurses.a 라이브러리가 있고 /usr/lib/libcurses.so 파일은 없는 경우

        foo : foo.c -lcurses

                cc $^ -o $@

는 ‘cc foo.c /usr/lib/libcurses.a -o foo’라는 명령을 수행하게 될것이다.

일반적인 환경에서 찾게 되는 라이브러리는 libname.so와 libname.a 이지만 .LIBPATTERNS 변수를 이용하면 입맛에 맞게 변경할 수도 있다.

이 변수의 값은 패턴 문자열로서 prerequisite에 ‘-lname’ 형태가 나타나면 name을 각 패턴에 치환하여 디렉토리 탐색을 수행한다. .LIBPATTERNS 변수의 디폴트 값은 ‘lib%.so lib%.a’이며 이 변수값를 빈값으로 설정함으로써 라이브러리 펼침을 금지할 수도 있다.


4.6 Phony 타깃

 

Phony 타깃은 파일이름이 아닌 타깃이다. 단지 수행하고자한 recipe의 이름이라는 편이 낫다. 포니 타깃을 사용하는 이유는 같은 이름을 갖는 실제 파일과 혼동을 방지하고 성능을 향상하기 위함이다.

만약 타깃파일을 생성하지 않는 recipe를 갖는 규칙을 작성한다면 이 recipe는 타깃이 호출될 때마다 실행되게 된다. 예를 들어 :

clean :

rm *.o temp

rm 명령이 clean이라는 이름의 파일을 생성하지 않기 때문에 그런 파일은 존재하지도 않을것이다. 결국 ‘make clean’을 수행할 때마다 rm 명령이 수행될 것이다. 하지만 만약 누군가에 의해 clean이란 이름의 파일이 현재 디렉토리에 생성되었다면 이 타깃에 prerequisite이 없기 때문에 clean이란 이름의 파일이 부득이 최신상태로 간주되어 recipe는 결코 수행될 수 없을 것이다. 이런 문제를 피하기 위해 이러한 타깃을 .PHONY를 이용해 가짜임을 명기하여 선언한다.

        .PHONY : clean

이렇게 하면 ‘make clean’은 clean이란 이름의 파일이 있건 없건 recipe를 실행시키게 된다.

make는 포니 타깃이 다른 어떤 파일에 의해서도 재작성되지 않는다는 걸 알기 때문에 이를 위한 암시적 규칙을 아예 고려하지 않는다. 이것이 포니 타깃을 선언하면 왜 성능이 향상되는지에 대한 이유이다.

재 귀적으로 make를 기동시키는 경우와 관련해서 유용한 포니 타깃의 예를 들어보자 (make의 재귀적 사용 참조). 이 경우 makefile은 생성해야할 많은 하위 디렉토리의 리스트를 갖는 변수를 가지게 된다. 이를 처리하는 한가지 방법은 recipe를 하위 디렉토리에 대해 shell loop 방식으로 처리하는 것이다.

     SUBDIRS = foo bar baz     

     subdirs:

             for dir in $(SUBDIRS); do \

               $(MAKE) -C $$dir; \

             done

하 지만 이 방식에는 문제가 있다. 먼저 하위 make에서 발생한 에러가 이 규칙에 의해 무시되기 때문에 어떤 한 디렉토리에서 에러가 발생해도 나머지 디렉토리에 대한 실행은 계속된다는 것이다. 이 문제는 에러를 감지하여 빠져나오게 하는 쉘명령을 추가해 극복할 수 있지만 재수없게 make에 ‘-k’ 옵션을 사용하면 다시 말짱 도루묵이 된다. 두 번째 문제는 사실 더 중요한 문제로 make의 병렬 타깃 생성 기능을 사용할 수 없다는 것이다 (병렬 처리 참조).

하위 디렉토리를 포니 타깃으로 선언하여 이러한 문제들을 해결할 수 있다.

     SUBDIRS = foo bar baz

     .PHONY: subdirs $(SUBDIRS)

     subdirs: $(SUBDIRS)

     $(SUBDIRS):

             $(MAKE) -C $@

      foo: baz

여 기서 foo 하위디렉토리는 baz 하위디렉토리가 완료된 다음에야 생성되도록 선언되었다. 이런 종류의 관계 선언은 병렬 생성시에 특별히 중요하다. 포니 타깃은 실제 타깃의 prerequisite이 되어서는 안된다. 이렇게 되면 그 recipe는 make 가 파일을 갱신할 때마다 실행될 것이기 때문이다. 포니 타깃이 다른 실제 타깃의 prerequisite이 되지 않는다면 이 포니 타깃의 recipe는 포니 타깃이 goal로 명시될 때만 실행된다 ( Arguments ot Specify the Goals 참조).

포 니 타깃이 prerequisites를 가질 수 있다. 어떤 디렉토리가 여러 프로그램을 포함하고 있을 때 그 모든 프로그램들을 하나의 ./Makefile에 기술하는 것이 가장 편리하다. 디폴트로 생성되는 타깃은 makefile 내의 첫 타깃이므로 그 첫 타깃을 ‘all’이라 이름 짖고 그 타깃의 prerequisite으로 각각의 프로그램을 기술한다. 예를 들면 :

     all : prog1 prog2 prog3

     .PHONY : all

    

     prog1 : prog1.o utils.o

             cc -o prog1 prog1.o utils.o

    

     prog2 : prog2.o

             cc -o prog2 prog2.o

    

     prog3 : prog3.o sort.o utils.o

             cc -o prog3 prog3.o sort.o utils.o


세 가지 프로그램을 모두 재작성하기 위해 ‘make’ 라고만 쓰거나 프로그램을 각각 재작성하기위해 ‘make prog1’ 등과 같이 기술하면 된다. 포니 특성은 상속되지 않는다. 즉 포니 타깃의 prerequisite는 포니가 아니다.

어 떤 포니 타깃이 다른 타깃의 prerequisite이 되면 마치 그 타깃의 서브루틴 같은 역할을 한다. 다음 예에서 ‘make cleanall’ 은 object 파일과 디퍼런스 파일 그리고 프로그램 파일 모두를 지워 버릴 것이다.

     .PHONY: cleanall cleanobj cleandiff

    

     cleanall : cleanobj cleandiff

             rm program

    

     cleanobj :

             rm *.o

    

     cleandiff :

             rm *.diff


4.7 recipe나 prerequisite가 없는 규칙


        clean: FORCE

                rm $(objects)

        FORCE

FORCE 의 기능은 ‘.PHONY : clean’과 동일하다. 하지만 다른 버전의 make에서 .PHONY를 지원하지 않고 FORCE 만을 지원해 많은 makefile에서 FORCE를 볼 수 있다. 하지만 .PHONY가 더 명시적이고 효율적이다.


4.8 사건을 기록하기 위한 빈 타깃


빈 타깃은 포니 타깃에 대한 일종의 변형으로서 명시적으로 실행할 recipe를 담아두기 위해서 사용된다. 포니 타깃과 달리 타깃 파일이 존재할 수는 있으나 그 파일의 내용에는 관심이 없고 일반적으로는 빈문서로 존재한다.

빈 타깃 파일의 목적은 그 규칙의 recipe가 최종 실행된 시간을 기록하기 위해서다. 이는 그 recipe들 중 하나가 타깃 파일을 touch 함으로써 이루어진다.

빈 타깃은 prerequisite을 가지지 않으면 빈 타깃의 의미가 없어진다. 빈타깃을 재작성하려할 때 prerequisite이 타깃보다 새것이면 recipe가 실행된다. 예를 들어:

     print: foo.c bar.c

             lpr -p $?

             touch print

‘make print'는 지난번 ’make print'를 수행한 이래로 소스파일에 변화가 있으면 lpr을 실행할 것이다. 여기서 자동 변수 ‘$?’는 prerequisite 들중 타깃보다 새로운 파일들의 리스트를 나타낸다.


4.9 특별한 타깃 이름

 

.PHONY

포니 타깃 참조

.SUFFIXES

예전 방식의 첨자 규칙 참조

.DEFAULT

.PRECIOUS

.PRECIOUS 가 종속되는 타깃들은 다음과 같이 특별히 처리된다: 만약 make가 recipe를 실행하는 사이 인터럽트에 의해 중단되거나 죽어버려도 그 타깃은 지워지지 않는다. (Interrupting or Killing make 참조). 타깃이 임시 파일인 경우 이 파일이 더 이상 필요없어져도 지워지지 않는다 (암시적 규칙의 연속 처리 참조). .SECONDARY 특별 타깃과 겹치는 부분이 있음.

.INTERMEDIATE

.INTERMEDIATE가 종속된 타깃들은 임시 파일로 갖주된다 (암시적 규칙의 연속 처리 참조). prerequisite이 없는 .INTERMEDIATE는 아무런 효력이 없다.

.SECONDARY

.SECONDARY 가 의존하는 타깃들은 임시 파일로 간주되며 절대로 자동 삭제 되지 않는다 (암시적 규칙의 연속 처리 참조). prerequisite이 없는 .SECONDARY는 모든 타깃들을 secondary로 처리되게 한다.

.SECONDEXPANSION

makefile 내의 어디에서든 .SECONDEXPASION이 타깃으로 기술되면 그 이후에 나타나는 모든 prerequisite들은 모든 makefile을 읽어들인 이후 2차 펼침이 이루어진다.

.DELETE_ON_ERROR

Recipe 에러 참조

.IGNORE

Recipe 에러 참조

.LOW_RESOLUTION_TIME

.SILENT

Recipe Echoing 참조.

.EXPORT_ALL_VARIABLES

변수를 통한 하위 make와의 통신 참조

.NOTPARALLEL

.ONESHELL

Recipe 실행하기 참조

.POSIX

        

4.10 한 규칙 내에 여러개의 타깃 사용하기

 

여러 타깃을 갖는 규칙은 한개의 타깃씩 여러개로 나누어 적은 규칙과 동일하다. 즉 모든 타깃에 대해 동일한 recipe가 적용되는 것이다. 하지만 recipe에서 ‘$@’를 이용할 경우 약간의 차이가 있을 수 있다.

이 방법은 두 경우에 유용하다.

        * recipe 없이 단지 prerequisite 만을 필요로 할 때

                kbd.o command.o files.o: command.h

        * 비슷한 recipe가 모든 타깃에 적용될 때. 자동변수 ‘$@’ 특정 타깃을 대시할 수 있기 때문에 모든 recipe가 완전히 동일할 필요는 없다. 예를 들어:

          bigoutput littleoutput : text.g

                 generate text.g -$(subst output,,$@) > $@

         는 아래와 동일하다.

          bigoutput : text.g

                  generate text.g -big > bigoutput

          littleoutput : text.g

                  generate text.g -little > littleoutput

여 기서 우리는 가상의 프로그램 generate이 ‘-big’k과 ‘-little’에 따라 두가지 출력을 만들어 낸다고 가정한다. 만약에 ‘$@’ 변수가 recipe를 변하게 했던 것처럼 prerequisite도 다르게 하길 원한다면 이를 일반 규칙에서는 구현할 수 없으나 static pattern rule 에서는 가능하다 (정적 패턴 규칙 참조).


4.11 한개의 타깃에 다수의 규칙 적용하기

 

하나의 파일이 여러개 규칙의 타깃이 될 수 있다. 모든 규칙에서 언급된 prerequisite들은 하나의 리스트로 합쳐져서 타깃을 생성하며 타깃이 어떤 prerequsite보다라도 오래되었으면 recipe가 실행된다.

타 깃은 오직 하나의 recipe를 가져야 하며 둘이상의 규칙에서 recipe를 명기하면 make는 가장 뒤에 나온 recipe를 채용해서 수행하고 에러 메시지를 출력한다. 경우에 따라 makefile 내의 여러곳에 정의된 다수의 recipe를 동일한 타깃을 위해 수행하는 것이 유용할 때가 있다 (Double-Colon 참조).

     objects = foo.o bar.o

     foo.o : defs.h

     bar.o : defs.h test.h

     $(objects) : config.h


또다른 예:

     extradeps=

     $(objects) : $(extradeps)


‘make extradeps=foo.h’ 명령을 수행하면 foo.h가 각 object의 prerequisite로 작용하게 된다.


4.12 정적 패턴 규칙


정적 패턴 규칙이란 복수 타깃으로 이루어진 규칙으로 prerequisite 이름이 각 타깃에 기초해 지어지는 것을 말한다.


4.12.1 정적 패턴 규칙의 문법


정적 패턴 규칙의 형식을 보면 다음과 같다.:

targets ...: target-pattern: prereq-patterns ...

recipe 

...

targets는 규칙의 타깃 리스트로서 일반 규칙에서처럼 와일드카드 문자를 포함할 수 있다 (파일 이름에 와일드카드 문자 사용하기 참조).

target-patternprereq-patterns는 각 타깃에 대한 prerequisite을 어떻게 만들어 낼것인가를 알려준다. 각 타깃에서 target-pattern과 일치하는 부분을 뽑아내고 (stem이라 부름) 이 stem은 prerequisite 이름을 만들기위해 prerequisite-patterns에 치환된다.

각 패턴에는 일반적으로 단 하나의 ‘%’만 포함된다. target-pattern이 타깃과 상응할 때 ‘%’는 타깃이름의 어느 부분과도 상응되며 그 나머지 부분은 정확히 같아야 한다. 예를 들어 foo.o는 패턴 ‘%.o’와 비교할 때 stem ‘foo’로써 상응한다.

prerequisite 이름은 각각의 prerequiste 패턴에서 stem을 치환함으로써 얻어진다. 예를 들어 prerequisite 패턴이 ‘%.c’일때 스템 ‘foo’를 치환함으로써 foo.c를 얻게 된다. ‘%’를 포함하지 않는 prerequisite 패턴을 작성할 수도 있는데 이때 모든 타깃은 같은 prerequisite을 같게 된다.

각각의 .c 파일을 컴파일하여 .o 파일을 만드는 예를 보도록 하자:

     objects = foo.o bar.o

    

     all: $(objects)

    

     $(objects): %.o: %.c

             $(CC) -c $(CFLAGS) $< -o $@

어기서 ‘$<’는 첫 prerequisite의 이름, ‘$@’는 타깃 이름을 나타내는 자동 변수이다.

각각의 타깃은 타깃패턴에 상응해야 하는데 패턴에 맞지 않는 타깃이 있을 경우 경고가 발생된다. 이런경우에는 filter 함수를 사용해 패턴에 맞지 않는 이름을 제거할 수 있다 (문자열 치환 및 분석을 위한 함수 참조).

     files = foo.elc bar.o lose.o

    

     $(filter %.o,$(files)): %.o: %.c

             $(CC) -c $(CFLAGS) $< -o $@

     $(filter %.elc,$(files)): %.elc: %.el

             emacs -f batch-byte-compile $<


이 예에서 ‘$(filter %.o,$(files))’는 bar.o lose.o를 걸러내고 첫 정적 패턴 규칙은 각 C파일로부터 .o 파일을 컴파일해 낸다.

다음 예는 정적 패턴 규칙에서 $*를 사용하는 방법을 보여준다.

     bigoutput littleoutput : %output : text.g

             generate text.g -$* > $@


generate 명령이 수행될 때 $*는 big이나 little 스템으로 펼쳐진다.


4.12.2 정적 패턴 규칙 대 암시적 규칙


정 적 패턴 규칙은 암시적 규칙과 많은 공통점이 있다 (패턴 규칙의 정의 및 재정의 참조). 둘다 타깃을 위한 패턴이 존재하고 패턴은 prerequisite의 이름을 구성하기 위해 사용된다. 차이점으로는 규칙이 적용되는 때를 make가 어떻게 결정하느냐이다.

암 시적 규칙은 그의 패턴과 일치하는 어떤 타깃에도 적용될 수 있지만 단지 recipe가 없고 prerequisite 만이 주어진 타깃에 대해서만 적용가능하다. 둘이상의 암시적 규칙이 적용 가능하면 그 중 하나만 적용되고 그 선택은 규칙의 순서에 의존한다.

반면에 정적 패턴 규칙은 규칙내에 명기된 정확한 타깃 리스트에 적용된다. 다른 어떤 타깃에 적용될 수 없고 단지 이 명기된 타깃에만 적용된다. 만약 각각 recipe를 갖은 두개의 규칙이 충돌하면 에러가 발생한다.

정적 패턴 규칙은 다음의 이유로 암시적 규칙보다 낫다고 할 수 있다.

        1)


3.13 Double-colon 규칙

 

Double-colon 규칙은 타깃이름 다음에 ‘:’ 대신 ‘::’를 쓴 명시적 규칙이다. 이 규칙은 같은 이름의 타깃에 대한 규칙이 여럿 나타날 때 일반 규칙과 다르게 처리된다. double-colon으로 된 패턴 규칙은 그 의미가 완전히 달라진다 (Match-Anything 규칙 참조)

한 개의 타깃이 여러 규칙 속에 출현할 때 그 규칙들은 모두가 보통 규칙이던지 아니면 모두가 double-colon 규칙이던지 같은 형태여야 한다. 그들이 double-colon 규칙이면 각각은 서로 독립적이다. 각각의 double-colon recipe는 타깃이 prerequisite보다 오래된 경우에 실행되는데 그 규칙에 prerequisite이 없으면 그 recipre는 항상 실행된다. 같은 타깃을 같는 여러개의 double-colon 규칙들은 사실상 서로가 완전히 분리되어 있다. 각각은 마치 다른 이름의 타깃처럼 별개로 처리된다. 한 타깃에 대한 여러개의 규칙들은 makefile 내에 출현하는 순서에 따라 실행되지만 실제적 의미에 있어 그 순서는 문제가 되지 않는다.

Double-colon 규칙은 약간 애매하고 그렇게 유용한 편은 아니다. 모든 double-colon 규칙은 recipe가 주어져야 한다. 그렇지 않으면 암시적 규칙이 적용되어 버리기 때문이다.


4.14 자동으로 prerequisite 생성하기

 

makefile속의 많은 규칙들은 단지 헤더파일과 오브잭트 파일의 관계만으로 이루어지는 경우가 많다. 예를 들어 main.c가 #include를 통해 defs.h를 사용한다면 다음과 같이 나타내면 된다.

        main.o: defs.h

이 규칙은 make에게 defs.h 가 변화할 때마다 main.o를 갱신하라고 알려준다. 더 큰 프로그램의 makefile에서는 이런 형태의 규칙이 더 많이 나타난다. 이 때 소스파일에서 #include를 추가하거나 제거할 때마다 매우 조심스럽게 makefile을 고쳐 써야 한다. 이런 혼란을 피하기 위해 대부분의 현대 C 컴파일러는 소스파일내의 #include 행을 보고 규칙을 쓸 수 있다. 예를 들어:

        cc -M main.c

는 다음과 같은 출력을 내놓는다.

        main.o : main.c defs.h

그리하여 우리는 더 이상 직접 이런 규칙을 써넣을 필요가 없다.

오 래된 make 프로그램들에서는 ‘make depend’라는 명령을 통해서 위와 같은 컴파일러 특성을 사용해 왔다. 이 명령을 통해 depend 파일이 포함하고 있는 모든 자동생성 prerequisite들을 만들어 낼 수 있으며 makefile은 그들을 include를 통해 읽어들여 사용한다. 하지만 이 기능은 makefile을 새로 만들어 낼 수 있는 GNU make 특성으로 인해 쓸모없어지고 말았다.

우리가 권장하는 자동 prerequisite 생성에 대한 예를 보자. 여기서 각 소스파일은 해당하는 자신의 makefile을 갖는다. 즉 소스파일명이 name.c일 때 그에 해당하는 makefile은 name.d가 되며 name.d는 name.o가 의존하는 파일들을 열거하고 있고 있다. 이 방식은 변화가 일어난 소스파일만이 재 스캔되어 새로운 prerequisite을 만들어 내는 방식이다.

다음 예는 name.c라는 소스파일로부터 name.d라는 prerequisite 파일을 만들어 내는 패터 규칙을 보여주고 있다:

        %.d:  rm -f $@; \

              $(CC) -M $(CPPFLAGS) $<> $@.$$$$; \

              sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \

              rm -f $@.$$$$


패 턴 규칙의 정의에 관해서는 패턴규칙의 정의와 재정의를 참조하기 바란다. ‘-e’ 는 $(CC) 명령 (또는 다른 어떤 명령)이 실패 (0이 아닌 상태)로 끝날 경우 쉘로 하여금 즉시 빠져나오게 한다. GNU 컴파일러에는 ‘-M’ 옵션 대신에 ‘-MM’을 사용할 수도 있는데 이는 헤더파일의 prerequisite을 생락하게 된다. 자세한 내용은 전처리기를 제어하기위한 옵션 참조.

sed 명령의 목적은 다음 행을:

        main.o : main.c defs.h

다음 행으로:

        main.o main.d : main.c degs.h

바꾸어주는 데에 있다. ‘.d’파일은 그에 해당하는 ‘.o’파일이 의존하는 소스파일과 헤더파일에 의존하게 되며 이로써 make는 어떤 소스파일이나 헤더파일이 바뀔 때마다 타깃이 갱신되어야 함을 깨닫게 된다.

일단 ‘.d’파일들을 재 생성하기위한 규칙이 정의되었으면 include 지시자를 사용해 그 모든 파일들을 읽어들인다. 예를 들어:

        sources = foo.c bar.c

        include $(souces:.c=.d)

(이 예에서는 소스파일 ‘foo.c bar.c’를 ‘foo.d bar.d’로 변환시켜주는 치환변수 참조 법을 사용하고 있다. Substitution References 참조). ‘.d’파일도 makefile이기 때문에 필요할 때면 make에 의해 재작성된다.