Makefile이란
make빌드 방식은 gcc 빌드 방식을 대신하여 간단하게 빌드 할 수 있도록 해준다.
이때 Makefile은 make 빌드툴을 이용하여 프로그램을 빌드할 수 있도록 make문법에 맞게 작성하는 문서라고 할 수 있다.
gcc 빌드는 해본사람이라면 알겠지만 실행파일을 만드는데 필요한 소스파일이 적을 때엔 make빌드보다 간단하고 쉬워보이지만 필요한 파일이 많아질수록 점차 복잡해지고 오래걸린다는 것을 알 수 있다.
make빌드는 Makefile을 통해 복잡한 빌드과정을 간단하게 수행할 수 있다.
CROSS_COMPILE=/usr/lib/aarch64-linux-gun/aarch64-linux-gun-
CC=$(CROSS_COMPILE)gcc
TARGET=test
OBJS=test.o
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $(TARGET) $(OBJS)
%.o: %.c
$(CC) -c -o $@ $<
clean:
rm -rf $(TARGET) $(OBJS)
가장 기본적인 형태의 Makefile을 작성해보았다.
Makefile을 어떻게 작성하는지, 한 줄씩 알아보도록 할건데 그 전에 우선 할당 유형 연산자부터 알아보자.
할당 유형
Make는 Makefile을 쓰는 것을 단순화하기 위해 변수가 지원된다.
=, ?=, :=, ::=, +=, !=연산자 중 하나를 가지고 할당된다. 각 연산자의 차이점은 다음과 같다.
유형 | 설명 |
= | 지연된 값을 변수에 할당한다. 변수가 사용될 때마다 변수의 값이 요구된다는 의미이다. 쉘 명령의 결과를 대입할 때 - 변수가 읽혀질 때마다 쉘 명령이 실행된다는 것을 잊지 마시라. |
:= / ::= | 이러한 대입은 변수값을 한 번만 처리하고 그 값을 저장한다. 단순하고 강력하다. 이런 유형의 할당은 디폴트로 선택되어야 한다. |
?= | 변수가 정의되지 않은 경우에만 :=역할을 한다. 그렇지 않은 경우는 아무것도 하지 않는다. |
+= | 더하기 대입 연산자이다. 변수가 미리 :=또는 ::=에 설정되어 있는 경우, 우변은 즉시 값으로 간주된다. 그렇지 않으면 지연된 값으로 간주된다. |
!= | 쉘 대입 연산자다. 우변은 즉시 평가되고 쉘에 전달된다. 결과는 좌변의 변수에 저장된다. |
이제 예제 Makefile을 한 줄 씩 자세히 알아보자.
우선, CC는 컴파일러를 의미한다.
CC 뒤에 붙인 $(CROSS_COMPILE)이 크로스 컴파일러이다.
CROSS_COMPILE 경로에 해당 컴파일러에 대한 크로스 컴파일러가 있다.
컴파일러는 보통 컴파일 하는 시스템과 실행파일이 동작하는 시스템이 일치하는 '네이티브 컴파일러'겠지만, 임베디드에서는 조금 다르다.
임베디드 시스템의 경우, 코드를 작성하여 컴파일 하는 시스템(PC)과, 컴파일 후 생성된 실행파일이 동작하는 시스템(보드)이 다른 경우가 많은데, 이럴 때는 '크로스 컴파일러' 환경을 구축해야한다.
Makefile을 보다보면 CC를 포함하여 AR, AS, LD, NM, GCC, CPP, CXX, FC, RANLIB, READELF, STRIP, OBJDUMP, INSTALL등 여러 키워드가 보일 것이다. 이것은 전부 컴파일러들이며, 크로스컴파일을 할 수 있도록 한다.
그 다음으로 보이는 TARGET은 빌드 후 생성될 실행파일의 이름을 의미하고, OBJ는 오브젝트 파일을 의미한다.
빌드는 전처리 -> 컴파일 -> 어셈블 -> 링킹 의 과정을 거치는데, 이 중 어셈블 단계에서 전처리 된 소스파일을 통해 오브젝트 파일을 생성한다.
이 때 생성되는 오브젝트 파일을 지정하는 라인이다.

