- Makefile은 파일 변경을 기준으로 필요한 빌드 단계만 다시 실행해, 대형 프로젝트의 C/C++ 컴파일을 자동화하는 데 주로 쓰임
- Make는 대상과 의존성의 파일시스템 타임스탬프를 비교해, 대상이 없거나 의존성이 더 최신일 때만 명령을 실행함
- 기본 규칙은
targets: prerequisites와 탭으로 들여쓴 명령으로 이루어지며, 공백 들여쓰기는make실패로 이어질 수 있음 - 변수, 와일드카드, 자동 변수, 패턴 규칙, 조건문, 함수, 재귀 make,
include,.PHONY,.DELETE_ON_ERROR로 빌드 흐름을 확장할 수 있음 - Cookbook 예제는
src/의 C/C++ 소스를build/산출물로 만들고,-MMD -MP와-include로 자동 의존성 관리를 구성함
Makefile이 필요한 상황
- Makefile은 대형 프로그램에서 어떤 파일을 다시 컴파일해야 하는지 판단하는 데 사용됨
- 튜토리얼의 중심 사례는 C/C++ 파일 컴파일임
- 파일 변경 여부에 따라 일련의 명령을 실행해야 하는 작업에도 Make를 컴파일 외 용도로 활용할 수 있음
- C/C++의 다른 빌드 도구로 SCons, CMake, Bazel, Ninja가 소개됨
- Java에는 Ant, Maven, Gradle이 있고, Go, Rust, TypeScript 같은 언어는 자체 빌드 도구를 가짐
- Python, Ruby, 순수 JavaScript 같은 인터프리터 언어는 파일이 바뀌어도 다시 컴파일할 필요가 없어 Makefile 같은 도구가 필수적이지 않음
Make의 기본 실행 모델
- 예제는
make가 설치된 터미널에서Makefile파일을 만든 뒤, 해당 디렉터리에서make를 실행하는 방식으로 동작함 - 기본 규칙은 아래 형태를 따름
targets: prerequisites- 탭으로 시작하는
command
- 대상(target) 은 보통 파일 이름이며, 명령은 해당 대상을 만들기 위한 단계임
- 의존성(prerequisites) 은 명령 실행 전에 존재해야 하는 파일이며, dependencies라고도 부름
- Makefile의 명령 줄은 반드시 탭 문자로 들여써야 하며, 공백을 쓰면
make가 실패함
대상과 의존성의 시간 비교
make hello는hello대상에 연결된 명령을 실행하지만,hello파일이 이미 있으면 명령을 실행하지 않음- 대상과 파일 이름은 직접 연결되며, 일반적으로 대상의 명령은 대상과 같은 이름의 파일을 만듦
blah: blah.c규칙에서는blah가 없거나blah.c가blah보다 더 최신일 때만 컴파일 명령이 실행됨- Make는 변경 이력을 직접 추적하지 않고 파일시스템 타임스탬프를 휴리스틱으로 사용함
- 파일을 수정한 뒤 수정 시간을 과거로 바꾸면 Make가 해당 파일이 바뀌지 않았다고 잘못 판단할 수 있음
기본 타깃과 clean
make에 대상을 지정하지 않으면 Makefile의 첫 번째 대상이 기본 대상으로 실행됨all대상은 여러 대상을 한 번에 만들 때 쓰이며, 첫 번째 규칙으로 두면make만 실행해도 전체 대상이 빌드됨clean은 보통 빌드 산출물을 삭제하는 대상으로 쓰이지만, Make의 특별한 예약어는 아님clean이 첫 번째 대상도 아니고 다른 대상의 의존성도 아니면make clean처럼 명시적으로 호출해야 실행됨clean이라는 파일이 실제로 존재하면clean대상이 실행되지 않을 수 있어, 뒤에서.PHONY로 해결함
변수와 문자열 처리
- Make 변수는 문자열만 가질 수 있으며, 튜토리얼은 일반적으로
:=사용을 권장함 - 변수 참조는
$(name)또는${name}형식을 사용함 - 작은따옴표와 큰따옴표는 Make 자체에서는 특별한 의미가 없고, 변수에 들어가는 문자일 뿐임
- 변수 정의 방식은 크게 두 가지임
=: 재귀 변수로, 사용될 때 변수를 찾아 확장함:=: 단순 확장 변수로, 정의 시점에 이미 정의된 값만 확장함
?=는 아직 설정되지 않은 변수에만 값을 설정함+=는 변수에 값을 추가함- 정의되지 않은 변수는 빈 문자열로 취급됨
- 줄 끝의 공백은 제거되지 않으며, 한 칸짜리 공백 변수를 만들려면
$(nullstring)같은 기법을 사용할 수 있음
와일드카드와 자동 변수
- Make에서
*와%는 모두 와일드카드로 불리지만 의미가 다름 *는 파일시스템에서 일치하는 파일 이름을 찾으며, 직접 변수 정의에 쓰기보다$(wildcard *.o)처럼 wildcard 함수로 감싸는 편이 안전함*가 아무 파일과도 일치하지 않으면wildcard함수 안이 아닌 경우 그대로 남을 수 있음%는 규칙과 특정 함수에서 사용되며, 일치한 부분은 stem으로 불림- 자주 쓰이는 자동 변수는 아래와 같음
$@: 대상 이름$?: 대상보다 최신인 의존성$^: 모든 의존성$<: 첫 번째 의존성
암묵 규칙과 패턴 규칙
- Make는 C 컴파일을 위한 암묵 규칙을 갖고 있어, 명시적으로 컴파일 명령을 쓰지 않아도 빌드가 진행될 수 있음
- 대표적인 암묵 규칙은 아래 형태임
n.o는n.c에서$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@형태로 생성됨n.o는n.cc또는n.cpp에서$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@형태로 생성됨n은n.o에서 링커 명령으로 생성됨
- 암묵 규칙에서 중요한 변수는
CC,CXX,CFLAGS,CXXFLAGS,CPPFLAGS,LDFLAGS임 - 정적 패턴 규칙은
targets...: target-pattern: prereq-patterns ...형태로, 대상에서%가 일치한 stem을 의존성 패턴에 대입함 - 패턴 규칙은
%.o: %.c처럼 대상에%를 포함하며, 직접 만든 암묵 규칙처럼 사용할 수 있음 - 이중 콜론 규칙
::은 같은 대상에 여러 규칙을 정의할 수 있지만 드물게 쓰임
명령 실행과 오류 처리
- 명령 앞에
@를 붙이면 해당 명령 자체가 출력되지 않음 make -s를 실행하면 각 줄 앞에@를 붙인 것처럼 동작함- 각 명령 줄은 새 셸에서 실행되는 효과를 가지므로, 한 줄의
cd ..는 다음 줄에 영향을 주지 않음 - 같은 셸 상태를 유지하려면
cd ..; echo \pwd``처럼 한 줄에 쓰거나 백슬래시로 이어야 함 - 기본 셸은
/bin/sh이며,SHELL=/bin/bash처럼 바꿀 수 있음 - 셸 변수의
$를 Makefile 안에서 쓰려면$$를 사용함 - 오류 처리는 실행 방식에 따라 달라짐
make -k: 오류가 있어도 가능한 다른 작업을 계속 실행함- 명령 앞의
-: 해당 명령 오류를 무시함 make -i: 모든 명령에 오류 무시를 적용함
ctrl+c로 Make를 중단하면 Make가 방금 만든 최신 대상을 삭제함
재귀 make와 환경 변수
- Makefile 안에서 다시 make를 호출할 때는
make대신 특수 변수$(MAKE)를 사용함 $(MAKE)는 make 플래그를 전달하며, 플래그의 영향을 적절히 처리함- Make가 시작되면 실행 당시 설정된 환경 변수를 Make 변수로 자동 생성함
export지시어는 변수를 모든 recipe의 셸 명령 환경에 설정함- 하위 make에서도 변수를 쓰려면 해당 변수를
export해야 함 .EXPORT_ALL_VARIABLES를 사용하면 모든 변수를 export할 수 있음make clean run test처럼 여러 대상을 한 번에 지정하면clean,run,test순서로 실행됨
조건문과 함수
- 조건문은
ifeq,ifneq,ifdef,ifndef,else,endif로 구성됨 ifdef는 변수 참조를 확장하지 않고, 변수가 정의되어 있는지만 확인함$(MAKEFLAGS)와findstring을 사용해make -i같은 플래그 전달 여부를 검사할 수 있음- Make 함수는 주로 텍스트 처리에 사용되며,
$(fn, arguments)또는${fn, arguments}형태로 호출함 - 주요 함수는 아래와 같음
subst: 문자열 치환patsubst: 패턴에 맞는 단어 치환foreach: 공백으로 구분된 단어 목록 변환if: 첫 번째 인자가 비어 있지 않으면 두 번째 인자, 아니면 세 번째 인자 사용call: 변수를 기본 함수처럼 호출하고$(1),$(2)같은 매개변수를 사용shell: 셸을 호출하지만 개행을 공백으로 바꿈filter: 목록에서 특정 패턴에 맞는 요소 선택filter-out: 특정 패턴에 맞지 않는 요소 선택
include, vpath, .PHONY, .DELETE_ON_ERROR
include filenames...는 하나 이상의 다른 Makefile을 읽도록 함include는gcc의-M같은 옵션으로 생성한 Makefile을 읽을 때 유용함vpath <pattern> <directories>는 특정 의존성 파일을 찾을 디렉터리를 지정함VPATH변수로 비슷한 검색 경로 동작을 전역적으로 지정할 수 있음- 백슬래시
\는 긴 명령을 여러 줄에 나눠 쓰는 데 사용됨 .PHONY를 대상에 추가하면 Make가 해당 대상을 같은 이름의 파일과 혼동하지 않음.DELETE_ON_ERROR는 규칙 실행 중 명령이 0이 아닌 종료 상태를 반환하면 해당 규칙의 대상을 삭제함.DELETE_ON_ERROR는PHONY처럼 바로 앞 대상에만 적용되는 것이 아니라 모든 대상에 적용됨
Makefile Cookbook 예제
- Cookbook 예제는 중간 규모 프로젝트에 맞는 Makefile 템플릿을 제공함
src/폴더에 C/C++ 파일을 넣으면 Makefile이 컴파일 대상과 의존성을 자동으로 결정함- 주요 변수 구성은 아래와 같음
TARGET_EXEC := final_programBUILD_DIR := ./buildSRC_DIRS := ./src
SRCS는find와$(shell ...)을 사용해*.cpp,*.c,*.s파일을 찾음OBJS는 각 소스 파일 앞에BUILD_DIR을 붙이고.o를 추가해 빌드 산출물 경로를 만듦DEPS는.o를.d로 바꿔 의존성 Makefile 목록을 만듦INC_DIRS는src/아래 모든 디렉터리를 찾고,INC_FLAGS는 각 디렉터리에-I접두사를 붙임CPPFLAGS := $(INC_FLAGS) -MMD -MP는 GCC가.d의존성 파일을 만들도록 구성함- C와 C++ 소스는 각각 별도 패턴 규칙으로
build/아래에.o파일을 만들며, 필요한 디렉터리는mkdir -p $(dir $@)로 생성함 clean은.PHONY로 선언되고rm -r $(BUILD_DIR)로 빌드 디렉터리를 삭제함-include $(DEPS)는.d파일을 포함하되, 처음 빌드처럼 파일이 없을 때 발생하는 오류를 억제함