Eval Function

make 에서는 대입 연산이나 rule 정의에 변수나 함수가 사용될 경우 자동으로 확장된 후 처리가 되므로 shell 의 eval 명령과는 차이가 있습니다. 하지만 실행 중에 동적으로 대입 연산과 rule 을 정의해 사용하려면 eval 함수를 사용해야 합니다. eval 함수의 반환값은 기본적으로 empty 이므로 makefile 어느 곳에서든지 syntax error 없이 위치시킬 수 있습니다.

실행 중에 동적으로 대입 연산을 정의

다음은 실행 중에 함수를 이용해 동적으로 변수를 정의하려고 한것인데 eval 함수를 사용하지 않으면 오류가 발생하는 것을 볼 수 있습니다.

define AA                                         define AA
foo := bar                                        foo := bar
endef                                             endef

$(AA)                                             $(eval $(AA))  # eval 함수를 사용
$(info $(foo))                                    $(info $(foo))

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

Makefile:5: *** empty variable name.  Stop.       bar

위에서 발생하는 오류는 foo := bar 문장을 대입 연산이 아니라 foo 로 시작하는 rule 정의로 인식하여 발생하는 오류입니다. 룰을 정의할 때 사용되는 구분자인 : 문자는 존재하지만 이후에 = bar 타겟 변수 설정에서 왼쪽에 위치하는 변수 이름이 없기 때문에 empty variable name 오류가 발생합니다. 만약에 := 연산자 대신에 = 연산자를 사용하면 missing separator 오류가 발생합니다.

AA = $1 = $2

$(call AA,foo,bar)    # call 함수의 반환값은 foo = bar 가되고 rule 정의로 인식된다.
$(info $(foo))

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

Makefile:3: *** missing separator.  Stop.

따라서 실행 중에 동적으로 대입 연산을 정의하려면 다음과 같이 eval 함수를 사용해야 합니다.

AA = $1 := $2

$(eval $(call AA,foo,bar))
$(info $(foo))

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

bar

실행 중에 동적으로 rule 을 정의

위 예제에서도 보았지만 makefile 에 임의의 스트링이 오면 make 은 기본적으로 rule 정의로 인식합니다. 따라서 다음과 같은 경우는 eval 함수 없이도 실행 중에 동적으로 rule 을 정의할 수 있습니다.

define onerule
$(target) : $(prerequisites) ;@echo target: $$@, prerequisites: $$^
endef

target = t1
prerequisites = p1 p2 p3
$(onerule)                # 여기서 함수에 의해 t1 룰이 동적으로 정의되어

.DEFAULT: ;

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

target: t1, prerequisites: p1 p2 p3      # 오류 없이 실행된다.

하지만 이것은 rule 전체를 하나의 라인에 작성할 경우만 가능하고, recipe 가 tab 을 이용해 별도의 라인에 작성될 경우는 eval 함수를 사용해야 합니다.

define onerule
$(target) : $(prerequisites)
    @echo target: $$@, prerequisites: $$^   # recipe 를 별도의 라인에 작성
endef

target = t1
prerequisites = p1 p2 p3
$(eval $(onerule))       # eval 함수를 사용해야 한다.

target = t2
prerequisites = r1 r2 r3
$(eval $(onerule))

.DEFAULT: ;

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

target: t1, prerequisites: p1 p2 p3           # t1 룰의 실행 결과

다음은 foreach 함수를 활용하는 예제

define onerule
$T : $($T_prerequisites)
    @echo target: $$@, prerequisites: $$^
endef

targets = t1 t2

t1_prerequisites = p1 p2 p3
t2_prerequisites = r1 r2 r3

$(foreach T,$(targets),$(eval $(onerule))) 

.DEFAULT: ;

다음은 call 함수를 활용하는 예제

define onerule
$1 : $2
    @echo target: $$@, prerequisites: $$^
endef

$(eval $(call onerule,t1,p1 p2 p3))
$(eval $(call onerule,t2,r1 r2 r3))

.DEFAULT: ;

