Makefile 이 도대체 뭘까?

서재환·2021년 5월 21일
4

Makefile

목록 보기
1/1

Makefile

다음은 Makefile과 관련하여 linux manual에 나와 있는 내용이다. 프로그램을 관리하는 프로그램이고 특정 프로그램의 일정 부분이 수정돼 재 컴파일이 필요할 경우 이를 용이하게 해주는 파일이다.

make - GNU make utility to maintain groups of programs

The purpose of the make utility is to determine automatically which pieces of a large program need to be recompiled, and issue the commands to recompile them.

Makefile manual 링크

Makefile 의 필요성

  • 프로그램 개발 시 라인이 길어지게 되면 여러개의 모듈로 나누어 개발이 진행하게 되는데 이 때 입력 파일이 바뀌게 되면 바뀐 파일과 관계가 있는 파일 또한 다시 컴파일을 해야 하는 불편함이 발생한다. 이 때 Makefile 을 만들어 빌드(실행 파일 만들기)시 불편함을 줄일 수 있다.

Makefile의 기본 뼈대

target 내용 : prerequisite 내용
	command 내용
예제 1-0)

test : main.o read.o write.o
	gcc -o test main.o read.o write.o
main.o : main.c
	gcc -c main.c
read.o : read.c
	gcc -c read.c
write.o : write.c
	gcc -c write.c

목표 (target)

  • 원하는 변수명을 설정해 줄 수 있다.
  • 목적파일 (.o), 실행파일 (.out), 라이브러리 (*.a) 등 만들고 싶은 파일명을 적는다. 예제 1-0에선 target 란에 총 4개(test, main.o write.o read.o)가 적혔다.

명령 (command)

  • 꼭 Tab을 사용하여 작성한다. Shell 명령어, C언어, Python 등 다양한 언어로 command를 작성할 수 있다고 한다. 그런데 Shell 명령어만 써봤다.

전제 조건 (Prerequisite)

  • target 옆란에 작성한다. 보통 타겟을 만들기 위해 필요한 전제조건을 쓴다. 아래 예제 1-0 을 보면 test 실행 파일 옆에 있는 란에 main.o read.o write.o 이 있는데 모두 실행 파일을 만들기 위해 필요한 파일들이다.

작동 원리 (Principle)

  • 예제 1-0에서 작성한 내용을 Makefile에 복사해서 붙여넣고 make 명령어를 쳐서 실행시키면 아래와 같이 명령어가 순차적으로 실행이 된다. 실행 파일을 만들기 위한 목적파일을 기준으로 1) 목적파일이 없거나 2) 목적 파일이 있는데 목적파일이 만들어진 시점보다 최근의 *.c 파일이 있을 경우 recompilation 을 해서 최종 target을 생성하게 된다.
    gcc -c main.c
    gcc -c read.c
    gcc -c write.c
    gcc -o test main.o read.o write.o
  • 또 주목할 것은 make 프로그램은 처음으로 인식한 target의 prerequisite 에 접근하고 해당 내용에 적힌 내용이 target이 된 영역으로 가 꼬리에 꼬리를 물어 command가 실행되는 절차를 볼 수 있다.

    다시 말해 test 파일을 만들기 위해 필요한 절차들이 모두 실행된다. 그리고 꼬리에 꼬리를 무는 재귀적 작업을 make 프로그램이 수행하기 위해서는 작성자가 위에서 배운 형식에 맞춰 작성해야 한다.

예제(1-1); 작성하는 것에도 순서가 있다.

test : main.o read.o write.o
	gcc -o test main.o read.o write.o
main.o : main.c
	gcc -c main.c
read.o : read.c
	gcc -c read.c
write.o : write.c
	gcc -c write.c
  • 위의 예제에선 만들려는 최종 target 인 test를 먼저 작성해주어야 Makefile 최종적으로 test 실행파일을 만들 수 있다. 즉 순서가 있는데 재귀적으로 작성해주지 않으면 위에 make로 쉘에서 실행시켰을 때 첫번 째 줄만 실행하게 된다. 아래 예제를 통해 한번 확인해 보도록 하자.

예제(1-2) 코드의 순서를 바꾸어도 test 실행파일을 만들 수 있을까?

main.o : main.c
	gcc -c main.c
read.o : read.c
	gcc -c read.c
write.o : write.c
	gcc -c write.c코드를 입력하세요
