Recursive Make

make 에서 사용되는 rule 은 구조가 간단하여 읽고 해석하는데 어려움이 없지만 의존성을 가지고 서로 연결되기 시작하면 가독성이 떨어지고 추적하기가 어렵습니다. 따라서 프로젝트가 커지면 각 모듈 디렉토리별로 Makefile 을 두어서 독립적으로 빌드하게 됩니다. make 실행시 처음 실행되는 makefile 을 top makefile 이라고 하는데 여기서 전체적인 global 설정과 각 모듈별 의존관계를 설정한 후에 하나의 makefile 이 실행되는것 처럼 일괄 빌드하는데 이것을 recursive make 이라고 합니다.

recursive make 을 위해 기본적으로 사용되는 형식은 아래 예제와 같습니다. 여기서 recipe 에서 실행되는 make 을 sub-make 이라고 하는데 실행파일명으로는 $(MAKE) 변수를 사용해야 합니다. 왜냐하면 recipe 명령 라인에 $(MAKE) 이 존재하면 그 라인은 앞에 + prefix 가 붙은 것과 같이 동작하기 때문입니다. 이것은 프롬프트 상에서 make 명령을 -t (--touch), -n (--just-print), -q (--question) 옵션을 이용해 실행했을 때에도 sub-make 이 실행되게 합니다.

SUBDIRS := foo bar zoo

all clean : $(SUBDIRS)

$(SUBDIRS) :
    $(MAKE) -C $@ $(MAKECMDGOALS)

foo : baz       # ---- foo 보다 baz 를 먼저 실행 (1)

.PHONY: all clean $(SUBDIRS)

make 명령 실행시 -j 옵션을 이용해 병렬 실행을 하게 되면 prerequisites 에 해당하는 foo bar zoo 타겟들은 모두 동시에 실행되게 됩니다. 이때 만약에 특정 디렉토리가 먼저 빌드 되어야 할 경우에는 위의 (1) 번과 같이 의존 관계를 설정해 주면 됩니다.

sub-make 이 실행될 때는 자동으로 --print-directory 옵션이 설정되므로 makefile 실행 전, 후에 아래와 같은 메시지가 출력됩니다. 만약에 이와 같은 메시지 출력을 disable 하고 싶으면 --no-print-directory 옵션을 사용하면 됩니다. [1] 안에 있는 숫자는 sub-make 의 depth 정도를 나타냅니다. top makefile 에서는 0 이 되고 첫 번째 sub-make 에서는 1 이 되는데 여기서 또 sub-make 이 실행되면 2 가 됩니다. ( MAKELEVEL 변수로 조회할 수 있습니다.)

make[1]: Entering directory '/home/mug896/tmp/foo'
. . .
make[2]: Entering directory '/home/mug896/tmp/foo/bar'
. . .
make[2]: Leaving directory '/home/mug896/tmp/foo/bar'
. . .
make[1]: Leaving directory '/home/mug896/tmp/foo'

병렬 실행 시에도 순서대로 실행하기

병렬 실행 시에도 특정 순서에 따라 serially 실행시키는 것이 필요할 경우에는 다음과 같이 recipe 에서 shell script 를 이용해 빌드하면 됩니다. 이때도 각각의 sub-make 내에서는 병렬로 실행됩니다.

all clean : serial

serial :
    $(MAKE) -C foo $(MAKECMDGOALS)
    $(MAKE) -C bar $(MAKECMDGOALS)
    $(MAKE) -C zoo $(MAKECMDGOALS)

--------------------------------------
SUBDIRS := foo bar zoo

all clean : serial

serial :
    @for dir in $(SUBDIRS);                          \
    do                                               \
        $(MAKE) -C $$dir $(MAKECMDGOALS) || exit;    \
    done

recurive make 에서 변수의 사용

recursive make 은 새로 make 프로세스를 생성해 실행하는 것이므로 top makefile 에서 설정한 변수를 sub-make 에서도 사용하려면 export 지시자를 사용해야 합니다.

export ARCH SRCARCH CONFIG_SHELL HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP PAHOLE KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS
export MAKE LEX YACC AWK INSTALLKERNEL PERL PYTHON PYTHON2 PYTHON3 UTS_MACHINE
. . . .

