본문 바로가기
개발 이야기/c++

초보자를 위한 Makefile 작성 방법

by 떵떵남편 2024. 11. 28.

소프트웨어 개발에서 Makefile은 컴파일 과정을 자동화하고 프로젝트의 빌드 관리를 단순화하는 중요한 도구입니다. Makefile은 특히 C/C++와 같은 언어에서 유용하며, 프로젝트가 커질수록 그 중요성이 더 커집니다. 하지만 초보자에게는 처음 접할 때 약간 어려울 수 있습니다. 이 글에서는 Makefile의 기초부터 간단한 예제와 단계별 작성 방법까지 알아보겠습니다.


1. Makefile이란?

Makefile make라는 빌드 도구에 의해 읽히는 구성 파일입니다. 여러 소스 파일과 종속성을 가진 프로젝트에서, 컴파일 명령을 일일이 입력하는 대신 Makefile을 사용하여 빌드 과정을 자동화할 수 있습니다.

Makefile의 주요 개념

  • 타겟(Target): 만들고자 하는 파일 또는 동작(예: 실행 파일).
  • 종속성(Dependencies): 타겟을 생성하기 위해 필요한 파일들.
  • 명령(Command): 타겟을 생성하는 데 필요한 쉘 명령어.

2. 기본 구조

Makefile은 아래와 같은 형식으로 작성됩니다:

target: dependencies
    command

 

예제

hello: hello.o
    g++ -o hello hello.o
  • target: hello 실행 파일.
  • dependencies: hello.o라는 오브젝트 파일.
  • command: g++ -o hello hello.o 명령으로 실행 파일을 생성.

중요한 점

  • 탭(Tab): 명령어는 반드시 탭 문자로 시작해야 합니다. 공백(space)을 사용하면 오류가 발생합니다.
  • Makefile은 위에서 아래로 읽히며, 각 타겟과 그 종속성을 처리합니다.

3. 간단한 예제: 단일 파일 컴파일

디렉토리 구조

project/
│
├── main.cpp
└── Makefile

 

main.cpp

#include <iostream>

int main() {
    std::cout << "Hello, Makefile!" << std::endl;
    return 0;
}

 

Makefile

all: main

main: main.o
    g++ -o main main.o

main.o: main.cpp
    g++ -c main.cpp

clean:
    rm -f main main.o
  • all: 디폴트 타겟으로, main을 생성.
  • main: main.o를 링크하여 실행 파일 생성.
  • main.o: main.cpp를 컴파일하여 오브젝트 파일 생성.
  • clean: 빌드 파일들을 삭제.

사용 방법

  • 빌드:
make
  • 클린업:
make clean

4. 다중 파일 프로젝트 컴파일

디렉토리 구조

project/
│
├── main.cpp
├── utils.cpp
├── utils.h
└── Makefile

main.cpp

#include <iostream>
#include "utils.h"

int main() {
    printMessage();
    return 0;
}

utils.cpp

#include <iostream>

void printMessage() {
    std::cout << "Hello from utils!" << std::endl;
}

utils.h

#ifndef UTILS_H
#define UTILS_H

void printMessage();

#endif

Makefile

CC = g++
CFLAGS = -c -Wall

all: main

main: main.o utils.o
    $(CC) -o main main.o utils.o

main.o: main.cpp
    $(CC) $(CFLAGS) main.cpp

utils.o: utils.cpp
    $(CC) $(CFLAGS) utils.cpp

clean:
    rm -f main *.o
  • 변수 사용: CC와 CFLAGS로 컴파일러와 옵션을 지정하여 재사용성을 높임.
  • 다중 파일: 각 소스 파일을 독립적으로 컴파일한 후, 링크하여 실행 파일을 생성.

5. Makefile에서 패턴 규칙 활용하기

프로젝트에 소스 파일이 많아지면 각 파일에 대해 규칙을 작성하는 것이 번거로울 수 있습니다. 이때 패턴 규칙을 사용하면 간결하게 작성할 수 있습니다.

예제

CC = g++
CFLAGS = -c -Wall

all: main

main: main.o utils.o
    $(CC) -o $@ $^

%.o: %.cpp
    $(CC) $(CFLAGS) $<

clean:
    rm -f main *.o
  • 패턴 규칙: %.o: %.cpp는 모든 .cpp 파일에 대해 동일한 규칙을 적용.
  • 자동 변수:
    • $@: 현재 타겟 이름.
    • $<: 첫 번째 종속성.
    • $^: 모든 종속성.

6. Makefile에서의 고급 기능

(1) 의존성 자동 생성

Makefile에서 파일 간의 종속성을 수동으로 관리하는 것은 번거롭습니다. 의존성을 자동으로 생성하는 방법이 있습니다.

CC = g++
CFLAGS = -c -Wall
DEPS = utils.h

all: main

main: main.o utils.o
    $(CC) -o $@ $^

%.o: %.cpp $(DEPS)
    $(CC) $(CFLAGS) $<

clean:
    rm -f main *.o

(2) 병렬 빌드

make 명령에서 -j 옵션을 사용하면 다중 스레드를 활용해 병렬로 빌드할 수 있습니다. (뒤의 숫자는 스레드 개수)

make -j4

7. Makefile의 장점

  1. 자동화: 컴파일 및 링크 과정을 단순화.
  2. 재컴파일 최소화: 변경된 파일만 다시 컴파일.
  3. 확장성: 대규모 프로젝트에서 효율적인 빌드 관리.
  4. 이식성: Makefile은 다양한 운영 체제에서 동작.

8. 자주 하는 실수와 팁

  1. 탭(Tab) 사용: 명령어 앞에는 반드시 탭 문자를 사용해야 함.
  2. 파일 이름과 타겟 이름: 일치하지 않으면 빌드 실패 가능성.
  3. 변수 활용: 컴파일러 옵션 등을 변수로 정의해 재사용성 증대.

9. 결론

Makefile은 초보자에게 처음에는 어렵게 느껴질 수 있지만, 기본 구조와 작성 방법을 익히면 프로젝트의 빌드 관리가 훨씬 간편해집니다. 위에서 소개한 단일 파일, 다중 파일, 패턴 규칙, 의존성 자동 생성 등 다양한 방법을 적용해 보세요. 익숙해지면 자신만의 최적화된 Makefile을 작성할 수 있을 것입니다.