Target-Specific Variables

make 을 사용하는 분들 중에는 global 변수와 recipe 에서 사용되는 automatic 변수는 알고 있는데 특정 타겟과 관련해서 변수를 설정해 사용할 수 있는 것은 모르는 경우가 많습니다. 타겟 변수를 설정하면 이후에 실행되는 모든 recipe 에서 해당 변수값이 사용됩니다.

먼저 타겟 라인은 global 영역에서 대입 연산과 동일하게 작성 순서에따라 처리됩니다. 이것은 변수가 recursive 이던 simple 이던 상관없습니다. 다음을 보면 중간에 BUILD_DIR 값을 release 로 변경하면 이후에는 타겟 라인 변수값이 모두 release 로 변경되는 것을 볼 수 있습니다.

all : debug/foo release/bar

BUILD_DIR = debug

$(BUILD_DIR)/foo : $(BUILD_DIR)/foo.o    # $(BUILD_DIR) 변수값은 debug 가 된다.
    @echo foo : $@ : $^

BUILD_DIR = release

$(BUILD_DIR)/bar : $(BUILD_DIR)/bar.o    # $(BUILD_DIR) 변수값은 release 가 된다.
    @echo bar : $@ : $^

.DEFAULT: ;

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

foo : debug/foo : debug/foo.o
bar : release/bar : release/bar.o

타겟 변수는 global 변수를 override 해서 타겟 전용 변수로 사용할 수 있게 해줍니다. 타겟 변수는 타겟 라인의 prerequisites 부분에서 설정을 합니다. 다음 첫 번째 예제를 보면 bar 타겟의 변수 AA 의값은 global 변수와 동일하게 100 으로 나오지만 foo 타겟의 값은 타겟 변수 설정에 따라 200 이 되는 것을 볼 수 있습니다. 또한 타겟 변수는 prerequisites 에게 상속이 됩니다. 따라서 오른쪽 예제에서처럼 foo 타겟의 prerequisite 으로 bar 를 추가하면 bar 타겟의 AA 값도 200 으로 나오게 됩니다.

all : foo bar                              all : foo

AA := 100                                  AA := 100

foo : AA := 200                            foo : AA := 200    # 타겟 변수
foo :                                      foo : bar          # bar prerequisite 추가
    @echo foo : $(AA)                          @echo foo : $(AA)

bar :                                      bar :
    @echo bar : $(AA)                          @echo bar : $(AA)

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

foo : 200                                  foo : 200
bar : 100                                  bar : 200

타겟 변수값이 prerequisites 에게 상속되는 것은 변수값이 recipe 에서 사용될 때 입니다. 다음을 보면 bar 는 foo 의 prerequisite 이지만 bar 타겟 라인 변수 CC 가 설정될 때는 global 변수 AA 의 값이 적용됩니다. 반면에 recipe 에서 변수 AA 값이 사용될 때는 foo 로 부터 상속되어 200 이됩니다.

AA := 100

foo : AA := 200
foo : BB := $(AA)      # foo 타겟 변수값이 사용되어 BB 값은 200 이 된다.

foo : bar              # bar prerequisite 추가
    @echo foo : $(AA) $(BB)   # AA : 200, BB : 200


bar : CC := $(AA)      # global 영역에서 타겟 라인이 처리될때 AA 값은 100 이된다.
bar :
    @echo bar : $(CC) $(AA)   # CC 는 100, AA 는 foo 로부터 상속되어 200 이 된다.

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

bar : 100 200
foo : 200 200

recipe 는 global 영역 처리가 완료된 후에 실행된다.

따라서 recipe 가 실행될 때는 global 영역에서 마지막으로 설정된 변수값이 사용됩니다.

CFLAGS := aaaaaa
foo :
    @echo foo : $(CFLAGS)

CFLAGS := bbbbbb
bar :
    @echo bar : $(CFLAGS)

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

foo : bbbbbb

타겟 변수는 global 영역과 별도의 flavor 를 갖는다.

이것은 타겟 변수를 사용할 때 주의해야 될 사항입니다. global 영역에 동일한 이름의 변수가 존재하더라도 타겟 변수에서는 별도의 flavor 를 갖습니다. 다음 두 예제를 보면 CFLAGS 변수가 처음에는 둘 다 simple 변수로 초기화되어 있는데 왼쪽은 예상대로 foo 값으로 100 이 출력되지만 += 연산자를 사용하는 오른쪽은 bbbbbb 와함께 200 이 출력되는 것을 볼 수 있습니다. 이것은 CFLAGS 변수가 global 설정과 달리 처음 변수에 += 연산자를 사용할때 처럼 recusive 변수가 된다는 의미인데요. 만약에 위쪽의 주석 처리한 라인의 주석을 해제하여 simple 변수로 만들면 동일하게 값으로 100 이 출력되는 것을 볼 수 있습니다.

CFLAGS := aaaaaa                            CFLAGS := aaaaaa
AA := 100                                   AA := 100

foo : CFLAGS := $(AA)                       # foo : CFLAGS := 
foo :                                       foo : CFLAGS += $(AA)$(info $(flavor CFLAGS))
    @echo foo : $(CFLAGS)                   foo :
                                                @echo foo : $(CFLAGS)
