Makefile

mtak·2021년 1월 26일
4

42Seoul

목록 보기
5/13

0. compile이란?

내가 소스 코드(.c)를 작성하고 실행 파일 (.exe)을 만드는 것을 컴파일이라고 생각하겠지만 이것은 컴파일러가 하는 역할이지 컴파일은 아니다.
컴파일의 개념은 소스 코드(원시 코드) 목적 파일로 바꾸는 것이다.

1. Makefile

1-1.make?

명령어 script를 미리 저장해서 컴파일까지 자동으로 진행하게 하는 툴.

object 파일 생성 (컴파일)

$gcc -c -o a.o a.c
$gcc -c -o b.o b.c
$gcc -c -o c.o c.c
$gcc -c -o main.o main.c

헤더파일들은 파일에 포함되어 있으므로 명시해주지 않아도 된다.

실행파일 생성 (링크)

$ ./a.out

1-2.Makefile?

make 를 실행하기 위해서는 프로젝트의 루트 디렉토리에 Makefile이라는 파일이 있어야 한다. 여기서 Makefile이란 프로젝트를 어떻게 빌드할 것인지 적은 파일이다.

Makefile을 사용하는 이유는 위와 같은 복잡한 과정을 생략할 수 있어서이기도 하지만, Makefile이 제공하는 강력한 기능 중 하나인 Incremental build 를 사용하기 위해서다.

Incremaental build 란?

반복적인 빌드 과정에서 변경된 소스코드에 의존성(Dependency)이 있는 대상들만 추려서 다시 빌드하는 기능이다.

예를 들어, 위의 빌드 예제에서 main.c의 한 줄만 바꾸고 다시 빌드를 할 때, main.o 컴파일(gcc -c -o main.o main.c)과 a.out링크(gcc -o a.out main.o a.o b.o)만 수행하는 경우가 이에 해당한다.

1-3.Rule

가장 작은 단위인 Rule은 아래와 같은 형식을 가지고 있다.

[target] ... : [prerequisites] ...
		[command]
		...
		...
  • 대상 [target] : 빌드 대상 이름. 명령에 의해 생성되는 결과 파일, 오브젝트 파일이나 실행 파일

  • 의존 관계 [prerequisite] : 대상을 만들 때 의존되는 파일들. 여기에 나열된 대상들을 먼저 만들고 빌드 대상을 생성한다. 의존 파일이 변경됐다면 대상을 만들 도록 명령을 내릴 것이다.

  • 명령 [command] : 빌드 대상을 생성하는 명령. 여러 줄로 작성할 수 있으며, 의존 관계에 있는 파일들이 변경됐거나 대상 파일이 없을 때 명령이 실행된다. 쉘에서 쓸 수 있는 명령을 사용할 수 있다.

    명령을 쓸 때 반드시 tab키로 띄어준 후에 써야한다.

위의 빌드 예제를 makefile 로 작성하면 다음과 같다.

    a.out : a.o b.o c.o main.o
        gcc -o a.out a.o b.o c.o main.o
    
    a.o : a.c
        gcc -c -o a.o a.c
    
    b.o : b.c
        gcc -c -o b.o b.c
    
    c.o : c.c
        gcc -c -o c.o c.c
        
    main.o : main.c
        gcc -c -o main.o main.c
    
    clean:
        rm *.o a.out

:exclamation:Make에서 rule이 실행되는 조건

  1. 타겟 파일이 프로젝트 폴더 안에 없을 때
  2. Prerequisites에 변경점이 발생했을 때

1-4.자동 변수

  • $@ : 목표 이름
  • $* : 목표 이름에서 확장자가 없는 이름
  • $< : 첫 번째 전제 조건의 파일 이름
  • $? : 목표 파일 보다 더 최근에 갱신된 파일 이름
  • $^: 현재 Target이 의존하는 대상들의 전체 목록
  • $?: 현재 Target이 의존하는 대상들 중 변경된 것들의 목록
  • $% : 대상의 이름 (해당 규칙 대상이 아카이브 인 경우)

위 예시는 다음과 같이 변경될 수 있다.

SRC = a.c b.c c.c 
NAME = a.out

$(NAME): $(SRC)
         gcc $(SRC)

1-5. 패턴 룰

위 예시를 패턴 룰을 사용해 바꿔보면 다음과 같이 사용할 수 있다.

OBJ = a.o b.oc.o
NAME = a.out

%.o : %.c
      gcc $< -o $@

$(NAME): $(OBJ)
         gcc $(OBJ)

