makefile로 편하게 컴파일하고 링크하여 파일을 쉽게 만들어보자:)

dqQQQ

·

2023. 11. 24. 20:48

개요

왜 makefile을 써야하는지와 makefile 문법에 대해 알아보겠다.

 

make는 매우 유용한 유틸리티이다.

 

리눅스에서 개발 할 시에 소스 코드를 수정하고 컴파일하는 것은 매우 많이 일어난다.


Makefile의 이해


make는 매우 유용한 유틸리티이다.

리눅스에서 개발 할 시에 소스 코드를 수정하고 컴파일하는 것은 매우 많이 일어난다.

이때 소스 파일의 개수가 적다면 일일이 gcc 명령을 사용하는 것도 무리가 없지만 소스파일이 많아지고 각자 다른 옵션으로 컴파일해야한다면 일일이 타이핑하는 것은 바보같은일이다.

개발자들의 편의성을 굉장히 높여주고 시간낭비를 줄여주는 도구가 makefile이다.

make에 대한 정의는 한마디로 파일 관리 유틸리티로 볼 수 있다.

make는 각 파일 간의 종속 관계를 파악해 makefile에 적힌대로 컴파일 명령이나 쉘 명령을 순차적으로 실행한다.


기본적인 빌드 구조


Makefile을 사용하기 전에 우리가 작성한 코드를 빌드할 때 어떻게 컴퓨터에서 처리하는지 알아야한다.

사진에서 첫번째 단계를 보면 우리가 코드를 작성한 소스코드가 .o의 확장자를 가진 오브젝트 파일로 변환하는 과정을 컴파일이라고 한다.

오브젝트 파일을 열어보면 c코드가 어셈블리 코드로 변환되있는 것을 볼 수 있을 것이다.


터미널 명령어

{% highlight markdown %}
gcc -c -o 원하는 이름(.o) 컴파일 할 소스파일(.c)
{% endhighlight %}

이제 오브젝트파일들이 만들어졌다.

이렇게 만들어진 오브젝트 파일들을 합쳐서 하나의 실행파일을 실행해야한다.

합치는 과정을 링크라고 하며 gcc 컴파일러에게 목적파일을 전달하면 진행할 수 있다.


링킹을 터미널 명령어

{% highlight markdown %}
gcc -o 원하는 이름(.out) 링킹할 오브젝트 파일(.o)
{% endhighlight %}

소스 코드가 한 두개라면 이러한 과정을 수행하고 파일들을 관리하는 것이 큰 부담은 아니다.

그러나 소스 코드가 많아진다면?? 그 땐 makefile이 해답이 될 것이다.


makefile 구문 작성 기본 규칙


명령의 시작은 TAB으로 시작된다.

명령이 탭으로 시작되어야 하는 규칙은 make의 오랜 관습이다.

탭으로 시작되지 않는 명령절을 만나면 make는 명령절을 타겟절로 해석하기 때문에 "missing separator"의 오류메시지를 출력하고 중단한다.

비어 있는 행은 무시된다.

타겟절 이하에 오는 명령절에 있어 비어있는 명령라인은 무시된다.

'#'을 만나면 개행 문자를 만날 때까지 무시한다.

#는 주석으로 파이썬에서 주석과 같은 기능을 한다.

기술 행이 길어지면 ''를 이용해서 이을 수 있다.

diary : memo.o calendar.o main.o

diary : memo.o calendar.o\

main.o

위의 두 코드는 같은 기술 코드이다.

;는 명령라인을 나눌 때 사용한다.

타겟절에 ;를 붙이면 명령문이 바로 올 수 있지만 가독성의 이유로 잘 사용하지 않는다. 알고 있으면 좋다.

종속 항목이 없는 타겟도 사용 가능하다.

종속 항목이 없기 때문에 명령은 바로 실행된다.

명령 부분에는 어떤 명령이 와도 상관없다.

명령절에는 반드시 컴파일 명령이나 종속 항목에 관련된 명령만 사용하라는 법은 없다.


make 매커니즘


이제 프로그램 빌드가 어떻게 이루어지는지 알았다.

그렇다면 make가 어떤 매커니즘으로 작동하는지 알아야한다.

make 명령을 내리면 make는 먼저 현재 디렉토리 내에서 기술 파일을 찾는다.

기술 파일은 Makefile, makefile 또는 GNU make인 경우에는 GNUmakefile일 수 있다. 42프로젝트에서는 Makefile이 기술 파일에 해당한다.

다음의 Makefile의 예시를 통해 어떤 매커니즘인지 살펴보겠다.

all : diary

diary : memo.o calendar.o main.o
    gcc -o diary memo.o calendar.o main.o

memo.o : memo.c
    gcc -c -o memo.o memo.c

calendar.o : calendar.c
    gcc -c -o calendar.o calendar.c

main.o : main.c
    gcc -c -o main.o main.c

