[Linux] 리눅스 Makefile 만들기, make 사용법, make clean

Makefile이란? make 프로그램이란?

리눅스에서 Makefile은 소스 파일들을 쉽게 빌드할 수 있도록 돕는 make 프로그램의 설정 파일이다.

 

 

컴파일 예시
[그림 1] 컴파일 예시

 

[그림 1]과 같이 여러 개의 헤더 파일과 소스 파일을 빌드한다고 하자. 

각 파일의 내용은 아래와 같다.

 

 

hello.h

void hello();

 

hello.c

    #include <stdio.h>
    
    void hello()
    {
        printf("hello ");
    }

 

world.h

void world();

 

world.c

    #include <stdio.h>
    
    void world()
    {
        printf("world\n");
    }

 

main.c

    #include "hello.h"
    #include "world.h"
    
    int main()
    {
        hello();
        world();
    
        return 0;
    }

 

 

이 헤더 파일과 소스 파일들을 app.out 프로그램으로 빌드하는 과정은 다음과 같다.

 

$ gcc -c -o hello.o hello.c
$ gcc -c -o world.o world.c
$ gcc -c -o main.o main.c

 

먼저 c 파일들을 오브젝트 파일로 변환한다. 헤더 파일(.h)은 전처리 단계에서 소스 파일(.c)에 합쳐지므로 따로 명시할 필요 없다. 

 

$ gcc -o app.out hello.o world.o main.o

 

이후 오브젝트 파일들을 링크하여 app.out 프로그램을 만든다. 

 

위와 같은 방식으로 C 프로그램을 빌드할 수 있다. 하지만 이같은 과정은 파일이 많아질수록 매우 번거롭고 실수할 가능성이 크다.

 

이를 해결하기 위해 쉘 스크립트를 작성하여 자동화할 수 있지만, 쉘 스크립트를 이용한 방식은 큰 단점이 존재한다.

 

쉘 스크립트를 사용하여 빌드를 자동화한다면, 특정 소스 하나만 내용이 바뀌더라도 전체 파일을 다시 컴파일해야 하는 비효율이 발생한다.

 

이 같은 단점을 해결할 수 있는 방식이 바로 make 프로그램의 사용, 즉 Makefile을 이용한 방식이다. 

 

 

 

참고)

[Linux] 리눅스 gcc란? gcc로 C언어 컴파일하기

 

[Linux] 리눅스 gcc란? gcc로 C언어 컴파일하기

GCC란? GCC는 GNU 컴파일러 모음(GNU Compiler Collection)이다. GNU는 운영 체제의 하나이자 컴퓨터 소프트웨어의 모음집이다.(참고1) 현재 대부분 GNU와 LINUX를 결합하여 사용하기 때문에 이 조합을 짧게 "Li

code-lab1.tistory.com

 

Incremental Build

make 프로그램은 단순히 컴파일 및 빌드 과정을 자동화해 주는 것뿐만 아니라 incremental build를 지원한다. 

incremental build는 변경된 파일에 의존성이 있는 대상들만 다시 빌드해 주는 기능이다. 

 

예를 들어 [그림 1] 상황에서 한 번 빌드를 하고 나서 hello.c의 소스 코드를 수정했다면 hello.o 파일과 app.out만 다시 빌드하게 된다.

 

만약 소스파일이 매우 많은 상황에서 전체를 다시 빌드해야한다면 많은 시간과 자원이 소모되겠지만, incremental build 기능을 사용한다면 변경된 부분만 다시 빌드하면 되므로 시간과 자원을 절약할 수 있다.

 

 

 

Makefile 작성법

<target>: <prerequisites>
	<recipe>

