GNU Make 와 gcc 를 이용한 멀티 타겟 Makefile 만들기

예전에 GNU Make 와 gcc 를 이용한 auto-dependency Makefile 만들기를 썼었는데, 이것은 단지 하나의 타겟만을 만들 수 있었다. 이번에는 이 부분을 개선해서 멀티 타겟 Makefile 을 만들어보자.

멀티 타겟을 지원하기 위해서는 GNU Make 의 몇 가지 함수를 먼저 살펴볼 필요가 있다.

foreach 함수


foreach 함수의 문법은 다음과 같다.

$(foreach var,list,text)

기능은 list 의 내용을 빈 칸을 기준으로 구분해서 var 에 각각 대입하고, 매번 text 를 확장한다. 예를 들면,

l := $(foreach d,a b c d,l $(d))

이 경우, l 은 l a l b l c l d 가 된다.

call 함수


call 함수의 문법은 다음과 같다.

$(call var,param,param,...)

call 함수는 var 를 일종의 함수처럼 처리한다. var 뒤의 param 들은 var 의 $(1), $(2), ... 로 대체된다. 예를 들면,

merge = $(1)/$(2)
path := $(call merge,a,b)

이 경우 path 는 a/b 가 된다.


define 지시자


define 지시자와 endef 를 통해 멀티 라인 변수를 만들 수 있다. 다음과 같이 쓰인다.

define m
echo foo
echo $(boo)
endef

만일 call 함수와 함께 쓰인다면 보통의 함수와 같은 역할을 할 수 있다.

eval 함수


eval 함수의 문법은 다음과 같다.

$(eval text)

eval 함수는 text 의 내용을 Make 파일의 일부로 해석하게 만든다. 예를 들면,

.PHONY: all

define a
all:
    echo hello
endef

$(eval $(a))

이것은 다음처럼 처리된다.

.PHONY: all

all:
    echo hello

멀티 타겟 Makefile 을 만들기위한 기본적인 함수와 지시자를 알아보았다. 이제 실제 구현을 해보자.

변수들


우선 멀티 타겟을 지원하기 위해서는 해당 타겟들을 지정할 변수가 필요하다. 이를 BIN_PROGRAMS 라 하자. BIN_PROGRAMS 에 필요한 타겟들을 빈 칸으로 구분하여 저장한다. 그리고 각종 옵션들을 저장하기 위한 공통 기본 변수들(CLFAGS, CXXFLAGS, LDFLAGS 따위)뿐만 아니라 각 타겟만을 위한 옵션들을 저장할 변수들도 필요하다. 각각에 대해 _SRCS, _CFLAGS, _CXXFLAGS, _LDFLAGS, _LDLIBS 접미사를 사용하자.

템플릿


이를 위한 기본적은 템플릿은 음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
define program_template
$(1)_DEPS := $(foreach s,$($(1)_SRCS),$(s:$(suffix $(s))=.d))
$$($(1)_DEPS) : CFLAGS += $($(1)_CFLAGS)
$$($(1)_DEPS) : CXXFLAGS += $($(1)_CXXFLAGS)

$(1)_OBJS := $$($(1)_DEPS:.d=$(OBJEXT))
$$($(1)_OBJS) : CFLAGS += $($(1)_CFLAGS)
$$($(1)_OBJS) : CXXFLAGS += $($(1)_CXXFLAGS)

$(1)$(EXEEXT) : $$($(1)_OBJS)
     $(LD) $(LDFLAGS) $$($(1)_LDFLAGS) -o $$@ $$^ $(LDLIBS) $$($(1)_LDLIBS)

$(1) : $(1)$(EXEEXT)
endef



눈이 돌아갈 정도로 $ 들의 향연이다. 어느 것에는 한 개가, 어느 것에는 두 개가 붙어 있는데, 이는 뒤에서 이야기하겠다.

각 줄의 의미를 살펴보면, 2 번줄은 지정된 소스 파일들에 대한 의존성 파일들을 추출하는 것이다. 3번줄과 4번줄은 각각 타겟 전용으로 지정된 플래그들을 합치는 작업이다.

