Makefile
개요
make는 개발자가 작성한 소스 코드에서 최종 실행파일을 얻기 위해 컴파일, 링크 등 일련의 단계를 연속적으로 자동으로 수행해주는 ‘빌드 도구’의 하나로, 오래전부터 리눅스 등 유닉스 계열 OS에서 표준으로 사용되고 있다.
make에서는 개발자가 수행하고자 하는 일련의 절차를 Makefile에 기술하고, make 명령어에 이를 주면 내용을 해석하여 명령어 실행 등을 연속적으로 자동으로 수행하며, make 명령어 한 번 실행으로 실행파일 생성이 완료된다.
Makefile이란?
Makefile에는 컴파일러, 소스 파일, 생성물, 빌드 순서, 빌드 순서, 종속성 등을 기술하고, make
명령을 실행만 하면 빌드할 수 있다. 그 내용을 보면 프로그램 빌드 방법을 알 수 있다. 또한 make는 업데이트가 필요한 파일을 자동으로 판단해 컴파일 횟수를 최소화해 개발 시간을 단축할 수 있다.
유명한 GNU에서 제공하는 GNU Make의 Home Page에 따르면 Make는 다음과 같은 Tool이라고 한다.
- GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files.
이에 따르면 다음과 같은 장점이 있는 것 같다.
- 앤드유저는 그 방법을 자세히 몰라도 패키지를 빌드하고 설치할 수 있다.
- 변경된 소스 파일을 기반으로 업데이트가 필요한 파일을 자동으로 파악하고, 적절한 순서를 자동으로 결정한다.
- Make는 C 언어뿐만 아니라 다른 컴파일 언어도 지원한다.
Make - GNU Project - Free Software Foundation
Makefile 기본 작성법
많은 라이브러리를 사용하는 C/ C++을 빌드할 때 make를 사용하면 유용하다. make를 사용하려면 대상 Makefile이 필요한데, 이번에는 그 작성법을 소개한다.
Makefile의 기본 구문은 두 줄로 한 단위이며, 윗줄에 ‘Target: SourceFile’, 아랫줄에 ‘실행할 명령어(맨 앞에 탭 문자)‘를 각각 기술한다. 줄 바꿈을 ‘;’(세미콜론)으로 대체하여 한 줄로 묶을 수도 있다. ‘변수명=값’ 형태로 변수를 정의할 수도 있으며, 파일명 등의 지정에 변수를 사용할 수 있다.
의존 관계를 기록하는 부분이다.
Target : [Prerequisites]
Recipe
- Target
- 프로그램에 의해 생성되는 파일의 이름이다.
- Prerequisites
- 전제 조건
- 타겟을 생성하기 위한 입력으로 사용되는 파일이다.
- 여러 파일도 가능하고, 생략할 수도 있다.
- Recipe
- recipe가 성립하기 위해서는 prerequisites가 필요하다.
- make가 실행할 액션이다.
- 여러 줄 가능하다. (행을 분할할 경우
\
를 사용한다) - 열 앞에는 Tab 입력은 필수이다.
빌드할 때 사용하는 명령어를 다음과 같은 규칙에 따라 작성한다.
Target: SourceFile
Command
간단한 예로 들면 아래와 같이 작성할 수 있다.
hello: hello.c
gcc -o hello hello.c
그러고, 생성된 파일을 삭제하거나 복사하는 명령도 추가로 작성할 수도 있다.
clean:
rm -f *~ hello
install: hello
install -s hello.exe Path
make
를 실행할 때에 아무런 인자를 지정하지 않으면 맨 위에 있는 Target이 실행된다.
위의 clean
이나 install
을 실행하고 싶다면 아래와 같이 인수를 지정한다.
make clean
make install
Makefile이 아닌 다른 파일을 Makefile로 지정하고 싶다면, -f
옵션으로 다음과 같이 실행할 수 있다.
``shell
make -f sample.mk
또한, 요소를 변수로 취급할 수 있다. 간단한 예는 다음과 같다.
```makefile
CXX = g++
OPTIMIZE = -O3
CFLAGS = -IC:/Users/include \
-IC:/Python/include
DEST = C:/Users/Local
LDFLAGS = -LC:/Users/Local/libs
LIBS = -lpython
OBJS = hello
all: clean $(PROGRAM) install
$(PROGRAM): $(OBJS)
$(CXX) -o $(OBJS) $(OBJS).cpp $(CFLAGS) $(LDFLAGS) $(LIBS)
clean: rm -f *~ $(OBJS)
install: $(PROGRAM)
install -s $(OBJS).exe $(DEST)
Makefile 변수
Makefile의 변수는 두 가지가 있다. 암묵적 변수와 새로 정의되는 변수이다. 암묵적 변수는 암묵적 규칙에서 사용되는 특정 사전 정의된 변수를 말한다.
암묵적 규칙이란?
다양화되는 규칙은 굳이 설명하지 않아도 미리 암묵적인 정의된 변수가 있다.
C 소스 파일의 컴파일에 사용되는 레시피는 실제로 $(CC) -c $(CFLAGS) $(CPPFLAGS)
라는 코드가 실행되고 있다.
CC
, CFLAGS
, CPPFLAGS
는 미리 변수로 정의되어 있으며, 이 내용을 덮어써서 컴파일 레시피를 다시 작성할 수도 있다.
암묵적 변수 목록
주요한 변수 항목은 아래와 같다.
변수 이름 | 설명 | default |
---|---|---|
AR | 아카이브 메인테넌스 프로그램 | ar |
AS | 어셈블리 실행 프로그램 | as |
CC | C 프로그램 컴파일 프로그램 | cc |
CXX | C++ 프로그램 컴파일 프로그램 | g++ |
CO | RCS의 배포 프로그램 | co |
CPP | C 전처리 프로그램, 표준 출력에 결과 | $(CC) -E |
FC | Fortranr과 Ratfor 프로그램을 위한 컴파일 또는 전처리기 프로그램 | f77 |
GET | SCCS의 배포 프로그램 | get |
LEX | Lex 문법을 C 또는 Ratfor로 변환하는 프로그램 | lex |
PC | Pascal 프로그램 컴파일 프로그램 | pc |
YACC | Yacc 문법을 C 또는 Ratfor로 변환하는 프로그램 | yacc |
YACCR | Yacc 문법을 Ratfor로 변환하는 프로그램 | yacc -r |
MAKEINFO | Texinfo 소스 파일을 Info 파일로 변환하는 프로그램 | makeinfo |
TEX | TeX 소스에서 TeX DVI 파일을 만드는 프로그램 | tex |
TEXI2DVI | Texinfo 소스에서 TeX DVI 파일을 만드는 프로그램 | texi2dvi |
WEAVE | 웹을 TeX로 번역하는 프로그램 | weave |
CWEAVE | C Web을 TeX로 번역하는 프로그램 | cweave |
TANGLE | 웹을 파스칼로 번역하는 프로그램 | tangle |
CTANGLE | C 웹을 C로 번역하는 프로그램 | ctangle |
RM | 파일을 삭제하는 명령 | rm -f |
ARFLAGS | 아카이브 유지 보수 프로그램에 제공하는 플래그 | rv |
ASFLAGS | 어셈블러에 주는 플래그(.s 또는 .S 파일에 대해 명시적으로 호출되는 경우) | 없음 |
CFLAGS | C 컴파일러에 주는 플래그 | 없음 |
CXXFLAGS | C++ 컴파일러에 주는 플래그 | 없음 |
COFLAGS | RCS co 프로그램에 주는 플래그 | 없음 |
CPPFLAGS | C 프리프로세서 및 프로그램에 제공하는 플래그(C 및 Fortran 컴파일러) | 없음 |
FFLAG | Fortran 컴파일러에 주는 플래그 | 없음 |
GFLAG | SCCS get 프로그램에 주는 플래그 | 없음 |
LDFLAGS | 링커 ld를 호출할 때 컴파일러에 주는 플래그 | 없음 |
LFLAGS | Lex에게 주는 플래그 | 없음 |
PFLAGS | Pascal 컴파일러에 주는 플래그 | 없음 |
RFLAGS | Ratfor 프로그램에 대한 Fortran 컴파일러에 제공하는 플래그 | 없음 |
YFLAGS | Yacc에게 주는 플래그 | 없음 |
https://www.gnu.org/software/make/manual/make.html#Implicit-Variables
변수 정의
Makefile 내에서 변수를 정의하는 방법에는 두 가지가 있다.
기호 | 상세 |
---|---|
= |
재귀적으로 확장되는 변수 |
:= |
단순하게 전개되는 변수 |
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo) # => Hub?
변수 호출
$(name)
, 또는 ${name}
objects = program.o foo.o utils.o # => program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
규칙 내에서 작동하는 자동 변수
자동변수는 타겟과 전제조건을 호출할 때 사용할 수 있는 표기법이다. 약어로 사용할 수 있을 뿐만 아니라, 조건에 따라 필요한 것만 참조하는 기능을 가진 것도 있다.
자동 변수 | 기능 |
---|---|
$@ |
대상 이름 |
$< |
종속성의 첫 번째 이름 |
$^ |
대상의 모든 종속성 이름 |
$? |
대상보다 타임스탬프가 새로운 종속성의 이름 |
$+ |
대상의 모든 종속성 이름 |
$* |
대상의 패턴 매칭과 일치하는 부분 |
$@
현재 타켓 이름.
변수 | 설명 |
---|---|
$@ |
규칙의 타겟 이름이다. $(@) 로 써도 같은 의미를 가진다. |
(@D) |
규칙 타겟의 디렉토리 이름. |
(@F) |
규칙 타겟의 파일 이름. |
aaa/bbb/foo:
echo $@ # => aaa/bbb/foo
echo $(@D) # => aaa/bbb
echo $(@F) # => foo
$<
전제 조건의 첫 번째 이름이다.
변수 | 설명 |
---|---|
$< |
전제 조건의 첫 번째 이름이다. $(<) 로 써도 같은 의미를 가진다. |
$(<D) |
전제 조건의 첫 번째 디렉토리 이름. |
$(<F) |
전제 조건의 첫 번째 파일 이름. |
output/foo: input/bar input/baz
echo $< # => input/bar
echo $(<D) # => input
echo $(<F) # => bar
$^
대상의 모든 전제 조건의 이름이다.
변수 | 설명 |
---|---|
$^ |
대상의 전제 조건의 이름이다. $(^) 로 써도 같은 의미를 갖는다. |
$(^D) |
대상 전제 조건의 디렉토리 이름. |
$(^F) |
대상 전제 조건의 파일 이름. |
output/foo: input/bar input/baz
echo $^ # => input/bar input/baz
echo $(^D) # => input input
echo $(^F) # => bar baz
$?
대상보다 타임스탬프가 새로운 전제 조건의 이름이다.
변수 | 설명 |
---|---|
$? |
대상보다 새로운 모든 전제 조건의 이름이다. $(+) 로 써도 같은 의미를 가진다. |
(?D) |
대상보다 새로운 모든 전제 조건의 디렉토리 이름. |
(?F) |
대상보다 새로운 모든 전제 조건의 파일 이름. |
output/foo: input/bar input/baz
echo $? # => input/bar
echo $(?D) # => input
echo $(?F) # => bar
$?
타겟보다 타임스탬프가 새로운 전제 조건의 이름이다.
변수 | 설명 |
---|---|
$? |
대상보다 새로운 모든 전제 조건의 이름이다. (?) 로 써도 같은 의미를 가진다. |
$(?D) |
대상보다 새로운 모든 전제 조건의 디렉토리 이름. |
$(?F) |
대상보다 새로운 모든 전제 조건의 파일 이름. |
output/foo: input/bar input/baz
echo $? # => input/bar
echo $(?D) # => input
echo $(?F) # => bar
$+
대상의 모든 전제 조건의 이름(중복이 있어도 생략하지 않음). 일반적으로 $^가 더 많이 사용된다.
변수 | 설명 |
---|---|
$+ |
중복이 포함된 대상의 전제 조건의 이름이다. $(+) 로 써도 같은 의미를 가진다. |
$(+D) |
중복이 포함된 대상 전제 조건의 디렉토리 이름. |
$(+F |
) 중복이 포함된 대상 전제조건의 파일명. |
output/foo: input/baz input/baz input/baz
echo $+ # => input/baz input/baz input/baz
echo $(+D) # => input input input
echo $(+F) # => baz baz baz
$*
대상의 패턴과 일치하는 부분이다. 관련 파일을 생성할 때 등에 유용하다.
변수 | 설명 |
---|---|
$* |
대상의 패턴과 일치하는 부분이다. $(*) 로 써도 같은 의미를 가진다. |
$(*D) |
대상의 패턴과 일치하는 부분의 디렉토리 이름. |
$(*F) |
대상의 패턴과 일치하는 부분의 파일명. |
Makefile의 함수
Make에는 Makefile 내에서 문자열 처리와 조건부 분기를 위한 함수가 있다. 주요 함수는 다음과 같다.
텍스트 변환을 위한 함수
함수는 makefile에서 변수를 정의할 때, 텍스트 조작을 하기 위해 사용된다. 여기서는 주요 함수를 소개한다.
함수는 다음과 같은 구문으로 호출된다.
$(function arguments)
${function arguments}
참고: https://www.gnu.org/software/make/manual/make.html#Text-Functions
shell 함수
쉘을 실행한다.
files := $(shell echo *.c)
INCLUDE := $(shell find $(INCDIRS) -type d)
SRCDIR = ./srcs
SRCS := $(shell find $(SRCDIR) -name *.c)
all:
echo $(files) # => hoge.c foo.c
echo $(INCLUDE) # => include
echo $(SRCDIR) # => ./srcs
echo $(SRCS) # => hoge.c foo.c
RESULT = $(shell seq 1 10)
all:
echo $(RESULT) # => 1 2 3 4 5 6 7 8 9 10
addprefix 함수
앞에 문자열을 추가한다. -l 옵션 등을 사용할 때 유용할 것 같다.
$(addprefix prefix,names...)
FILES := foo bar
all:
echo $(addprefix src/,$(FILES)) # => src/foo src/bar
dir 함수
파일 이름에 대한 함수. 디렉토리만 추출한다.
$(dir names...)
names의 파일명에서 디렉토리 부분을 추출한다.
파일명의 디렉토리 부분은 마지막 슬래시 이전의 모든 부분이다.
슬래시를 포함하지 않으면 디렉토리 부분은 문자열 ./
가 된다.
FILES := src/hoge.c src/hoge.h index.html
all:
echo $(dir $(FILES)) # => src/ src/ ./
notdir
파일명 검색하는 함수
FILES := ./dir/hoge.txt
all:
echo $(notdir $(FILES)) # => hoge.txt
대체 참조
바꾸기 참조는 변수의 값을 지정된 변경 사항으로 대체한다.
이는 $(var: 대체전 = 대체후)
(또는 ${var: 대체전 = 대체후}
)의 형태로 변수 var의 값을 가져와 단어 끝에 있는 모든 a를 b로 대체하고 결과 문자열을 대체한다.
그러고, 단어 내 임의의 수의 문자와 일치하는 와일드카드 역할을 한다.
대체 참조는 patsubst
함수의 약어이다.
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
all:
echo $(bar) # => a.c b.c c.c
patsubst
함수를 지정한 경우
$(patsubst pattern(대체전), replacement(대체후), text(대상))
foo := a.o b.o c.o
bar := $(patsubst %.o,%.c,$(foo))
all:
echo $(bar) # => a.c b.c c.c
realpath
존재하는 전체 경로 가져오기
"$(realpath ./dir/hoge.txt)"//C:users/hoge/bin/hoge.txt
suffix
확장자 가져오기
"$(suffix ./dir/hoge.txt)"//.txt
wildcard
와일드카드를 사용하여 존재하는 파일명 검색
"$(suffix ./dir/*.txt)"//hoge.txt hogehoge.txt
조건부 분기를 위한 함수
if
조건부 분기
"$(if $(VAR1),$(exist),$(none))"
ifeq
조건부 분기별 작성법
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
Makefile 디버깅 방법
GNU Make의 -n
옵션은 Makefile의 명령을 실행하지 않고 출력한다.
Makefile이 아래와 같이 정의되어 있을 때,
CC = gcc
OBJ = hoge.c
CFLAGS = -c $(OBJ)
처리 출력
make
명령어 -n
옵션을 아래와 같이 넣어 실행하면, make가 어떤 처리를 하는지 출력할 수 있다.
$ make -n
gcc -c hoge.c -o hoge
어떻게 작성되는 확인 할때, 유용하게 사용할 수 있다.
warning 함수
make 실행 시 내용을 출력해 준다.
$(warning MAKE = $(MAKE)) # => Makefile:18: MAKE = /Library/Developer/CommandLineTools/usr/bin/make
$(warning CC = $(CC)) # => Makefile:19: CC = gcc
$(warning CFLAGS = $(CFLAGS)) # => Makefile:20: CFLAGS = -Wall -Wextra -Werror
g++와 make와 cmake의 install 방법
Windows에서 설치
Windows OS에서 g++/gcc는 MinGW를, make는 GnuWin을, cmake는 cmake.zipinstall을 통해 사용할 수 있다.
GUI로 g++ install
- MinGW-w64 - for 32 and 64 bit Windows download | SourceForge.net에서 installer를 Download하여 설치한다.
GUI로 make install
- Make for Windows에서 installer를 Download하여 설치한다.
C:Program Files (x86)\GnuWin32\bin
으로 경로를 지정한다.
GUI로 cmake를 install
- Download CMake에서 installer를 Download하여 설치한다.
CUI에서 g++ & make를 install한다.
choco install mingw
choco install make
환경 변수 설정
아래의 path 등에 설치되므로 path를 통해 설치한다.
C:\Program Files (x86)\MinGW\bin
C:\Program Files (x86)\GnuWin32\bin
리눅스에서 설치
일괄적으로 install
sudo apt install build-essential
개별적으로 install
sudo apt install g++
sudo apt install make
정리
Makefile 작성 방법에 대해 정리해 보았다. 비망록으로 업데이트해 나갈 것이다.