test : main.o read.o write.o
	gcc -o test main.o read.o write.o
  • 만약에 예제 1-2 에서 test 를 맨 뒤로 빼서 코드를 작성해서 make를 쉘에 입력할 경우 main.o 목적파일만 생성하고 코드가 종료된다. 순서가 중요하다. 그렇다면 순서에 구애받지 않고 작성하는 방법이 없을까? 그러니까 첫 시작명령을 표시해주는 것이 없을까? 있다!

예제(1-3) 최종 target을 생성하기 위한 all 의 도입

all : test

main.o : main.c
	gcc -c main.c
read.o : read.c
	gcc -c read.c
write.o : write.c
	gcc -c write.c
test : main.o read.o write.o
	gcc -o test main.o read.o write.o
  • all 을 먼저 적어주게 되면 test 파일을 만들기 위해 target 인 test 를 먼저 찾아 실행시키기 때문에 순서와 상관없이 Makefile 이 정상적으로 작동하게 된다. 즉 위와 같이 작성해 주어도 최종적으로 test 파일이 만들어 진다. 다만 현재 디렉토리 내에 main.c, write.c, read.c 3개의 파일은 반드시 있어야 한다.

Macro makes Makefile happy

이번에는 Macro의 사용을 통해 위에서 작성한 코드를 줄이는 방법에 대해서 알아보자 !

예제(2-0), 매크로를 통해 작성해 보자.

  • 매크로 이름은 사용자가 설정할 수 있고 보통 대문자로 작성한다.
  • 매크로 이름을 설정했다면 해당 변수에 대응시키고자 하는 것을 '=' 우측에 작성해준다.
  • 설정한 매크로에 대응하는 값을 매칭시켰다면 기본 룰 기반에서 사용할 때 매크로로 설정한 이름을 $() 안에 넣어주기만 하면 된다. 예시를 살펴보자.
매크로로 CC, TARGET, OBJS를 만들었고 대응하는 값으로 각각 gcc, test, main.o 
read.o write.o 값을 넣어주었다. 기본 뼈대 위에 변수 선언 하듯이 먼저 작성해주면 된다.

그 다음 아래에서 사용하기 위해서 매칭되는 부분이 있을 경우 $()안에 매크로명을 넣어주기만 하면
끝!
CC = gcc
TARGET = test
OBJS = main.o read.o write.o

all : $(TARGET)

$(TARGET) : $(OBJS)
	$(CC) -o $(TARGET) $(OBJS)
    
main.o : main.c
	$(CC) -c main.c
read.o : read.c
	$(CC) -c read.c
write.o : write.c
	$(CC) -c write.c

예제(2-1), $@ 와 $^을 사용해서 위의 예제 2-0을 더 간단히 해보자.

CC = gcc
TARGET = test
OBJS = main.o read.o write.o

all : $(TARGET)

$(TARGET) : $(OBJS)
	$(CC) -o $@ $^
    
main.o : main.c
	$(CC) -c main.c
read.o : read.c
	$(CC) -c read.c
write.o : write.c
	$(CC) -c write.c
  • $@ 는 해당 블록의 target 을 의미하는 표시이고 $^는 prerequisite 영역을 의미한다. $(OBJS) 가 dependency 란에 써주었기 때문에 치환할 수 있는 것이지 $^ 표시가 $(OBJS) 을 의미하는 것은 아니라는 의미이다.

예제(2-2), %o : %c 를 사용해서 위 코드를 더 단축해 보자

CC = gcc
TARGET = test
OBJS = main.o read.o write.o

all : $(TARGET)

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

%.o : %.c
	$(CC) -c $< -o $@
  • %o 는 target 영역에 있고 %c는 prerequisite 영역에 있다. %가 의미하는 것은 .o로 끝나는 모든 파일을 의미한다. 전제조건란에는 %.c가 있으므로 .c로 끝나는 모든 파일이 그에 해당한다.

  • command 란에 $(CC) -c $< -o $@ < 부분을 해석하면 gcc -c (파일이름).c -o (파일이름).o 인데 위에서 작성한 다음 코드부분을 수행시키기 위해 $(OJBS)에 해당하는 목적파일 main.o read.o write.o가 필요하므로 각각을 생성해주어야 한다.

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