6번줄부터 8번줄까지는 오브젝트 파일들과 플래그들을 처리하는 부분이다.

10번째와 11번째는 실행 파일을 만들기 위한 부분이다. 실행 파일들은 오브젝트 파일들에 대한 의존성이 있을 테니, 오브젝트 파일들(_OBJS)을 의존성 목록에 지정해주고, 실제 링크할 때 오브젝트뿐만 아니라 공통 링크 플래그(LDFLAGS)와 타겟 전용 플래그(_LDFLAGS) 그리고 공통 라이브러리(LDLIBS)와 전용 라이브러리(_LDLIBS)를 지정한다. $^ 은 의존성 목록에 나와 있는 모든 파일들을 뜻한다. 수동으로 의존성 목록에 다른 파일들을 추가한다면 그대로 반영이 된다. 만일 오브젝트 파일만 적용되기를 원한다면 $^ 을 $($(1)_OBJS) 로 바꾸면 된다.

Make 타겟 만들기


이제 이를 실제 Make 문장으로 바꾸기 위한 작업이 필요한데, 이 때 필요한 것들이 foreach, eval, call 이다.

foreach 는 여러 타겟들을 순환하기 위해 필요하고, call 은 템플릿 변수를 호출하기 위해 필요하며, eval 은 그 결과를 Make 문으로 바꾸기 위해 필요하다. 이렇게 하면된다.

$(foreach prog,$(BIN_PROGRAMS),$(eval $(call program_template,$(prog))))

이 문장을 살펴보면, eval 와 call 이 함께 쓰이고 있다. 따라서 program_template 의 내용은 call 에서 한 번, eval 로 Make 문으로 변환되고 나서 실제 실행될 때 한 번, 이렇게 두 번의 해석이 이루어짐을 알 수 있다. 따라서 $ 가 하나만 쓰이게 되면 call 할 때 치환이 되고, $가 두 개 쓰이게 되면, call 할 때는 $$ 가 $ 로 변환되고, eval 이후 실제 실행될 때 치환이 이루어진다. 결국 call 할 때 이미 변수가 설정되어 있다면 $, eval 할 때 변수가 설정된다면 $$ 를 쓰면 된다.

실제로 위 템플릿을 살펴보면, call 시점에서 이미 설정된 변수는 _SRC, LD, LDFLAGS, EXEEXT 따위이며, 나머지는 eval 이후 실행될 때 설정되는 변수들이다.

foreach 문을 거치고 나면 BIN_PROGRAMS 에 나타나 있는 타겟들에 대한 Make 타겟이 만들어진다.

clean


clean 타겟의 경우, 각 타겟별로 파일들이 생성되므로, 각 타겟별로 순환하면서 지워야 한다. 예를 들어 DEPS 의 경우,

rm -f $(foreach prog,$(BIN_PROGRAMS),$($(prog)_DEPS))

각 타겟들에 대한 _DEPS 가 의존성 파일들이므로, 이를 foreach 로 순환하면서 지울 목록을 만들면 된다.

의존성 파일 포함하기


의존성 파일들을 포함할 때도 마찬가지이다. 지정된 타겟들을 순환하면서 의존성 파일을 포함해야 한다.

-include $(foreach prog,$(BIN_PROGRAMS),$($(prog)_DEPS))

Makefile.common


이 내용들을 정리하면 이렇다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#                                                                              
#   Common parts of GNU Make/GCC build system for multi-targets
#   Copyright (C) 2014 by KO Myung-Hun <komh@chollian.net>
#
#   This file is part of GNU Make/GCC build system for multi-targets
#
#   This program is free software. It comes without any warranty, to
#   the extent permitted by applicable law. You can redistribute it
#   and/or modify it under the terms of the Do What The Fuck You Want
#   To Public License, Version 2, as published by Sam Hocevar. See
#   http://www.wtfpl.net/ for more details.
#

##### Common parts that you don't have to modify

# set compiler and linker
CC  = gcc
CXX = g++
LD  = g++

# default extension of an object file
OBJEXT ?= .o

# default extension of the executable
EXEEXT ?= .exe

