Makefile이란?
Makefile은 프로젝트의 컴파일, 빌드 및 기타 작업을 자동화하는데 사용되는 파일로 make라는 유틸리티와 함께 사용된다. Makefile은 일련의 규칙과 지시를 포함하고 있어서 해당 규칙에 맞게 작성하면 소스코드를 컴파일하는 과정을 간소화할 수 있다.
왜 Makefile이 필요한가?
- 자동화: Makefile을 사용하면 여러 파일을 컴파일하고 링크하는 과정을 자동화할 수 있다.
- 효율성: 의존성 파일의 타임스탬프를 비교하여 변경된 파일만 다시 컴파일하므로 빌드 시간을 줄여준다.
- 재사용성: 프로젝트마다 반복되는 빌드 작업을 쉽게 재사용할 수 있다.
- 일관성: 모든 개발자가 동일한 빌드 과정을 따르도록 할 수 있다.
Makefile 작성법을 익히기 위한 예제 프로젝트
디렉토리 구성
/project
├── include
│ ├── foo.h
│ └── bar.h
├── main.c
├── foo.c
├── bar.c
└── Makefile
main.c
#include <stdio.h>
#include "foo.h"
#include "bar.h"
int main() {
foo();
bar();
return 0;
}
foo.c
#include <stdio.h>
#include "foo.h"
void foo() {
printf("This is foo.\n");
}
bar.c
#include <stdio.h>
#include "bar.h"
void bar() {
printf("This is bar.\n");
}
include/foo.h
#ifndef __FOO_H__
#define __FOO_H__
void foo();
#endif
include/bar.h
#ifndef __BAR_H__
#define __BAR_H__
void bar();
#endif
Makefile 작성
# 컴파일러
CC = gcc
# 컴파일 옵션
CFLAGS = -Wall -g -I./include
# 대상 바이너리 파일
TARGET = example
# 소스 파일
SRCS = main.c foo.c bar.c
# 목적 파일
OBJS = $(SRCS:.c=.o)
# 기본 타겟
all: $(TARGET)
# 타겟을 빌드하는 규칙
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
# .c 파일을 .o 파일로 컴파일하는 규칙
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
# 정리 규칙
clean:
rm -f $(OBJS) $(TARGET)
Makefile 하나씩 뜯어보기
# 컴파일러
CC = gcc
여기서 CC 변수는 사용할 컴파일러를 지정한다. 보통 크로스 컴파일러의 경우 gcc 앞에 타겟 아키텍쳐와 로컬 환경 등이 붙기 때문에 CROSS = '크로스 컴파일러 접두사', CC = $(CROSS)gcc 이렇게 사용하기도 한다.
# 컴파일 옵션
CFLAGS = -Wall -g -I./include
CFLAGS 변수는 컴파일러 옵션을 정의한다. 여기서 -Wall은 모든 경고 메시지를 출력하게 하고, -g는 디버깅 정보를 포함시킨다. 그리고 헤더 파일이 다른 디렉토리에 있는 경우 '-I' 옵션 뒤에 디렉토리 경로를 추가하면 된다.
CFLAGS 외에도 링커 옵션인 LDFLAGS도 있을 수 있다.
# 대상 바이너리 파일
TARGET = example
TARGET 변수로 최종적으로 생성될 실행 파일의 이름을 정의하고 있다. 이후 make 명령을 사용해 빌드하면 'example'이란 실행파일이 생긴다. 여기서 변수명은 TARGET이 아닌 다른 이름으로도 설정할 수 있다.
# 소스 파일
SRCS = main.c foo.c bar.c
SRCS 변수는 프로젝트에 포함된 모든 소스 파일을 나열하고 있다.
혹은 이렇게 나열하지 않고 SRCS := $(wildcard *.c)와 같이 wildcard를 사용해서 현재 디렉토리에 있는 모든 .c 파일을 포함시킬 수 있다.
# 목적 파일
OBJS = $(SRCS:.c=.o)
OBJS 변수는 소스 파일을 컴파일한 후 생성될 목적 파일(.o 파일)을 정의한다. SRCS 변수를 참조해서 .c 확장자를 .o로 바꾸기만 하고 있다.
# 기본 타겟
all: $(TARGET)
all은 가장 먼저 등장하는 기본 타겟으로, make 명령어를 실행했을 때 기본적으로 호출되는 타겟이다. 보통 기본 타겟은 all이나 default로 정의되지만, 다른 이름으로도 설정할 수도 있다.
혹은 이 문장을 아예 생략할 수도 있다 그렇게 하면 이 아래 등장하는 타겟인 $(TARGET)이 기본 타겟이 된다.
# 타겟을 빌드하는 규칙
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
이 규칙은 목적 파일들을 링크해서 최종 실행 파일을 만드는 과정이다. 아랫줄에 TAB을 하고 실행 명령이 등장하는데 CC 변수와 CFLAGS 변수를 참조해 컴파일 한다. 여기서 $@는 윗줄 ':' 왼쪽에 나오는 현재 타겟을, $^는 그 오른쪽에 있는 모든 의존성을 의미한다.
# .c 파일을 .o 파일로 컴파일하는 규칙
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
이 규칙은 .c 파일을 .o 파일로 컴파일하는 과정이다. 여기서 $@는 현재 타겟을, $<는 첫 번째 의존성을 의미한다.
그리고 사실 이 규칙을 통째로 생략해도 된다. Make 명령은 암시적 규칙에 의해 .o 파일이 없는 경우 같은 이름의 .c 파일을 찾아 자동으로 컴파일 하기 때문이다.
# 정리 규칙
clean:
rm -f $(OBJS) $(TARGET)
clean 타겟은 빌드 결과물인 목적 파일과 실행 파일을 삭제하는 명령을 실행하는 규칙이다.
Makefile 기본 구조
타겟: 의존성
실행 명령어
- 타겟(Target): 빌드할 파일 또는 실행할 작업의 이름
- 의존성(Dependencies): 타겟을 빌드하기 위해 필요한 파일들
- 실행 명령어(Commands): 타겟을 빌드하거나 작업을 수행하는 명령어들로 반드시 탭(TAB)으로 시작해야 한다.
✔️ Makefile에서 처음으로 나타나는 타겟이 기본 타겟(Default Target)으로 설정되어, make 명령어를 실행했을 때 기본적으로 이 타겟이 실행된다. 또한, 타겟이 의존성을 가지고 있다면, 그 의존성이 먼저 실행된다.
✔️ 기본 타겟과 기본 타겟의 의존성에 의해 실행 될 타겟들 말고도 다양한 작업을 위해 여러 타겟을 정의할 수 있다. 그런 타겟을 실행할 땐 'make 타겟명'으로 실행한다.
✔️ make 명령은 암시적 규칙에 의해 기본적으로 .o 파일이 없으면 대응하는 .c 파일을 찾아 컴파일한 후 링크한다.
✔️ Makefile에서 wildcard를 사용하면 파일 패턴을 매칭하여 해당 패턴에 맞는 파일들의 목록을 반환하는 기능을 한다.
$(wildcard pattern)
$(wildcard *.c)
Makefile 실행
터미널에서 make 명령 실행
$ make
빌드된 실행파일 실행하여 확인
./example
정리
make clean
만약 깨끗히 재빌드를 하려면 make clean 후 make를 하면 되고, 의존성 파일 중 일부만 수정한 뒤 make clean 없이 바로 make 명령을 사용하면 프로젝트 전체를 컴파일하는게 아니라 수정된 파일만 재컴파일하여 빌드한다.
Makefile에서 변수 값 할당 방식
Makefile의 앞부분을 보면 CC, CFLAGS, TARGET 등 변수의 값을 할당하고 명령어에서 $(변수)로 해당 변수의 값을 참조하는 것을 볼 수 있다. Makefile에서 변수 값을 할당하는 방식은 주로 등호(=)를 사용하나 그 외에도 다양한 방식이 존재하며 각 방식마다 값 평가시점의 차이가 존재한다.
- 변수 = 값
- 지연평가 방식으로 변수를 실제 참조할 때 값이 계산된다. 따라서 변수의 값이 평가 시점 전까지 계속 바뀔 수 있다.
- 변수 := 값
- 즉시 평가 방식으로 변수 선언 시점에 값을 계산하고, 이후 변수의 값은 고정된다.
- 변수 ?= 값
- 조건부 할당 방식으로 변수가 정의되지 않은 경우에만 값을 할당한다. 이미 정의된 변수의 값이 있다면 ?=로 할당한 값은 무시된다.
- 변수 += 값
- 값 추가 방식으로 기존 변수의 값에 공백으로 구분된 새 값을 추가하는 방식으로 작동한다. 여러 줄로 나누어 값을 추가할 때 유용한다.
'임베디드 개발 > 펌웨어' 카테고리의 다른 글
의사난수 생성기 (PRNG, Pseudo Random Number Generator) (0) | 2024.08.08 |
---|---|
SMD 타입 저항 읽는법 (0) | 2024.07.28 |
접지(Ground)의 종류 (0) | 2024.06.14 |
RJ45 커넥터에서 LED의 의미 (0) | 2024.06.07 |
이더넷 트랜스포머의 역할 (0) | 2024.05.26 |