make
- SW 개발을 위해 유닉스 계열 운영체제에서 사용되는 프로그램 빌드 도구
- 어떤 파일들을 컴파일 하고, 어떤 방식으로 컴파일 할 지 직접 컴파일러에게 알려줘야 함
- 프로젝트의 크기가 커지고 파일들이 많아진다면 매번 명령어를 치기는 힘들어짐
- 이 문제를 해결하기 위해 리눅스에서는 make 라는 프로그램을 제공
- make 프로그램은 Makefile을 읽어서 주어진 방식대로 명령어를 처리함
- 즉, make 명령어를 통해 make 프로그램은 Makefile을 읽고, 많은 수의 파일들을 명령어 한번으로 컴파일 할 수 있음
- 또한, Increment Build를 수행한다는 장점이 있음 (단순 shell script 작성과의 차이점)
💡 빌드(build)
컴파일 언어를 실행파일로 변환하는 과정 (컴파일 + 링크)
> 컴파일(Compile): 소스 코드를 컴퓨터가 이해할 수 있는 어셈블리어로 변환하는 과정 (오브젝트 파일 생성)
> 링크(link): 오브젝트 파일을 하나의 실행파일로 엮어주는 것
💡 빌더(builder)
빌드를 하는 프로그램 ex) C언어 - gcc / C++ - g++
💡 Incremental build
반복적인 빌드 과정에서 변경된 소스코드에 의존성(Dependency)이 있는 대상들만 추려서 다시 빌드하는 기능
(Makefile 에서 빌드 대상(Target)별로 의존성을 명시하면 자동으로 Incremental build 를 수행하므로 매우 편리)
# make 사용 이전
$ g++ -c main.cc
$ g++ -c foo.cc
$ g++ -c bar.cc
$ g++ main.o foo.o bar.o -o main
# make 사용 이후
# Makefile 작성
$ make
Makefile
- 프로그램을 빌드하기 위해 make 문법에 맞춰 작성하는 문서
- 어떠한 조건으로 명령어를 실행할지 작성
- 3가지 요소(target, recipes, prerequisites) 로 구성
- target : 빌드 대상 이름. 최종적으로 생성하는 파일명
- recipe : 빌드 대상을 생성하는 명령어. 여러 줄로 작성 가능. 각 줄 시작은 반드시 Tab 문자로 Indent 삽입
- prerequisites(Dependncies) : 빌드 대상이 의존하는 Target이나 파일 목록. 여기 나열된 대상을 먼저 만들고, recipe 명령어 실행
# Makefile 구성 요소
target … : prerequisites …
(탭)recipe
…
…
# 기본 패턴
CC = <컴파일러>
CFLAGS = <컴파일 옵션>
LDFLAGS = <링크 옵션>
LDLIBS = <링크 라이브러리 목록>
OBJS = <Object 파일 목록>
TARGET = <빌드 대상 이름>
all: $(TARGET)
clean:
rm -f *.o
rm -f $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS)
# Makefile 예시
app.out : main.o foo.o bar.o
gcc -o app.out main.o foo.o bar.o
main.o : foo.h bar.h main.c
gcc -c -o main.o main.c
foo.o : foo.h foo.c
gcc -c -o foo.o foo.c
bar.o : bar.h bar.c
gcc -c -o bar.o bar.c
# Target recipe 생략
app.out : main.o foo.o bar.o
gcc -o gcc.out main.o foo.o bar.o
main.o : foo.h bar.h main.c
foo.o : foo.h foo.c
bar.o : bar.h bar.c
# 한번에 실행파일(app.out) 생성
$ make
# 해당 Target만 빌드
$ make foo.o
변수
Recursively expanded variable
- = 기호를 이용해 변수에 값을 할당
- 변수에 다른 변수를 참조하고 있다면, 다른 변수가 참조하고 있는 값을 참조
- 변수 뒤에 다른 것을 추가하는 경우, 무한 반복에 빠짐
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo) # Huh? 출력
# 무한 반복
# Makefile:1: *** Recursive variable `foo' references itself (eventually). Stop.
foo = $(foo) -o
all:;echo $(foo)
Simply expanded variable
- := 기호를 이용해 변수에 값을 할당
- 재귀적으로 작동하지 않으며, 변수에 대입된 값을 그대로 출력
x := hello
x := $(x) world
all:;echo $(x)
# 실행 결과
echo hello world
hello world
함수
# 기본 문법
$(function arguments)
리링크
의존성이 변경되었을 때만 타겟을 생성하는 것
PHONY
- Target 위치에 작성하지만, 레시피 실행을 위한 이름일 뿐, 실제 파일 이름이 아니라는 것을 알려주기 위해 사용
- Makefile 에 흔히 추가하는 기능으로 빌드 관련된 파일들 (.o 파일들)을 모두 제거하는 명령을 넣음
- 그런데 만약 clean이라는 파일이 디렉토리에 생성된다면, make 는 "clean 의 필요 파일들이 없는데, clean 파일이 있으니까 clean 파일은 항상 최신이네? recipe 를 실행 안해도 되겠네!" 하면서 그냥 make clean 명령을 무시해버림
- 이와 같은 상황을 막기 위해서 clean 을 PHONY 라고 등록하면, make clean 시 clean 파일의 유무와 상관 없이 언제나 해당 타겟의 명령을 실행
- 보통 맨 윗줄에 작성하는 것이 편리
# Makefile에 흔히 추가하는 clean 기능
clean:
rm -f $(OBJS) main
# clean을 PHONY라고 등록
.PHONY: clean
clean:
rm -f $(OBJS) main
패턴 사용하기
- 프로젝트에서 수십 ~ 수백 개의 파일들을 다루는 경우, 각각의 파일들에 대해 모두 빌드 방식을 명시해준다면 Makefile 크기가 매우 커질 수 있음
- Makefile에서는 패턴 매칭을 통해 특정 조건에 부합하는 파일들에 대해 간단하게 recipe을 작성할 수 있도록 해줌
- $@ : 타겟 이름에 대응
- $< : 의존 파일 목록에 첫 번째 파일에 대응
- $^ : 의존 파일 목록 전체에 대응
- $? : 타겟 보다 최신인 의존 파일들에 대응
- $+ : $^ 와 비슷하지만, 중복된 파일 이름들 까지 모두 포함
- % 기호는 와일드카드와 비슷한 역할
CC = g++
CXXFLAGS = -Wall -O2
SRCDIR = src
BUILDDIR = build
# 패턴 미사용
build/file1.o: src/file1.cc src/file1.h
$(CC) $(CXXFLAGS) -c src/file1.cc -o build/file1.o
build/file2.o: src/file2.cc src/file2.h
$(CC) $(CXXFLAGS) -c src/file2.cc -o build/file2.o
build/file3.o: src/file3.cc src/file3.h
$(CC) $(CXXFLAGS) -c src/file3.cc -o build/file3.o
# 패턴 사용
# % 는 와일드카드 (*와 동일한 역할)
$(BUILDDIR)/%.o: $(SRCDIR)/%.cc $(SRCDIR)/%.h
$(CC) $(CXXFLAGS) -c $< -o $@
자동으로 prerequisite 만들기
# -MD 옵션을 추가해서 컴파일
$ g++ -c -MD main.cc
# main.d 라는 파일 생성됨
$ cat main.d
main.o: main.cc /usr/include/stdc-predef.h foo.h bar.h
# Makefile
CC = g++
CXXFLAGS = -Wall -O2
OBJS = foo.o bar.o main.o
%.o: %.cc %.h
$(CC) $(CXXFLAGS) -c $<
main : $(OBJS)
$(CC) $(CXXFLAGS) $(OBJS) -o main
.PHONY: clean
clean:
rm -f $(OBJS) main
include main.d
컴파일 옵션
-Wall (모든 컴파일 경고를 표시)
-O2 (최적화 레벨 2)
GNU make 설치
'언어 > C, C++' 카테고리의 다른 글
[C/C++] 공백을 포함한 파일을 문자열 변수에 쓰기 (0) | 2024.05.27 |
---|---|
CMake, CMakeLists.txt 정리 (0) | 2024.02.19 |
[C++] stringstream (0) | 2024.01.02 |
RapidJSON 정리 (0) | 2023.12.27 |
json-c 정리 (1) | 2023.12.26 |