GNU make 공식문서에서 설명하는 Makefile의 구조는 아래와 같다.

 

  • target : 일반적으로 프로그램에 의해 생성되는 파일의 이름이다. 혹은 'clean' 과 같이 수행할 작업의 이름이 되기도 한다(이에 대해서는 아래에서 설명)
  • prerequisites : target을 생성하기 위해 사용되는 인풋(input) 파일들이다. 혹은 target이 의존(depends on)하는 파일들이다. 
  • recipe : make 프로그램이 실행하는 명령이다. recipe 라인에는 꼭 tab 문자를 앞에 넣어줘야 한다.

 

예를 들어 [그림 1]상황에서 Makefile을 작성한다면 다음과 같다.

 

app.out: hello.o world.o main.o
	gcc -o app.out hello.o world.o main.o
    
hello.o: hello.h hello.c
	gcc -c -o hello.o hello.c
    
world.o: world.h world.c
	gcc -c -o world.o world.c

main.o: hello.h world.h main.c
	gcc -c -o main.o main.c

 

vi 편집기를 이용해 Makefile을 작성할 수 있다. 파일명은 Makefile로 하면 된다.

 

가장 윗줄부터 분석해보자.

app.out: hello.o world.o main.o
	gcc -o app.out hello.o world.o main.o

app.out이라는 target을 생성하기 위해 hello.o, world.o, main.o 라는 prerequisites가 필요하다. 

또한 아래 줄에는 recipe로 gcc 명령어를 작성해 놓았고 실제로 저 명령어가 실행된다.

 

그런데 hello.o, world.o, main.o라는 오브젝트 파일들도 컴파일 과정을 거쳐야 한다.

hello.o: hello.h hello.c
	gcc -c -o hello.o hello.c
    
world.o: world.h world.c
	gcc -c -o world.o world.c

main.o: hello.h world.h main.c
	gcc -c -o main.o main.c

 

 

따라서 마찬가지로 target, prerequisites, recipe를 통해 오브젝트 파일들을 생성한다. 

 

Makefile 예시
[그림 2] Makefile 예시

 

실제로 vi 편집기를 이용해 Makefile을 작성해 보았다. 

 

make 실행법
[그림 3] make 실행법

 

헤더와 소스 파일들이 있는 디렉터리에 Makefile을 생성하고 make 명령어를 입력하면 app.out 프로그램이 빌드된다.

app.out 프로그램을 실행해 보면 제대로 동작하는 것을 확인할 수 있다.

 

Makefile을 한 번만 작성하면 이후 쉽게 빌드할 수 있겠지만 이렇게 Makefile을 작성하는 것도 매우 번거롭다.

이를 더 쉽게 작성하는 법을 알아보자.

 

참고) prerequisites에 헤더파일을 명시하는 이유

헤더 파일은 컴파일 시에 소스 파일과 자동으로 합쳐지는데 굳이 prerequisites에 명시하는 이유가 뭘까?
그건 바로 헤더 파일의 변경을 감지해서 incremental build 기능을 실행하기 위해서이다.

make 프로그램은 소스 파일이 어떤 헤더 파일을 포함하는지 추적하지 않는다.
따라서 헤더 파일을 prerequisites에 명시하지 않는다면 헤더 파일의 변경을 감지하지 못하게 된다.

 

 

내장 규칙(Built-in Rule)

make 프로그램은 자주 사용되는 빌드 규칙들을 내장 규칙으로 자동으로 처리한다. 

예를 들어 소스 파일(*.c)을 오브젝트 파일(*.o)로 변환하는 규칙을 자동으로 처리해 준다. 

 

app.out: hello.o world.o main.o
	gcc -o app.out hello.o world.o main.o
    
hello.o: hello.h hello.c  
world.o: world.h world.c
main.o: hello.h world.h main.c

 

 

즉, 위와 같이 hello.o, world.o, main.o 같은 오브젝트 파일들은 prerequisites만 명시해 주고 recipe를 생략하더라도 내장 규칙으로 자동으로 컴파일해준다.

 

이로써 Makefile을 더 간단하게 작성할 수 있다.

 

make clean 사용하기

