Makefile 배우기

2 weeks ago 10

  • Makefile은 C/C++ 빌드 자동화 및 의존성 관리를 간소화하는 도구임
  • 타임스탬프를 활용한 변경 파일 검출 방식으로, 필요한 경우에만 컴파일 작업을 실행함
  • 규칙(rule), 명령어(command), 의존성(prerequisite) 등 핵심 구조를 예제와 함께 설명함
  • 자동 변수, 패턴 규칙, 변수 확장 같은 고급 기능도 실용적으로 다룸
  • 중간 규모 프로젝트용 실전 Makefile 템플릿을 통한 확장성과 관리의 중요성 소개함

Makefile 튜토리얼 가이드 소개

  • Makefile은 프로젝트 빌드 자동화와 의존성 관리를 담당하는 핵심 도구임
  • 다양한 숨은 규칙과 기호로 인해 처음 접할 때 복잡하게 느낄 수 있으나, 이 가이드는 주요 내용을 간결하고 직접 실행 가능한 예제로 정리함
  • 각 섹션별로 실습 기반 예시를 통한 이해가 가능

시작하기

Makefile의 존재 목적

  • Makefile은 대형 프로그램에서 변경된 부분만 재컴파일하는 데 활용됨
  • C/C++ 이외에도 여러 언어별 전용 빌드 도구가 존재하지만, Make는 일반적인 빌드 시나리오 전반에 활용됨
  • 변경된 파일을 감지해 필요한 작업만 실행하는 로직이 핵심임

Make의 대안 빌드 시스템

  • C/C++ 계열: SCons, CMake, Bazel, Ninja 등 여러 선택지가 있음
  • Java 계열: Ant, Maven, Gradle 등
  • Go, Rust, TypeScript 등도 자체 빌드 도구 제공
  • 파이썬, Ruby, JavaScript 등 인터프리터 언어는 컴파일이 필요 없어 Makefile과 같은 별도 관리 필요성 낮음

Make의 버전과 종류

  • 다양한 Make 구현체가 있으나, 본 가이드는 GNU Make(주로 Linux, MacOS에서 사용)에 최적화되어 있음
  • 예제는 GNU Make 3, 4 버전에 모두 호환

예제 실행 방법

  • 터미널에서 make 설치 후, 각 예시를 Makefile 파일로 저장 및 make 명령어 실행
  • Makefile 내의 명령어 줄은 반드시 탭 문자로 들여쓰기 필요

Makefile 기본 구문

규칙(Rule)의 구조

  • 타겟: 의존성(들)

    • 명령어
    • 명령어
  • 타겟: 빌드 결과 파일명(보통 하나)

  • 명령어: 실제 동작하는 쉘 스크립트(탭으로 시작)

  • 의존성: 타겟이 빌드되기 전에 반드시 준비되어야 할 파일 목록


Make의 본질

Hello World 예제

hello: echo "Hello, World" echo "This line will print if the file hello does not exist."
  • 타겟 hello는 의존성이 없고, 커맨드 2개를 실행함
  • make hello 실행 시, 파일 hello가 존재하지 않으면 명령어가 실행됨. 이미 파일이 있다면 실행하지 않음
  • 일반적으로 타겟=파일명이 일치하도록 작성됨

C 파일 컴파일 기본 예제

  1. blah.c 파일 생성(int main() { return 0; } 내용)
  2. 다음 Makefile 작성
blah: cc blah.c -o blah
  • make 실행 시, blah 타겟이 없다면 컴파일이 실행되어 blah 파일 생성됨
  • blah.c 변경 후에도 자동 재컴파일 X → 의존성 추가 필요

의존성 추가 방식

blah: blah.c cc blah.c -o blah
  • 이제 blah.c가 새로 변경됐다면, blah 타겟이 다시 빌드됨
  • 파일 타임스탬프를 변경 검출의 기준으로 사용함
  • 타임스탬프를 임의로 조작하면 의도와 다르게 작동할 수 있음

예제 추가

연결된 타겟 및 의존성 예제

blah: blah.o cc blah.o -o blah blah.o: blah.c cc -c blah.c -o blah.o blah.c: echo "int main() { return 0; }" > blah.c
  • 트리 구조로 의존성을 따라가며 각 단계별 생성 과정이 자동화됨

반드시 실행되는 타겟 예제

some_file: other_file echo "This will always run, and runs second" touch some_file other_file: echo "This will always run, and runs first"
  • other_file이 실제 파일로 생성되지 않으므로, some_file 명령이 매번 실행됨

Make clean

  • clean 타겟은 빌드 산출물을 삭제하는 용도로 자주 사용됨
  • Make에서 특별한 예약어는 아니며, 직접 명령어로 정의 필요
  • 만약 파일명이 clean이면 혼동될 수 있으므로, .PHONY 사용을 권장

예시:

some_file: touch some_file clean: rm -f some_file

변수 처리

  • 변수는 항상 문자열.
  • 보통 :=을 권장하며, =, ?=, += 등의 다양한 대입 방식 존재
  • 사용 예시:
files := file1 file2 some_file: $(files) echo "Look at this variable: " $(files) touch some_file file1: touch file1 file2: touch file2 clean: rm -f file1 file2 some_file
  • 변수 참조 방식: $(variable) 또는 ${variable}
  • Makefile 내 따옴표는 Make 자체에서는 의미 없음(단, 쉘 명령어에서는 필요)

타겟 관리

all 타겟

  • 여러 타겟을 한꺼번에 실행하려면, 첫 번째(디폴트) 타겟에 속성 부여
all: one two three one: touch one two: touch two three: touch three clean: rm -f one two three

다중 타겟 및 자동 변수

  • 다수 타겟에 대해 각자 개별 명령 실행 가능. $@는 현재 타겟명을 가짐
all: f1.o f2.o f1.o f2.o: echo $@

자동 변수와 와일드카드

* 와일드카드

  • *는 파일 시스템상 이름을 직접 탐색
  • 반드시 wildcard 함수로 감싸서 사용 권장
print: $(wildcard *.c) ls -la $?
  • 변수 정의에서 직접 * 사용 금지
thing_wrong := *.o thing_right := $(wildcard *.o)

% 와일드카드

  • 주로 패턴 규칙에서 사용, 지정 패턴을 추출하여 확장 가능

Fancy Rules

암시적(Implicit) 규칙

  • Make는 C/C++ 빌드와 관련된 여러 숨은 기본 규칙을 내장함
  • 대표 변수: CC, CXX, CFLAGS, CPPFLAGS, LDFLAGS 등
  • C 예제:
CC = gcc CFLAGS = -g blah: blah.o blah.c: echo "int main() { return 0; }" > blah.c clean: rm -f blah*

Static Pattern Rules

  • 동일한 패턴을 따르는 다수 규칙을 간결하게 작성 가능
objects = foo.o bar.o all.o all: $(objects) $(CC) $^ -o all $(objects): %.o: %.c $(CC) -c $^ -o $@ all.c: echo "int main() { return 0; }" > all.c %.c: touch $@ clean: rm -f *.c *.o all

Static Pattern Rules + filter 함수

  • filter를 활용하면 특정 확장자 패턴에 맞는 대상만 선택 가능
obj_files = foo.result bar.o lose.o src_files = foo.raw bar.c lose.c all: $(obj_files) .PHONY: all $(filter %.o,$(obj_files)): %.o: %.c echo "target: $@ prereq: $

Read Entire Article