다음은 if 문으로 정의하는 예제인데 recipe 에서 , 문자가 사용될 경우는 comma 변수를 정의해 사용하면 됩니다.

, := ,          # comma 변수 정의

define onerule
$(if $(filter foo,$(MYVAR)),
$1 : $2
    @echo target: $$@$(,) prerequisites: $$^
,    
$3 : $4
    @echo target: $$@$(,) prerequisites: $$^
)
endef

MYVAR := bar
$(eval $(call onerule,t1,p1 p2 p3, \
                      t2,r1 r2 r3))

.DEFAULT: ;

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

target: t2, prerequisites: r1 r2 r3

테스트 시에 오류가 발생하면 항상 tab 문자가 정확히 입력됐는지 확인하세요 !

makefile 이 처리되는 순서 알아보기

다음 예제를 이용하면 실제 make 이 어떤 순서에 따라서 변수, 함수가 확장되고 룰이 정의되어 실행되는지 알 수 있습니다. 예제가 좀 복잡해 보일수 있는데 아래 설명이 있으므로 이해하는데 어려움은 없을 것입니다.

define tworules
$(info 111)
$$(info 222)
AA := 100
$(if $(AA),$(info 333333),$(info 444444))
$$(if $$(AA),$$(info 555555),$$(info 666666))
$1 :
    @echo xxxxxxxxxx
    @$$(if $$(AA),echo aaaaaaaaaa,echo bbbbbbbbbb)

$2 :
    @echo yyyyyyyyyy
endef

$(info start.....)
$(eval $(call tworules,foo,bar))
$(info end.....)

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

$ make foo
start.....
111           ---> call 함수에 의해 출력
444444        ---> call 함수에 의해 출력
222           ---> eval 함수에 의해 출력
555555        ---> eval 함수에 의해 출력
end.....
xxxxxxxxxx    ---> foo 를 recipe 실행에 의해 출력
aaaaaaaaaa    ---> foo 룰 recipe 실행에 의해 출력

먼저 call 함수가 실행되면 $(변수) 가 확장되고, $$$ 로 변경되고, $(함수 ...) 가 실행됩니다.

  1. $(info 111) 함수가 실행되어 111 이 출력됩니다.
  2. $$(info 222)$$ 변수가 $ 로 변경되어 $(info 222) 가 됩니다.
  3. $(if $(AA)...) 함수가 실행되는데 이때는 앞선 AA := 100 대입 연산이 적용되지 않으므로 출력값이 444444 가 되는 것을 볼 수 있습니다.
  4. $$(if $$(AA)...) 함수는 $$ 변수가 $ 로 변경되어 $(if $(AA)...) 가 됩니다.
  5. rule 정의 부분은 $1 변수는 foo 가되고 $2 변수는 bar 가 됩니다.
  6. recipe 에 위치한 $$(if $$(AA)...) 함수는 $$ 변수가 $ 로 변경되어 $(if $(AA)...) 가 됩니다.

앞서 처리된 결과가 eval 함수로 전달되어 실행되는데 이때 대입 연산과 rule 이 정의됩니다. 위에서 1번, 3번은 info 함수 실행 결과로 출력이 발생하지만 반환값은 empty 이므로 eval 함수에 전달되는 값은 없습니다.

  1. $(info 222) 가 실행되어 222 가 출력됩니다.
  2. AA := 100 에 의해 변수 AA 가 정의됩니다.
  3. 두 번째 $(if $(AA)...) 함수가 실행되는데 이번에는 $(AA) 값이 존재하므로 555555 가 출력됩니다.
  4. eval 함수에 의해 foo, bar 두 개의 rule 도 정의가 됩니다.
  5. recipe 에 있는 $(if $(AA)...) 함수는 실행 결과로 echo aaaaaaaa 가 반환됩니다.

eval 함수가 실행을 완료하면 AA 변수와 foo, bar 두 개의 rule 이 정의된 상태가 되므로 최종적으로 첫 번째 룰에 해당하는 foo 룰이 실행되어 xxxxxxxxaaaaaaaa 메시지가 echo 명령에 의해 출력됩니다.