Makefile?

Makefile의 개념과 기초
문법 참고
Makefile이란 정말 쉽게 이해하자면 컴파일 과정을 간단하고 빠르게 할 수 있도록 도와주는 도구라고 보면 되는 것 같다.
한번 Makefile을 제대로 만들어두면 일일히 컴파일에 필요한 명령어들을 입력할 필요 없이 Makefile에서 정의된 간단한 명령어만으로 모든 과정을 한번에 처리할 수 있다.

Makefile 구현

NAME = libft.a
CC = gcc
CFLAGS = -Wall -Wextra -Werror
AR = ar
HEADER = ./

SRCS = ./ft_isalnum.c ./ft_isalpha.c ./ft_isascii.c\
./ft_isdigit.c ./ft_isprint.c ./ft_strlen.c ./ft_memset.c\
./ft_bzero.c ./ft_memcpy.c ./ft_memmove.c ./ft_strlcpy.c\
./ft_strlcat.c ./ft_strncmp.c ./ft_toupper.c ./ft_tolower.c\
./ft_strchr.c ./ft_strrchr.c ./ft_memchr.c ./ft_memcmp.c\
./ft_strnstr.c ./ft_atoi.c ./ft_calloc.c ./ft_strdup.c\
./ft_substr.c ./ft_strjoin.c ./ft_strtrim.c ./ft_split.c\
./ft_itoa.c ./ft_strmapi.c ./ft_striteri.c ./ft_putchar_fd.c\
./ft_putstr_fd.c ./ft_putendl_fd.c ./ft_putnbr_fd.c\

BONUS_SRCS = ./ft_lstnew.c ./ft_lstadd_front.c ./ft_lstsize.c ./ft_lstlast.c\
./ft_lstadd_back.c ./ft_lstclear.c ./ft_lstdelone.c ./ft_lstiter.c ./ft_lstmap.c

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

all : $(NAME)

$(NAME) : $(OBJS)
	$(AR) -rcs $(NAME) $^

%.o : %.c
	$(CC) $(CFLAGS) -c $^ -I$(HEADER)

clean :
	rm -f $(OBJS) $(BONUS_OBJS)

fclean : clean
	rm -f $(NAME)

re : fclean all

bonus :
	@make OBJS='$(SRCS:.c=.o) $(BONUS_OBJS)'

.PHONY : all clean fclean re bonus

Makefile의 구성 요소는 다음과 같다.

  • 변수 선언
  • 타겟과 필요조건
  • 명령어
  • PHONY

변수

크게 사용자 설정 변수와 Makefile에서 제공하는 자동 변수로 나눌 수 있다.

사용자 설정 변수

CC = gcc
CFLAGS = -Wall -Wextra -Werror

Makefile에서는 이런 식으로 변수를 정의할 수 있다.
변수를 사용할 때는 $(변수) 의 형태로 사용하면 된다.

SRCS = ./ft_isalnum.c ./ft_isalpha.c ./ft_isascii.c\
./ft_isdigit.c ./ft_isprint.c ./ft_strlen.c ./ft_memset.c\ 
...
...

위 변수 선언처럼 \로 줄바꿈을 할 수도 있다.

자동 변수

$@, $<, $^ 등이 있다.

  • $@ : 타겟 이름에 대응된다.
  • $< : 의존 파일 목록의 첫번째 파일에 대응된다.
  • $^ : 의존 파일 목록 전체에 대응된다.

타겟(target)

all, clean, fclean, re, bonus와 같은 것들이다.
혹은 $(NAME) : $(OBJS)$(NAME)같은 경우에도 해당된다.
Makefile 실행시 make 타겟 의 형태로 입력하면 수행된다.

all

Makefile을 실행시키기 위해 다른 옵션 없이 그냥 make 명령어만 입력했을 경우, 해당 명령은 Makefile을 위에서부터 읽으면서 만나는 첫번째 타겟을 실행시킨다.
따라서 all이라는 더미 타겟을 가장 첫번째 타겟으로 두어 작동하게 하는 것이 일반적이라고 한다.

clean

컴파일 과정에서 생성된 오브젝트 파일들을 제거하는 옵션이다.

fclean

clean의 기능에 더해, 생성된 라이브러리 파일(.a)도 제거하는 옵션이다.

re

fclean의 기능에 더해, 다시 all 옵션을 실행하여 make를 새로 실행한다.

bonus

libft의 보너스 과제를 풀기 위해 넣은 옵션이다.
생성할 오브젝트 파일 목록 OBJS 변수에 보너스 과제의 함수들도 포함시켜 재지정한 후 다시 make한다. 이 때 make 앞의 @는 해당 명령어가 출력되지 않게 하는 기능을 가졌다.


필요조건(prerequisites)

타겟 : 필요조건 의 형태로 되어있으며, 쉽게 말해 타겟 실행시 필요한 파일들을 선언해 놓은 것이다. 의존 파일이라고도 한다.
예를들어 $(NAME) : $(OBJS)$(NAME)타겟의 명령어를 실행시키기 위해 $(OBJS)에 해당하는 파일이 필요하다는 것을 뜻한다.


명령어(recipes)