현재 오브젝트 파일은 없고 코드 파일만 있다고 가정했을때, Make가 어떻게 돌아가는지 설명해보겠다.

  1. make a.out을 실행하면, 먼저 prerequisite를 확인합니다.
  2. a.o를 확인하려 하는데, 현제 폴더에 a.o가 없습니다.
  3. 만약 prerequisite에 참조되는 파일이 폴더에 없다면, Make는 a.o를 타겟으로 가진 룰이 있는지 확인하고 있다면 이를 실행합니다.
  4. 타겟이 %.o인 룰은 파일이 .o로 끝난 모든 파일을 대상으로 하는 패턴 룰입니다. a.o는 이 패턴 룰에 포함되기 때문에 이 룰을 실행합니다. 이때 이 룰의 prerequisite에 %.c가 적혀있는데, 이는 실제 사용될 때 %.o 에서의 %부분을 가져와 채우게 됩니다. a.o에서 file_a가 %에서 대체되었기 때문에 prerequisite는 a.c가 됩니다.
  5. 커멘드 중 $<$@는 Make에서 제공하는 자동 변수입니다.
    $@는 타겟을 저장하는 변수이고,
    $<는 첫 prerequisite를 저장하는 변수입니다.
    그렇다면 패턴 룰에 있는 커멘드는 gcc a.c -o a.o로 대체되어 실행됩니다.
  6. 이후 $(OBJ)의 나머지 값인 b.cc.c도 2~5번 과정을 거쳐가면서 오브젝트 파일을 만들고, a.out을 컴파일하게 됩니다.

💡 패턴 룰에서 사용되는 %을 보면 평소 와일드카드 문자인 *가 익숙할탠데, 이에 대해서 make에서도 지원하지만 이를 사용하게 된다면 문제가 생겨 잘 사용하지 않습니다.

  • Make를 실행할 때 타겟을 넣지 않고 make 명령어를 실행한다면, Makefile의 룰 중 '.'으로 시작하는 타겟을 가진 룰이나 패턴 룰을 제외한 최상단에 있는 룰을 실행합니다. 따라서 저희가 사용한 예시는 make a.out 뿐만 아니라 make로도 룰을 실행할 수 있습니다.
  • Makefile에서도 함수를 만들 수 있다.
  • 타겟이 무조건 파일 이름일 필요는 없습니다. 예를 들어 대부분의 사람들이 오브젝트 파일을 지우는 명령을 사용하기 위해 clean이란 타겟을 사용합니다. 주로 하단과 같이 구현됩니다.
   clean:
          rm -f $(OBJ)
  • 위와 같은 상황에서 반대로 clean이라는 파일이 존재한다면, 위 룰은 제 역할을 하지 않을겁니다. 초반에 언급했던 두가지 경우를 모두 만족하지 않기 때문에 up to date라고 뜨며 커멘드가 돌아가지 않을 것입니다. 따라서 위의 경우에는 .PHONY라는 타겟을 사용해야 합니다. Phony 타겟의 prerequisite로 지정되면 이를 타겟으로 가진 룰은 두가지 경우를 만족하지 않아도 무조건 실행하게 됩니다. 위 Makefile을 다음과 같이 수정하면 clean파일이 있어도 작동될 것입니다.
   clean:
          rm -f $(OBJ)
          
   .PHONY: clean

1-6.문법

  • $(addsuffix 접미사, 문자열)
        $(addsufix  .c, memo  main) => memo.c   main.c 
  • $(addprefix 접두어, 문자열)
        $(addprefix src/, memo main) => src/memo   src/main
  • $(shell <shell-command>)
        SH = $(shell ls *.c)  => shell 명령에 대한 결과가 변수에 들어감
  • $(subst 찾을 문자, 변경할 문자, 대상 문자)

    "대상문자"에서 "찾을 문자"를 "변경할 문자"로 변경해 준다.

  • $(patsubst 찾을 패턴, 변경할 패턴, 대상 문자)

    subst와 기능은 동일하나 확장자를 바꿀때 사용함

  • $(strip 대상문자)

    대상문자에서 모든 공백을 1칸으로 줄인다. 여백이 많아도 모두 1칸으로

  • $(filter 찾을 문자, 대상문자)

    대상문자에서 찾을 문자를 포함한 문자들을 찾아서 저장한다.

  • $(filter-out 찾을 문자, 대상문자)

    filter와 반대로 찾을 문자가 있는 경우를 제외하고 저장한다.

  • $(join 원본문자, 붙일문자)

    원본문자와 붙일문자를 붙여서 한 문자로 만든다.

  • $(dir 대상문자)

    대상문자에서 파일문자를 제외한 경로명만 추출한다.

  • $(notdir 대상문자)

    대상문자에서 파일명만 추출한다.

  • $(baseanme 대상문자)

    대상문자에서 확장자를 제외한 문자를 알려준다.

1-7.Makefile 기본 패턴

CC=<컴파일러>
CFLAGS=<컴파일 옵션>
LDFLAGS=<링크 옵션>
LDLIBS=<링크 라이브러리 목록>
OBJS=<Object 파일 목록>
TARGET=<빌드 대상 이름>
 
all: $(TARGET)
 
clean:
    rm -f *.o
    rm -f $(TARGET)
 
$(TARGET): $(OBJS)
	$(CC) -o $@ $(OBJS)

ref - 1
ref - 2

profile
노는게 젤 조아. 친구들 모여라!!

2개의 댓글

comment-user-thumbnail
2021년 11월 16일

너무 좋은 글입니다. 덕분에 Makefile에 대해서 너무나도 자세히 잘 알게 되었습니다!!

1개의 답글