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 함수가 실행되면 $(변수)
가 확장되고, $$
는 $
로 변경되고,
$(함수 ...)
가 실행됩니다.
$(info 111)
함수가 실행되어 111 이 출력됩니다.$$(info 222)
는$$
변수가$
로 변경되어$(info 222)
가 됩니다.$(if $(AA)...)
함수가 실행되는데 이때는 앞선AA := 100
대입 연산이 적용되지 않으므로 출력값이 444444 가 되는 것을 볼 수 있습니다.$$(if $$(AA)...)
함수는$$
변수가$
로 변경되어$(if $(AA)...)
가 됩니다.- rule 정의 부분은
$1
변수는foo
가되고$2
변수는bar
가 됩니다. - recipe 에 위치한
$$(if $$(AA)...)
함수는$$
변수가$
로 변경되어$(if $(AA)...)
가 됩니다.
앞서 처리된 결과가 eval 함수로 전달되어 실행되는데 이때 대입 연산과 rule 이 정의됩니다. 위에서 1번, 3번은 info 함수 실행 결과로 출력이 발생하지만 반환값은 empty 이므로 eval 함수에 전달되는 값은 없습니다.
$(info 222)
가 실행되어 222 가 출력됩니다.AA := 100
에 의해 변수 AA 가 정의됩니다.- 두 번째
$(if $(AA)...)
함수가 실행되는데 이번에는$(AA)
값이 존재하므로 555555 가 출력됩니다. - eval 함수에 의해
foo
,bar
두 개의 rule 도 정의가 됩니다. - recipe 에 있는
$(if $(AA)...)
함수는 실행 결과로echo aaaaaaaa
가 반환됩니다.
eval 함수가 실행을 완료하면 AA
변수와 foo
, bar
두 개의 rule 이 정의된 상태가 되므로
최종적으로 첫 번째 룰에 해당하는 foo
룰이 실행되어
xxxxxxxx
와 aaaaaaaa
메시지가 echo 명령에 의해 출력됩니다.