.SUFFIXES : .c .cpp $(OBJEXT) $(EXEEXT)

define program_template
$(1)_DEPS := $(foreach s,$($(1)_SRCS),$(s:$(suffix $(s))=.d))
$$($(1)_DEPS) : CFLAGS += $($(1)_CFLAGS)
$$($(1)_DEPS) : CXXFLAGS += $($(1)_CXXFLAGS)

$(1)_OBJS := $$($(1)_DEPS:.d=$(OBJEXT))
$$($(1)_OBJS) : CFLAGS += $($(1)_CFLAGS)
$$($(1)_OBJS) : CXXFLAGS += $($(1)_CXXFLAGS)

$(1)$(EXEEXT) : $$($(1)_OBJS)
    $(LD) $(LDFLAGS) $$($(1)_LDFLAGS) -o $$@ $$^ $(LDLIBS) $$($(1)_LDLIBS)

$(1) : $(1)$(EXEEXT)
endef

%.d : %.c
    $(CC) $(CFLAGS) -MM -MP -MT "$(@:.d=$(OBJEXT)) $@" -MF $@ $<

%.d : %.cpp
    $(CXX) $(CXXFLAGS) -MM -MP -MT "$(@:.d=$(OBJEXT)) $@" -MF $@ $<

%$(OBJEXT) : %.c
    $(CC) $(CFLAGS) -c -o $@ $<

%$(OBJEXT) : %.cpp
    $(CXX) $(CXXFLAGS) -c -o $@ $<

.PHONY : all $(BIN_PROGRAMS) clean

all : $(BIN_PROGRAMS)

$(foreach prog,$(BIN_PROGRAMS),$(eval $(call program_template,$(prog))))

clean :
    rm -f $(foreach prog,$(BIN_PROGRAMS),$($(prog)_DEPS:.d=.bak))
    rm -f $(foreach prog,$(BIN_PROGRAMS),$($(prog)_DEPS))
    rm -f $(foreach prog,$(BIN_PROGRAMS),$($(prog)_OBJS))
    rm -f $(foreach prog,$(BIN_PROGRAMS),$(addsuffix $(EXEEXT),$(prog)))

ifeq ($(filter %clean,$(MAKECMDGOALS)),)
-include $(foreach prog,$(BIN_PROGRAMS),$($(prog)_DEPS))
endif


Makefile.common 자체에 문제가 있거나 새로운 기능이 필요하지 않은 이상, Makefile.common 은 수정할 필요가 없다. 실제 수정이 필요한 부분은 Makefile 로 따로 분리하고 Makefile 에서 Makefile.common 을 포함하면 된다.

Makefile


수정할 부분들은 Makefile 로 따로 모아둔다. Makefile 의 템플릿이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#
#   Configuration parts of GNU Make/GCC build system for multi-targets
#   Copyright (C) 2014 by KO Myung-Hun <komh@chollian.net>
#
#   This file is part of GNU Make/GCC build system for multi-targets
#
#   This program is free software. It comes without any warranty, to
#   the extent permitted by applicable law. You can redistribute it
#   and/or modify it under the terms of the Do What The Fuck You Want
#   To Public License, Version 2, as published by Sam Hocevar. See
#   http://www.wtfpl.net/ for more details.
#

# specify gcc compiler flags for all the programs
CFLAGS :=

# specify g++ compiler flags for all the programs
CXXFLAGS :=

# specify linker flags such as -L option for all the programs
LDFLAGS :=

# specify libraries such as -l option for all the programs
LDLIBS :=

# specify a list of programs without an extension
BIN_PROGRAMS := program

# specify sources for a specific program with program_SRCS. REQUIRED.
# specify various flags for a specific program with program_CFLAGS,
# program_CXXFLAGS, program_LDFLAGS, and program_LDLIBS. OPTIONAL.

program_SRCS     := program.c
program_CFLAGS   :=
program_CXXFLAGS :=
program_LDFLAGS  :=
program_LDLIBS   :=

include Makefile.common


Make 를 실행하면, program.c 를 컴파일/링크하여 program.exe 를 만들어낸다. 적절히 바꾸어 쓰면 된다.

댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