clean:
    rm -f *.o
    rm -f $(TARGET)

Makefile에 위와 같이 clean과 같은 명령을 추가할 수 있다. 

 

make clean

이후 위와 같이 make clean 명령을 실행하면 오브젝트 파일들과 빌드한 프로그램이 삭제된다.

 

 

make를 진행하면 쓸데없이 오브젝트 파일(*.o)들이 남아있는다. 

 

이걸 일일이 지우려면 매우 귀찮은데, 위와 같이 clean 명령을 추가해 놓으면 쉽게 제거할 수 있다.

 

또한, 빌드 오류가 나서 오브젝트 파일뿐만 아니라 타겟도 모두 지우고 싶다면 $(TARGET) 변수를 지정할 수 있다.

 

변수에 대해서는 아래 설명하겠다.

 

 

 

변수(Variables)

변수를 사용하면 Makefile을 더 간단하고 확장성 있게 작성할 수 있다.

아래는 흔히 사용되는 Makefile의 구조이다.

CC=<컴파일러>
CFLAGS=<컴파일 옵션>
LDFLAGS=<링크 옵션>
LDLIBS=<링크 라이브러리 목록>
OBJS=<오브젝트 파일 목록>
TARGET=<빌드 대상>
 

$(TARGET): $(OBJS)
	$(CC) -o $@ $(OBJS)

all: $(TARGET)

clean:
    rm -f *.o
    rm -f $(TARGET)

 

  • CC : gcc나 g++ 등 컴파일러를 지정
  • CFLAGS : 컴파일 옵션 지정
  • LDFLAGS : 링크 옵션 지정
  • LDLIBS : 링크 라이브러리 목록 지정
  • OBJS : 오브젝트 파일 목록 지정
  • TARGET : 빌드 대상 지정
  • $@ : $(TARGET)으로 치환되는 자동변수

 

기본적으로 변수 선언 및 사용법은 리눅스 쉘 스크립트에서 사용하는 방식과 같다.

 

예를 들어 위 예시는 아래와 같이 Makefile을 작성하면 된다.

 

CC= gcc
CFLAGS= -g -Wall
OBJS= hello.o world.o main.o 
TARGET=app.out
 
$(TARGET): $(OBJS)
    $(CC) -o $@ $(OBJS)
 
hello.o: hello.h hello.c
world.o: world.h world.c
main.o: hello.h world.h main.c

clean:
    rm -f *.o
    rm -f $(TARGET)

 

위와 같이 변수를 사용하면 더 확장성 있는 Makefile을 작성할 수 있다.

처음 방식이 더 간단해 보일 수 있으나 잘 생각해 보면 추후 파일이 추가된다고 했을 때 이 방식이 더 간단할 것임을 알 수 있다.

 

변수를 이용한 Makefile
[그림 4] 변수를 이용한 Makefile

 

실제로 위와 같이 Makefile을 작성한 뒤

 

make 예시
[그림 5] make 예시

 

make 명령어를 실행하면 제대로 동작함을 확인할 수 있다.

 

make clean
[그림 6] make clean

 

make clean 명령어를 사용하면 오브젝트 파일과 빌드한 프로그램이 삭제되는 것을 확인할 수 있다.

가끔 빌드가 꼬이면 make clean으로 전부 제거하고 다시 make를 해보는 것도 도움이 될 수 있다.

 

참고)

CFLAGS에서 설정한 gcc -g 옵션은 gdb에게 제공하는 정보를 바이너리에 삽입하는 것이고, -Wall 옵션은 모든 모호한 코딩에 대해 경고를 보내는 옵션이다.

 

 

 

 


참고

 

1. https://www.gnu.org/software/make/manual/

2. https://www.tuwlab.com/ece/27193

3. https://velog.io/@hidaehyunlee/Makefile-%EB%A7%8C%EB%93%A4%EA%B8%B0

 

반응형

댓글

Designed by JB FACTORY