clean : 
    rm -rf *.o diary

첫번째

Makefile 내에서 제일 처음 오는 타겟을 찾는다.

이 Makefile에서는 all 타겟이 해당한다.

두번째

all 타겟을 만들 종속 항목 diary를 보고 diary파일이 현재 디렉토리에 없다면 생성하기 위해 diary를 타겟으로 하는 명령으로 간다.

세번째

memo.o가 없으므로 memo.o를 생성하기 위해 memo.o를 타겟으로 하는 명령으로 가서 memo.c를 컴파일해 오브젝트 파일을 만들어낸다.

네번째

diary 타겟 명령으로 가서 종속항목들을 다시 살펴본다. memo.o는 없지만 calendar.o는 없다.

calendar.o를 생성하고 다시 살펴본다.

main.o가 없으므로 main.o를 생성하고 다시 살펴본다.

다섯번째

이제 diary 타겟을 생성하기 위한 종속항목들이 모두 존재하므로 diary 파일 생성을 위한 명령어를 실행한다.

여섯번째

이제 다시 all 타겟으로 가서 diary가 만들어진 것을 확인한 후 현재 all 타겟을 수행할 명령을 실행한다.

이 예제에서는 실행할 명령이 없으므로 make는 all 타겟이 생성되었다고 간주하고 종료한다.


매크로의 사용


매크로는 좀 더 일관되고 이식성이 높고 융통성이 있는 기술 파일을 만들기 위해서 필요하다.

매크로 작성 기본 규칙

매크로의 정의는 '='를 포함하는 하나의 문장이다.


Makefile


Makefile의 기본적인 구조

{% highlight markdown %}
CC = gcc


target … : prerequisites …


(tab)recipe
{% endhighlight %}

CC == 매크로 : Makefile에서 사용하면 정의된 스트링으로 치환된다.

target == 타겟 : 우리가 내고 싶은 결과물

prerequisites == 의존성 : 결과물을 만들기 위해 필요한 재료들

recipe == 명령 : 재료들을 가지고 타겟을 만들 수 있는 명령

이런 컴퓨터 용어들은 해석을 하려하면 더욱 어색해지는 경우가 있으니 원어로 기억해두시면 좋습니다.


Makefile CODE


{% highlight markdown %}
SRCS =


OBJS = $(SRCS:.c=.o)

BSRCS =


BOBJS = $(BSRCS:.c=.o)

NAME = libft.a

ifdef WITH\_BONUS  
OBJ\_FILES = $(OBJS) $(BOBJS)  
else  
OBJ\_FILES = $(OBJS)  
endif

.PHONY: all  
all : $(NAME)

%.o : %.c  
gcc -Wall -Wextra -Werror -c -o $@ $<

$(NAME) : $(OBJ\_FILES)  
ar cr $@ $^

.PHONY: bonus  
bonus :  
make WITH\_BONUS=1 all

.PHONY: clean  
clean :  
rm -f $(OBJS) $(BOBJS)

.PHONY: fclean  
fclean : clean  
rm -f $(NAME)

.PHONY: re  
re : fclean all  
{% endhighlight %}
  • SRCS : 많은 소스파일들의 매크로 변수
  • OBJS : 매크로 치환 -> $(매크로 이름:이전 내용=새로운 내용)
  • BSRCS : 많은 보너스 소스파일들의 매크로 변수
  • BOBJS : 많은 보너스 소스파일의 매크로 치환 변수
  • NAME : 내가 만들 타겟 매크로
  • all : $(NAME) : 다중 타겟 : 종속 타겟 (NAME)이 타겟인 명령 실행
  • ifdef WITH_BONUS : WITH_BONUS가 선언되어있으면 보너스파일까지/ 써주는 이유: relink를 방지하기 위해서
  • else : ifndef, 즉 WITH_BONUS가 선언되어있지 않으면 일반문제만
  • %.o : %.c: 타겟 : %.o ,의존성 : %.c //옛날버전 -> .c.o :
  • ar : 아카이브 파일 생성 명령어
  • c옵션 : 새 아카이브 생성 (덮어씀)
  • r옵션 : 파일들을 추가 해줄수 있는 옵션
  • $@ : 타겟을 의미하는 자동 변수
  • $< : 의존성중에 첫번째를 의미하는 자동 변수
  • $^ : 의존성의 모든 파일을 의미하는 자동 변수
  • bonus : bonus 파일을 아카이브에 추가하는 룰을 추가
  • clean : 오브젝트 파일을 삭제하는 룰을 추가
  • fclean : 아카이브 파일도 삭제하는 룰을 추가
  • re : fclean을 실행하고 all 타겟을 실행하는 룰을 추가
  • .PHONY: ~ : 가짜 목적물이라고 하며 동일한 이름의 파일과의 충돌을 막기 위해서 룰마다 적용해준다.