타겟 실행시 실제로 어떤 동작을 수행할 것인지를 나타낸 것으로, 타겟 아랫줄부터 tab 한번의 들여쓰기 이후에 작성된다.

$(NAME) : $(OBJS)
	$(AR) -rcs $(NAME) $^

에서 $(AR) -rcs $(NAME) $^ 부분이 명령어이다.

명령어가 수행되는 방식

all : $(NAME)

$(NAME) : $(OBJS)
	$(AR) -rcs $(NAME) $^

%.o : %.c
	$(CC) $(CFLAGS) -c $^ -I$(HEADER)

실행할 타겟이 선언되면, 그 타겟의 필요조건이 우선 검사된다.
만약 필요조건에 해당하는 파일이 없다면 해당 파일이 타겟으로 존재하는지 찾아본 후 그 타겟으로 이동하여 다시 필요조건을 검사한다.
반대로 필요조건에 해당하는 파일이 있다면 아랫줄의 명령어를 실행한다.
즉, 위 코드의 작동 순서는 다음과 같다.

  1. make 또는 make all 입력시 all타겟을 찾아 필요조건인 $(NAME)의 존재유무를 검사한다.
  2. libft.a로 선언된 변수 $(NAME)은 아직 생성되지 않았다. 따라서 $(NAME)이라는 타겟이 있는지 검사한다.
  3. $(NAME)타겟으로 이동하여 필요조건 $(OBJS)의 존재유무를 확인한다.
    변수 OBJS$(SRCS:.c=.o)로 선언되어있는데, 이는 SRCS 변수에 선언되어있는 .c 파일들을 .o로 바꾸어 읽으라는 뜻이므로 결과적으로 ./ft_isalnum.o, ./ft_strlcat.o 등등의 파일들을 가리키게 된다.
  4. $(OBJS)가 가리키는 파일들이 없으므로 다시 타겟을 찾는다. 타겟 %.o는 와일드카드를 쓴 *.o과 같은 의미이므로, 해당 타겟으로 이동하여 필요조건 %.c을 확인한다.
  5. %.c에 해당하는 파일들이 존재하므로 %.o : %.c의 명령어인 $(CC) $(CFLAGS) -c $^ -I$(HEADER)가 실행되어 .o형태의 오브젝트 파일들이 생성된다.
  6. $(OBJS)에 해당하는 파일들이 생성되었으니 다시 이전으로 돌아가 $(NAME) : $(OBJS)의 명령어인 $(AR) -rcs $(NAME) $^가 실행되어 libft.a 라이브러리 파일이 생성된다.
  7. libft.a가 선언되어있는 $(NAME)이 존재하게 되었으니 all : $(NAME)의 명령어가 실행되어야 하지만, 명령어가 없는 더미 타겟이므로 그대로 make의 과정이 종료된다.

PHONY

.PHONY : all clean fclean re bonus

위 타겟들을 실행할 때, 만약 해당하는 타겟과 같은 이름을 가진 파일이 디렉토리에 존재한다면 해당 타겟은 정상적으로 실행되지 않는다.
예를들어 make clean을 실행하는데, 이미 디렉토리에 clean이라는 이름의 파일이 존재할 경우 make 명령어는 clean이라는 파일의 유무를 먼저 검사한다.
이후 clean 파일의 변경점이 없다면 명령을 무시하게 되므로, 지정한 clean 타겟은 실행되지 않는다.
따라서 .PHONY에 해당 타겟들을 등록하여 같은 이름을 가진 파일의 유무와 관계없이 항상 명령에 따른 타겟을 실행하도록 해 안정성을 높일 수 있다.


의문점 및 생각해볼점

  1. 리링크
  2. 헤더파일은?

Makefile의 타겟 실행시 필요조건의 변화가 없을 경우, 해당 타겟을 재실행하는 것에는 아무런 의미가 없다.
따라서 그런 식으로 다시 링크되는 것을 막고자, Makefile 자체에서 타겟과 필요조건에 해당하는 파일들의 수정시간을 체크하여 비교한다.

  1. 필요조건의 수정시간이 타겟의 수정시간보다 뒤라면 필요조건의 변화가 생긴 것이므로 타겟도 달라질 필요가 있다. 즉, 타겟이 수행된다.
  2. 필요조건의 수정시간이 타겟의 수정시간보다 앞이라면 필요조건에 아무런 변화가 없는 것이다. 그러므로 make: Nothing to be done for 'all'. 이런 식의 메세지만을 출력하고 Make 과정을 종료한다.

헤더파일 참조

Makefile은 기본적으로 헤더파일들을 Makefile이 위치한 현재 디렉토리에서 찾는다.

%.o : %.c
	$(CC) $(CFLAGS) -c $^

따라서 같은 디렉토리에 헤더파일이 있다면 위와 같이 따로 명시하지 않더라도 정상적으로 컴파일 명령어가 실행된다.

HEADER = ./include
%.o : %.c
	$(CC) $(CFLAGS) -c $^ -I$(HEADER)

만약 헤더파일을 다른 디렉토리에 넣고싶다면 -I디렉토리(-I와 참조하고자 하는 디렉토리를 붙여써야함)을 추가해주면 된다.