MAKEFLAGS, MAKEOVERRIDES 같은 builtin 변수들은 자동으로 export 되어 sub-make 에도 적용됩니다. make 명령 라인에서 설정한 변수값은 기본적으로 makefile 에서 설정한 변수값에 우선해서 사용되는데 MAKEOVERRIDES 변수는 이와 같은 기능이 sub-make 에도 적용되게 합니다. 다음은 실제 두 변수가 어떻게 적용되는지 테스트하는 예제입니다.

sh$ cat Makefile                          # top makefile

AA := 100
$(info top : MF : $(MAKEFLAGS))
$(info top : MO : $(MAKEOVERRIDES))
$(info top : AA : $(AA))

sub :
    @$(MAKE) -C subdir

----------------------------------
sh$ cat subdir/Makefile                   # sub-make makefile

AA := 200
$(info sub : MF : $(MAKEFLAGS))
$(info sub : MO : $(MAKEOVERRIDES))
$(info sub : AA : $(AA))

0 :

###########  실행 결과  ############
sh$ make 
top : MF : 
top : MO : 
top : AA : 100
make[1]: Entering directory '/home/mug896/subdir'
sub : MF : w                    # '-w' 옵션은 sub-make 실행시 자동으로 설정된다.
sub : MO : 
sub : AA : 200                  # 기본적으로 makefile 에서 설정한 변수값이 사용된다.
make[1]: Nothing to be done for '0'.
make[1]: Leaving directory '/home/mug896/subdir'

sh$ make -rR                    # 명령 라인에서 옵션 설정
top : MF : rR                   # MAKEFLAGS 변수값이 설정되고
top : MO : 
top : AA : 100
make[1]: Entering directory '/home/mug896/subdir'
sub : MF : rRw                  # sub-make 에도 전달되어 사용된다.
sub : MO : 
sub : AA : 200
make[1]: Nothing to be done for '0'.
make[1]: Leaving directory '/home/mug896/subdir'

sh$ make -rR AA=111             # 명령 라인에서 AA 변수값 설정
top : MF : rR
top : MO : AA=111               # MAKEOVERRIDES 변수값이 설정된다.
top : AA : 111                                      
make[1]: Entering directory '/home/mug896/subdir'
sub : MF : rRw
sub : MO : AA=111               # MAKEOVERRIDES 변수값 설정에 따라
sub : AA : 111                  # sub-make 에서도 override 된다.
make[1]: Nothing to be done for '0'.
make[1]: Leaving directory '/home/mug896/subdir'

다음은 top makefile 을 다음과 같이 변경한 후에 실행합니다.

AA := 100
MAKEOVERRIDES =                       # MAKEOVERRIDES 변수값을 empty 로 만듦

$(info top : MF : $(MAKEFLAGS))
$(info top : MO : $(MAKEOVERRIDES))
$(info top : AA : $(AA))

sub :
    @$(MAKE) -C subdir MAKEFLAGS=     # MAKEFLAGS 변수값도 empty 로 설정

##########  실행 결과  ###########
sh$ make
top : MF : 
top : MO : 
top : AA : 100
make[1]: Entering directory '/home/mug896/subdir'
sub : MF : 
sub : MO : MAKEFLAGS=
sub : AA : 200
make[1]: Nothing to be done for '0'.
make[1]: Leaving directory '/home/mug896/subdir'

sh$ make -rR                    # 명령 라인에서 옵션 설정
top : MF : rR
top : MO : 
top : AA : 100
make[1]: Entering directory '/home/mug896/subdir'
sub : MF :                      # MAKEFLAGS 값이 적용되지 않는다.
sub : MO : MAKEFLAGS=
sub : AA : 200
make[1]: Nothing to be done for '0'.
make[1]: Leaving directory '/home/mug896/subdir'

sh$ make -rR AA=111             # 명령 라인에서 AA 변수값을 설정하였지만
top : MF : rR
top : MO :                           
top : AA : 111
make[1]: Entering directory '/home/mug896/subdir'
sub : MF : 
sub : MO : MAKEFLAGS=           # MAKEOVERRIDES 값이 설정되지 않아
sub : AA : 200                  # sub-make 에는 적용되지 않는다.
make[1]: Nothing to be done for '0'.
make[1]: Leaving directory '/home/mug896/subdir'