make란 무엇일까?
GNU make를 개발한 GNU에서는 make를 아래와 같이 소개하고 있다.
The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them.
make utility는 다시 컴파일 해야하는 대형 프로그램 부분을 자동으로 판별하고 이를 다시 컴파일하는 명령을 실행합니다.
규모가 큰 프로그램을 개발할 때에는 하나의 파일을 여러 개로, 모듈화하여 개발을 진행하게 된다. 만일 make를 사용하지 않을 경우 모든 코드를 하나의 파일에 작성해야 한다. 당연하게도 코드의 가독성이 떨어지고 유지보수도 어려워진다. 그러나 이를 여러 개의 파일로 나누어 코드를 작성하고자 할 경우에도 한 가지 문제가 발생한다. 각 파일로 나누어진 코드는 서로 의존관계를 가지기 때문에 코드를 수정할 경우 모든 파일을 다시 빌드해야 한다. make는 이것을 해결하기 위해 만들어졌다.
make는 여러 개의 모듈화된 코드를 빌드할 때 사용하는 프로그램 빌드 도구이다. 사용자는 makefile에 여러 파일 사이의 의존성과 각 파일에 필요한 명령을 정의함으로써 프로그램을 쉽게 빌드할 수 있으며, make는 이 모든 과정에 필요한 표준 문법을 제공하고 있다.
make의 가장 강력한 기능은 여러 파일 사이의 의존성을 정의할 수 있다는 것인데, 이를 Incremental Build라고 한다. Incremental Build는 반복적인 빌드 과정에서 변경된 소스코드의 의존하고 있는 대상들만 다시 빌드하는 기능으로, 쉽게 말해 make 명령어를 사용하여 프로그램을 다시 빌드할 경우 makefile에 정의된 전체 명령어를 실행하는 것이 아니라, 수정된 코드와 연관된 명령어만 다시 실행하는 것이라고 할 수 있다.
makefile은 무엇일까?
makefile은 각 파일 사이의 의존성과 필요한 명령이 정의된 파일이다. 즉, 실행파일을 만들기 위한 명령어가 서술된 파일이라고 할 수 있다. makefile은 대상(Target), 의존성(Dependency), 명령어(Recipe)으로 구성된다.
<Target> : <Dependency>
(Tab) <Recipe>
Target
명령어에 의해 생성되는 파일이며, 컴파일을 통해 만들어진 오브젝트 파일 또는 링킹을 통해 만들어진 실행 파일이 여기에 해당된다.
Dependency
Target을 생성할 때 필요한 파일, 다시 말해 Target이 의존하는 파일을 의미하며, 오브젝트 파일을 생성할 때 필요한 소스 파일 또는 실행 파일을 생성할 때 필요한 오브젝트 파일이 해당ehls다. 만일 Dependency(소스 파일 또는 오브젝트 파일)가 변경되었을 경우 Incremental Build 기능에 따라 변경된 Dependency에 의존하는 Target은 자동으로 다시 빌드된다.
Recipe
빌드 대상을 생성하는 명렁어를 의미하며, 여러 줄로 작성할 수 있고 ShellScript를 사용할 수도 있다. 명령어는 반드시 tab으로 들여쓰기를 한 후에 작성되어야 한다.
makefile 작성하기
// <Target> : <Dependency>
// <Command>
// 첫 번째 블록은 a.o b.o main.o(Dependency)를 통해 a.out(Target)을 생성한다.
// a.out은 a.o b.o main.o에 의존한다.
// a.out을 생성하기 위해 gcc -o a.out a.o b.o main.o 명령어가 실행된다.
a.out : a.o b.o main.o
gcc -o a.out a.o b.o main.o
// 두 번째 블록은 a.c(Dependency)를 통해 a.o(Target)을 생성한다.
// a.o는 a.c에 의존한다.
// a.o을 생성하기 위해 gcc -c -o a.o a.c 명령어가 실행된다.
a.o : a.c
gcc -c -o a.o a.c
// 세 번째 블록은 b.c(Dependency)를 통해 b.o(Target)을 생성한다.
// b.o는 b.c에 의존한다.
// b.o을 생성하기 위해 gcc -c -o b.o b.c 명령어가 실행된다.
b.o : b.c
gcc -c -o b.o b.c
// 네 번째 블록은 main.c(Dependency)를 통해 main.o(Target)을 생성한다.
// main.o는 main.c에 의존한다.
// main.o을 생성하기 위해 gcc -c -o main.o main.c 명령어가 실행된다.
main.o : main.c
gcc -c -o main.o main.c
// 다섯 번째 블록에서는 makefile에서 macro를 정의한다.
// 아래의 경우 make clean을 사용하면 make를 통해 만들어진 오브젝트 파일과 실행파일 a.out이 제거된다.
clean:
rm *.o a.out
위 코드가 작성된 makefile이 존재하는 디렉토리 내에서 make를 실행한다. make 명령어를 통해 정상적으로 빌드되었을 경우 아래와 같은 문구가 터미널에 나오게 된다.
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -c -o main.o main.c
gcc -o a.out a.o b.o main.o
변수를 활용한 makefile 작성
makefile에서도 변수를 사용할 수 있다. make 표준문법에서는 makefile에서 사용할 수 있는 내장변수와 자동변수를 제공하고 있다.
make 내장변수
make가 제공하는 내장변수는 [변수명] = [내장변수]와 같은 방식으로 정의하고, $(변수명)과 같은 방식으로 사용할 수 있다. 아래는 자주 사용되는 변수명이다. 이 변수명 이외에도 필요에 따라 직접 변수를 정의하고 사용할 수 있다.
CC
컴파일 명령어 ex) gcc, cc
CFLAGS
컴파일 옵션 ex) -o, -c, -g
OBJECTS
최종 실행파일을 만들 때 함께 링킹할 오브젝트 파일 ex) a.o, b.o
TARGET
빌드 대상, 주로 실행 파일 ex) a.out
LDFLAGS
링크 옵션
LDLIBS
링크 라이브러리
make 내장변수 예제
CC=gcc
CFLAGS=-g -Wall
TARGET=a.out
OBJECTS=a.o b.o main.o
$(TARGET) : $(OBJS)
$(CC) -o $(TARGET) $(OBJS)
a.o : a.c
$(CC) -c -o a.o a.c
b.o : b.c
$(CC) -c -o b.o b.c
main.o : main.c
$(CC) -c -o main.o main.c
clean:
rm $(OBJS) $(TARGET)
make 자동변수
make가 제공하는 자동변수는 내장변수와는 다르게 별도로 선언할 필요없이 사용할 수 있다. 또한 사용할 때에도 $[자동변수 기호]와 같은 방식으로 사용할 수 있다.
$@
Target을 의미. ex) a.out
$^
모든 Dependency 의미. ex) a.o b.o main.o
$<
첫 번째 Dependency 의미. ex) a.o
$?
변경된 Dependency 의미 ex) main.o
$*
Target의 이름 ex) a
make 자동변수 예제
CC=gcc
CFLAGS=-g -Wall
TARGET=a.out
OBJECTS=a.o b.o main.o
$@ : $^
$(CC) -o $@ $^
a.o : a.c
$(CC) -c -o a.o a.c
b.o : b.c
$(CC) -c -o b.o b.c
main.o : main.c
$(CC) -c -o main.o main.c
clean:
rm $@ $^
Top comments (0)