따라서 makefile은 main.o를 만들기 위해 main.c를 찾아 gcc -c main.c -o main.o를 수행할 것이고 그다음 gcc -c read.c -o read.o 마지막으로 gcc -c write.c -o write.o를 수행할 것이다.

  • $< 가 의미하는 것이 전제조건란에서의 첫번째 항목을 가리키고 %.c의 첫번째 항목은 main.o에 대응하는 main.c이므로 이를 수행하게된다.
  • 내부 작동원리를 정확히 알지 못하지만 main.o read.o write.o 순으로 main.c read.c write.c 순으로 컴파일 하는 것을 보면 %.o : %.c에 해당하는 모든 파일을 찾아서 컴파일 하는 것으로 볼 수 있다.

예제(3-0) clean 을 통해 makefile 로 만든 부산물들을 한번에 지워보자

CC = gcc
TARGET = test
OBJS = main.o read.o write.o

all : $(TARGET)

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

.c.o :
	$(CC) -c -o $@ $<

clean :
	rm -f $(OBJS) $(TARGET)
  • clean 에 해당 하는 명령어인 rm -f main.o read.o write.o test 로 설정시 make clean 명령어를 쳤을 때 명령어에 적힌 내용이 실행되는 것을 알 수 있다.

예제(3-1) target으로 작성한 이름과 디렉토리 내에 파일이름이 서로 같을 경우 make 프로그램이 오작동이 일어나지 않도록 .PHONY를 통해 작성해 주자.

CC = gcc
TARGET = test
OBJS = main.o read.o write.o

all : $(TARGET)

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

.c.o :
	$(CC) -c -o $@ $<

clean :
	rm -f $(OBJS) $(TARGET)
    
.PHONY : clean
  • 위와 같이 작성해주며 디렉토리내에 clean이라는 파일 명이 있을 경우 make clean 작성시 Makefile에서 작성한 대로 실행되는 것을 알 수 있따. 만일 .PHONY에 적어주지 않을 경우 make clean을 했을 때 원하는 바가 실행되지 않는다.
TARGET = 최종 만들 파일이름 적는 란
OBJS : 오브젝트 파일 적는 란

CC = 컴파일 적는 란
CFLAGS = gcc의 옵션 적는 란
INC = include 되는 헤더파일의 경로 적는 란

C09) Makefile로 libft.a 만들기

  • 마지막으로 정적 라이브러리를 Makefile로 만드는 실습을 해보고 글을 마무리 하고자 한다.
 TARGET = libft.a
 
 SRCS = srcs/ft_putchar.c srcs/ft_swap.c srcs/ft_putstr.c srcs/ft_strlen.c srcs/ft_strcmp.c
 OBJS = $(SRCS:.c=.o)
 INC = includes/ft.h
 
 CC = gcc
 CFLAGS = -c -Wall -Wextra -Werror
 
 all : $(TARGET)
 
 $(TARGET) : $(OBJS)
          ar -cr $@ $^
 %.o: %.c
          $(CC) $(CFLAGS) $< -o $@ -I $(INC)
 clean :
          rm -rf $(OBJS)
 fclean : clean
          rm -rf $(TARGET)
 re : fclean all
 
 .PHONY : all clean fclean re	
<매크로 설정 부분>
TARGET = libft.a

SRCS = srcs/ft_putchar.c srcs/ft_swap.c srcs/ft_putstr.c srcs/ft_strlen.c srcs/ft_strcmp.c
OBJS = $(SRCS:.c=.o)

INC = includes/ft.h

CC = gcc
CFLAGS = -c -Wall -Wextra -Werror

-> 현재 디렉토리 내의 디렉토리 srcs와 includes 폴더 내에 있는 *.c와 *.o 파일을 각각 매크로 처리해주었다.
------------------------------------------------------------------------------------------
< >
all : $(TARGET)
 
$(TARGET) : $(OBJS)
          ar -cr $@ $^
%.o: %.c
         $(CC) $(CFLAGS) $< -o $@ -I $(INC)
clean :
         rm -rf $(OBJS)
fclean : clean
         rm -rf $(TARGET)
re : fclean all

.PHONY : all clean fclean re

------------------------------------------------------------------------------------------
-> 라이브러리를 만들기 위한 형식이고 참고링크를 첨부합니다.
	
    1. ar -cr lib(파일이름) (목적파일...)이다.

정적라이브러리 링크

0개의 댓글