CFLAGS := bbbbbb                            
AA := 200                                   CFLAGS := bbbbbb
                                            AA := 200
bar :                                       
    @echo bar : $(CFLAGS)                   bar :
                                                @echo bar : $(CFLAGS)

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

foo : 100                                   recursive
                                            foo : bbbbbb 200

Variable scopes

make 에서 사용되는 variable scopes 에는 실질적으로 3 가지 종류가 있는 것과 같습니다.
( foreach 함수에서 사용되는 함수 scope 은 제외했습니다. )

  1. global 변수
  2. target ( pattern, static pattern ) specific 변수
  3. recipe 실행시 설정되는 automatic 변수

다음 예제에서 LDFLAGS, LDLIBS 변수는 global 변수가 되고, debug, release 타겟 라인에서 설정한 CFLAGS, CPPFLAGS 변수는 target specific 변수가 됩니다. recipe 에서 shell 명령을 작성할 때 사용된 $@ 변수는 automatic 변수입니다. 실행 결과를 보면 각각의 타겟 라인에서 설정한 변수값들이 적용되는 것을 볼 수 있습니다.

LDFLAGS := -L/usr/local/lib                # global 변수
LDLIBS  := -lzoo

debug : CFLAGS   := -g                     # target-specific 변수
debug : CPPFLAGS := -DDEBUG

release : CFLAGS   := -O2                  # target-specific 변수
release : CPPFLAGS := -DNDEBUG

debug release : foo.o bar.o
    @echo $@ : $(LDFLAGS) $(LDLIBS)        # '$@' 는 automatic 변수

%.o :
    @echo $@ : $(CFLAGS) $(CPPFLAGS)

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

sh$ make debug
foo.o : -g -DDEBUG
bar.o : -g -DDEBUG
debug : -L/usr/local/lib -lzoo

sh$ make release 
foo.o : -O2 -DNDEBUG
bar.o : -O2 -DNDEBUG
release : -L/usr/local/lib -lzoo

makefile 이 처리되는 순서

다음은 makefile 실행시 처리되는 순서를 info 함수를 이용해 조회해본 것입니다. 출력 결과를 보면 111 ~ 777 까지 순서대로 출력이 되고 마지막으로 recipe 가 실행되는 것을 볼 수 있습니다. 따라서 변수를 이용해 타겟 라인 값을 설정할 경우에는 변수 정의가 뒤에 오지 않도록 주의해야 합니다. 다음 오른쪽 예제를 보면 foo 타겟의 prerequisite 을 설정할 때 DEP 변수를 사용하고 있는데 변수 정의가 뒷부분에 위치하여 정상적으로 설정이 되지 않고 있습니다.

$(info 11111)                               $(info 11111)

DEP := bar                                  foo : $(DEP) $(info 22222)              
                                            foo $(info 33333) : $(info 44444)
foo : $(DEP) $(info 22222)                      $(info AAAAA)
foo $(info 33333) : $(info 44444)               @echo target foo
    $(info AAAAA)                       
    @echo target foo                        bar $(info 55555) : $(info 66666)
                                                $(info BBBBB)
bar $(info 55555) : $(info 66666)               @echo target bar
    $(info BBBBB)                       
    @echo target bar                        $(info 77777)

$(info 77777)                               DEP := bar      <----- 끝으로 이동

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

11111                                       11111
22222                                       22222
33333                                       33333
44444                                       44444
55555                                       55555
66666                                       66666
77777                                       77777
BBBBB                                       AAAAA        # bar 타겟 등록이 안된다.
target bar                                  target foo
AAAAA
target foo

변수값 설정이 중복될 경우 마지막 값이 사용된다.

global 영역에서 마지막으로 설정된 값이 recipe 에서 사용됩니다.

foo.o bar.o :                                     foo.o bar.o :
    @echo $@ : $(CFLAGS)                              @echo $@ : $(CFLAGS)

foo.o bar.o : CFLAGS := -g                        bar.o :       CFLAGS := -O2
bar.o :       CFLAGS := -O2                       foo.o bar.o : CFLAGS := -g

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

sh$ make foo.o                                     sh$ make foo.o
foo.o : -g                                         foo.o : -g

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

private 지시자

아래 예제의 foo : bar 룰 정의에서 foo 는 상위 타겟이 되고 prerequisite 에 해당하는 bar 는 하위 타겟이 됩니다. 상위 타겟에서 설정한 변수는 하위 타겟에 상속되기 때문에 bar 에서도 사용할 수가 있는데요. 만약에 foo 타겟 전용으로 변수를 사용하고 하위 타겟으로는 상속되지 않게 하려면 private 지시자를 사용하면 됩니다.

private 지시자는 GNU make 3.82 버전에서 추가된 기능입니다.

# foo 타겟에서 private 지시자를 사용하여 하위 타겟에 상속되지 않아 
# bar 타겟 에서는 global 변수값이 사용된다.

AA := 100                                  

foo : private AA := 200                    
foo : bar                                  
    @echo foo $(AA)                        

bar :                                      
    @echo bar $(AA)                        

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

bar 100                                    
foo 200