다음으론 all: $(TARGET) 이다.
all은 최종적으로 생성되는 target을 의미하는데, TARGET 이라는 실행파일을 생성한다는 의미이다.
TARGET에 test를 넣었다면 빌드 후, 최종적으로 test 실행파일이 생성됨을 의미한다.
그 밑에 또한 비슷한 구조로 다음과 같이 작성된것을 볼 수 있는데,
$(TARGET): $(OBJS)
$(CC) -o $(TARGET) $(OBJS)
%.o: %.c
$(CC) -c -o $@ $<
이는 이렇게 보면 이해하기 쉽다.
<target>: <dependency>
<command>
target을 생성하기 위해 dependency가 필요하고, 이 dependency를 가지고 command를 실행한다.
위의 예제로 다시 보면
TARGET을 생성하기 위해 OBJS 파일들이 필요해.
그 OBJS 파일들을 가지고 CC(크로스 컴파일러)를 통해 링킹해서 TARGET을 생성해.
이렇게 볼 수 있다.
dependency는 빌드 과정 참고.
전처리 파일(*.i)을 생성하기 위해서는 소스파일(*.c)이 있어야하고, 컴파일 파일(*.s)을 생성하기 위해서는 전처리 파일(*.i)이 있어야한다. 또 오브젝트 파일(*.o)을 생성하기 위해서는 컴파일 파일(*.s)이 있어야하고, 최종적으로 실행파일(*.out 또는 *.exe, ...)을 만들기 위해서는 오브젝트 파일(*.o)이 있어야한다.
참고로 전처리 파일과 컴파일 파일은 실제로 눈에 보이게 생성되지는 않는다.
그러면 그 다음라인은 어떻게 읽을 수 있을까
그런데 여기선 의미 모를 기호들이 보인다.
$@ 와 $< 이 보이는데 이 기호들은 무슨 의미를 가지고 있는지부터 알아보자.
- $@ => <target>: <dependency> tab <command> 구조에서 target을 의미.
- $< => <target>: <dependency> tab <command> 구조에서 현재 target의 첫 번째 dependency 파일을 의미.
그렇다면 이제 해당 라인을 읽을 수 있을 것이다.
오브젝트 파일을 생성하기 위해 소스 파일이 필요해.
그 소스 파일들을 가지고 CC(크로스 컴파일러)를 통해 어셈블해서 오브젝트 파일을 생성해.
마지막 라인이다.
clean:
rm -rf $(TARGET) $(OBJS)
해당 라인은 clean 매크로를 의미한다.
가장 대표적이고 기본처럼 넣는 매크로이기도 하며, clean 빌드 규칙을 추가해 make 뿐만 아니라 make clean 커멘드를 사용할 수 있게 된다.
make clean 을 통해, 위에서 정의한것과 같이 실행 파일과 오브젝트 파일을 삭제할 수 있다.
PKG_CONFIG
위의 makefile에선 볼 수 없지만 linux에서 테스트 및 개발을 하다보면 설치된 라이브러리 등의 정보를 알아야 할 때가 있다. /usr/lib, /usr/share 등 여러 경로에 있으며 모든 라이브러리 경로를 외울 수 없기 때문에 pkg-config를 이용한다.
pkg-config는 설치된 라이브러리에 대한 다양한 정보를 출력한다.
- C, C++ 컴파일러를 위한 매개변수
- 링커를 위한 매개변수
- 패키지 버전
실재 gcc를 이용하여 컴파일하는 경우 다음과 같이 작성한다.
$ gcc -o test test.c 'pkg-config --libs --cflags glib-2.0'
이러한 정보를 관리하는 파일을 '*.pc' 파일로 관리되며 경로는 /usr/lib/pkgconfig, /usr/share/ pkgconfig, /usr/local/lib/pkgconfig 등의 경로에 저장된다. PC파일 내용을 보면 아래와 같다.

기본적인 라이브러리 이름과 정보, 버전이 있고, 컴파일에 필요한 include 경로 Cflags와 link할 Libs 정보가 저장되어있다.
Makefile에서 보면 CFLAGS, LDFLAGS를 볼 수 있다. 이는 이 *.pc파일에서 있는 include 경로 Cflags와 link할 Libs 정보를 바탕으로 pkg-config를 실행하는 것이다.
추가 컴파일러 및 크로스컴파일러 옵션은 2024.06.15 - [Embedded SW 기초] - 컴파일러 & 크로스 컴파일 글에서 확인하면 된다.
참고사이트
https://80000coding.oopy.io/b553047b-42f6-4066-9f30-f4aef0b0503d
[make] Makefile 개념 및 사용법 정리
목차
80000coding.oopy.io
https://80000coding.oopy.io/df141ab9-7fa5-40fe-97df-e6004cf6a3b9
Makefile을 만들기 위한 A to Z
1. 프로그램의 실행 순서
80000coding.oopy.io
'Embedded SW 기초' 카테고리의 다른 글
HDMI란 무엇인가? (0) | 2024.06.26 |
---|---|
네트워크 인터페이스 명 변경 방법 (0) | 2024.06.25 |
WiFi (0) | 2024.06.17 |
컴파일러 & 크로스 컴파일 (0) | 2024.06.15 |
git 기초 명령어 (0) | 2024.06.14 |