make란?
: 이전 게시물에서 gcc를 통한 컴파일 명령어들을 살펴 보았다. gcc의 문제는 프로젝트의 크기가 커질 때, gcc를 통한 컴파일이 상당히 번거로운 작업이 될 것이다. 따라서 이를 위해 파일간의 종속 관계를 기술해 컴파일 명령을 순차적으로 수행하는 것이 make이다.
간단한 Makefile
다음과 같은 파일 구조가 있다고 가정하자.
jay@jay-VirtualBox:~/tutorial/make$ tree .
.
├── plus.c
├── calculator.h
├── main.c
└── minus.c
이를 위한 간단한 Makefile은 다음과 같을 것이다.
all: calculator
calculator : minus.o plus.o main.o
gcc -W -Wall -o calculator minus.o plus.o main.o
minus.o : minus.c
gcc -W -Wall -c -o minus.o minus.c
plus.o : plus.c
gcc -W -Wall -c -o plus.o plus.c
main.o : main.c
gcc -W -Wall -c -o main.o main.c
clean :
rm -rf *.o diary
여기서 make
명령어를 사용하면, Makefile 내에서 제일 처음 오는 타겟 을 찾는다.all
타겟의 종속 항목인 minus.o
가 만들어지지 않았으므로 minus.o를 생성하는 룰을 찾아 해당 명령을 수행하며, 이는 반복적으로 수행되어진다.
매크로를 사용한 Makefile
: 매크로를 정의하면 이는 Makefile 내에 정의된 문자열로 치환되어 사용되어 진다. 이는 일관성있고 이식성 높은 Makefile을 만드는 데 도움을 준다. 다음은 매크로 작성의 규칙이다.
#
은 주석문을 의미한다.- 정의한 매크로를 참조할 때에는 다음의 방법이 있다.
NAME = string
${NAME}
$(NAME)
- 여기서 중괄호는 셸의 환경변수 치환에도 사용되므로, 소괄호를 사용하는 것을 권장한다.
- 정의되지 않은 매크로는 null 문자열(공백)로 치환된다.
- 중복 정의된 경우 최후에 정의된 값으로 치환된다.
- 매크로 정의 시, 이전에 정의한 값을 참조해 정의할 수 있다.
- NAME2 = my valuable $(NAME)
- 대입에는 다음의 방법들이 있다.
NAME1 = string
: 재귀적 확장 매크로, 대입문에 post-defined variable이 사용될 경우 이를 계속적으로 스캔하며 대입한다.NAME2 := string
: 단순 확장 매크로, 대입문에 pre-defined variable만이 대입된다.NAME3 += string
: 기존의 매크로에 공백을 두고 덧 붙인다.NAME4 ?= string
: NAME4가 기존에 정의되어있지 않은 경우 이 명령을 통해 정의하고, 그렇지 않다면 무시한다.
NOTE
대입문에 따옴표(")를 넣으면 따옴표(") 또한 문자열로 인식하여 대입한다.
내부적으로 정의되어 있는 매크로
: 내부적으로 정의되어 있는 매크로를 확인하기 위해서는 make -p
명령을 사용하면 된다. 다음은 그 일부를 나타낸다.
# default
CC = cc
# environment
_ = /usr/bin/make
# default
CHECKOUT,v = +$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@)
# environment
MANAGERPID = 1220
# environment
CLUTTER_IM_MODULE = ibus
# environment
LESSOPEN = | /usr/bin/lesspipe %s
# environment
LC_NAME = ko_KR.UTF-8
# default
CPP = $(CC) -E
# default
LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
MAKE_HOST := x86_64-pc-linux-gnu
# environment
PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# default
LD = ld
...
...
# Implicit Rules
%: %.o
# recipe to execute (built-in):
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.c:
%: %.c
# recipe to execute (built-in):
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.ln: %.c
# recipe to execute (built-in):
$(LINT.c) -C$* $<
%.o: %.c
# recipe to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
%.cc:
%: %.cc
# recipe to execute (built-in):
$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.o: %.cc
# recipe to execute (built-in):
$(COMPILE.cc) $(OUTPUT_OPTION) $<
여기서 주요하게 살펴볼 것들은 CC
, CXX
, CFLAGS
, CXXFLAGS
, LD
등이 있다.
또한 다음의 자동 매크로 리스트를 살펴보자.
매크로 | 설명 |
---|---|
$? |
현재 타겟보다 최근에 변경된 종속 항목 리스트 (확장자 규칙에서 사용 불가) |
$< |
현재 타겟보다 최근에 변경된 종속 항목 리스트 (확장자 규칙에서만 사용 가능) |
$* |
현재 타겟보다 최근에 변경된 종속 항목의 이름(확장자 제외) (확장자 규칙에서만 사용 가능) |
$^ |
현재 타겟의 종속 항목 리스트 (확장자 규칙에서 사용 불가) |
$@ |
현재 타겟의 이름 |
$% |
현재 타겟이 라이브러리 모듈일 때, .o 파일에 대응되는 이름 |
위의 자동 매크로를 이용하면 아래와 같이 편리하게 기술할 수 있다.
target : dep1.c dep2.c
gcc -o target dep1.c dep2.c
CC = gcc
CFLAGS = -W -Wall
TARGET = calculator
all : $(TARGET)
$(TARGET) : dep1.c dep2.c
$(CC) $(CFLAGS) -o $@ $^
확장자 규칙의 사용
: *.c 파일을 오브젝트로 컴파일(gcc의 -c
옵션)하면 그에 상응하면 *.o파일이 생성된다. 이를 이용한 것이 make -p
명령어의 아래에서 볼 수 있는
%.o: %.c
# recipe to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
등의 pre-defined 명령문이다. 따라서 gcc는 확장자 규칙에 맞게 생성된 파일들(*.cc, *.java, *.c)의 파일을 보고 암묵적으로 적절한 컴파일러를 이용해 컴파일한다. 이를 이용하면 위의 Makefile은 더욱 간단해 질 수 있다.
OBJECTS = minus.o plus.o main.o
all : calculator
calculator : $(OBJECTS)
$(CC) -o $@ $^
clean :
rm -rf *.o calculator
위의 기술문에서 calculator를 생성하기 위한 종속항목들(minus.o, plus.o, main.o)을 생성하기 위해 확장자가 .o
인 내부 확장자 규칙을 이용해 현재 디렉토리에서 minus.o, plus.o, main.o
를 생성할 파일을 찾는다. 이러한 파일들은 확장자를 제외하고 동일한 이름을 가지고 있어야 한다.
더 나아가서 추가적으로 내부 확장자 규칙을 재 정의할 수 있다. 내부 확장자 규칙에서 디버깅 메시지를 활성화하고 싶은 경우, 다음과 같이 재정의 할 수 있다.
OBJECTS = minus.o plus.o main.o
.SUFFIXES : .o .c
%.o : %.c # .o와 대응되는 .c를 발견 시 아래 명령어를 수행한다.
$(CC) -DDEBUG -c -o $@ $<
all : calculator
calculator : $(OBJECTS)
$(CC) -o $@ $^
clean :
rm -rf *.o calculator
여기서 확장자 규칙을 재 정의하기위해 .SUFFIXES
라는 특수 타겟을 사용했다. .SUFFIXES
의 종속 항목은 확장자 규칙을 검사하는 데 사용되는 확장자들의 리스트이다. 따라서 확장자 규칙을 사용하려는 확장자는 먼저 .SUFFIXES
타겟의 종속 항목으로 설정되어야 한다.
여기서 %
는 일치하는 확장자를 제외한 파일명을 의미한다.
프로젝트의 파일이 수 없이 많을 경우 이를 모두 기술하는 것은 거의 불가능에 가깝다. 따라서 다음과 같이 편리하게 만들 수 있다.
SRCS = $(shell ls *.c)
SRCS = $(wildcard *.c)
OBJECTS = $(SRCS:.c=.o)
$(shell 실행할 셸 명령)
: 셸 명령을 수행하고 결과를 리턴한다.$(wildcard *.c)
: *.c와 일치하는 파일들을 공백으로 구분하여 대입$(SRCS:.c=.o)
: 대입 참조기법을 사용해 .c가 .o로 바뀐다.
NOTE
clean:
cd ~/Desktop/tutorial
rm -rf *.o
@echo "with @ option print only result, not with command itself"
위와 같은 명령을 수행하면 원하는 결과를 얻지 못할 수 있다.
각각의 명령은 새로운 셸을 띄워
/bin/sh -c cd ~/Desktop/tutoril
/bin/sh -c rm -rf *.o
를 수행하므로 이에 유의해야한다.
.
NOTE
echo $?
위 명령을 수행하면 직전에 수행한 명령의 리턴값을 나타낸다. 0일 경우 ERROR_NONE을 나타낸다.
재귀적 make의 사용
all : calculator.o plus.o minus.o main.o
$(CC) -o calculator $^
calculator.o:
cd calculator && make
...
: 재귀적으로 Makefile을 선언해 사용할 경우, 부모 Makefile에 기술한 매크로를 자식 Makefile에 전달하고 싶을 때가 있다. 이 경우 다음과 같이
TARGET := calculator
export CC = gcc
export
구문을 사용한다. 만약 모든 매크로에 대해 전달하고 싶을 경우 export
구문을 단독으로 사용하면 된다.
'빌드&테스트도구 > GCC&Make&CMake' 카테고리의 다른 글
CMake - (1) CMake의 전반적인 사항들 (0) | 2020.11.08 |
---|---|
GCC - (4) 링커(ld)에 관하여 (0) | 2020.09.13 |
GCC - (3) gcc의 각종 옵션 (0) | 2020.09.09 |
GCC - (2) gcc의 세부 과정 (0) | 2020.09.07 |
GCC - (1) C 컴파일 과정 개요 (0) | 2020.09.02 |