make 명령어는 Unix/Linux 시스템에 포함되어 있는 명령어이다. 주어진 쉘 명령어들을 조건에 맞게 실행해준다.
makefile이라는 파일을 참조하여 컴파일러에 명령을 전달하며, makefile에 기술된 Shell명령어들이 순차적으로 실행될 수 있게 한다.
Makefile은 어떤 조건으로 명령어를 실행할지 담은 파일이며, make를 터미널 상에서 실행하면 해당 위치에 있는 Makefile을 찾아서 읽는다.
Makefile은 간단하게 생각해서 rule이라는 것을 통해 어떤 명령어를 어떻게 실행할지를 표시한다.
rule은 rule의 target이라고 불리는 어떤 파일들을 언제 어떻게 make하는지를 지정한다.
이것은 목적물의 dependencies인 다른 파일들 리스트와 그 목적물을 생성하거나 갱신하는 데 사용될 "명령"을 담고 있다.
다음은 rule의 작성 형식이다.
targets : dependencies
command
target
make를 실행할 때 어떤 것을 make할지 전달할 대상을 의미한다.
make exmaple
dependencies
필요조건을 의미한다.
주어진 타겟을 make할 때 사용될 파일들의 목록이며, 해당 타겟을 처리하기 위해 필요한 파일들을 써놓았기 때문에 다른 말로 의존 파일이라고도 한다.
만약 주어진 파일들의 수정 시간보다 타겟이 더 나중에 수정되었다면 이미 타겟이 만들어졌다고 간주하기 때문에 해당 타겟의 명령어를 실행하지 않는다.
command
주어진 타겟을 make할 때 실행할 명령어들을 의미한다.
주의할 점: make가 Makefile을 제대로 읽어들이기 위해서는 recipe 자리에 명령어를 쓸 때 탭으로 한 번 들여쓰기를 해줘야한다.
예시
prog.o: prog.c cc -c prog.c # prog.o 타겟 # prog.c 필요 조건, prog.o를 만들기 위해서는 prog.c가 필요하다. # cc -c prog.c 명령어, 쉘에 cc -c prog.c를 입력한다. # *주의:cc -c prog.c 앞에는 탭 문자
Makefile내에서 다음과 같이 =를 이용하여 변수를 지정할 수 있다.
NAME = example
주의할 점은 =로 변수를 정의했을 때 정의에 다른 변수가 포함돼있다면 해당 변수가 정의될 때까지 변수값이 정해지지 않는다.
즉 다음과 같은 예시에서 C는 B가 정의되지 않았기 때문에 빈 문자열이 되어야하지만, 이 경우 C는 hello가 된다.
B = $(A)
C = $(B)
A = hello
또한 :를 이용하여 명령어를 지정할 수 있다.
간단한 Makefile의 예제는 다음과 같다.
Makefile을 다음과 같이 작성한 후,
MESSAGE = Helloworld!
example :
echo $(MESSAGE)
쉘에서 make example을 입력하면 출력은 다음과 같다.
>> make example
echo Helloworld!
Helloworld!
참고
GNU make에서 변수명=값 으로 변수를 선언하고,$(변수) 식으로 괄호로 묶어서 사용할 수 있다.다음의 명령은 cc - c prog.c와 일치한다.
foo = c prog.o : prog.$(foo) $(foo)$(foo) -$(foo) prog.$(foo)변수 값 앞에 있는 공백들은 변수 할당에서 무시되기 때문에 foo는 정확하게 c이다.
그 외 작성 규칙은 아래에서 다룬다.
다음과 같은 Makefile이 있다고 했을 때의 설명이다.
CC = cc
CFLAGS = -Wall -Wextra -Werror
NAME = libmy.a
SRC = ./srcs/my_putchar.c ./srcs/my_strcmp.c ./srcs/my_swap.c ./srcs/my_putstr.c ./srcs/my_strlen.c
OBJS = $(SRC:.c=.o)
INC = -Iincludes/
HEADER = ./includes/my.h
all : $(NAME)
$(NAME) : $(OBJS)
ar -rscu $(NAME) $(OBJS)
%.o : %.c $(HEADER)
$(CC) $(CFLAGS) -c $(INC) $< -o $@
clean :
rm -f $(OBJS)
fclean : clean
rm -f $(NAME)
re : fclean all
.PHONY: all clean flcean re
아래 부분은 변수를 지정해 준 것이다.
src 폴더에 .c 파일들이 있고, OBJS = $(SRC:.c=.o)에서 OBJS를 목적파일들로 지정해줬다.
SRC에서 .c를 .o로 바꿔준 것이니 .o 파일들도 srcs 폴더안에 생성될 것 이다.
CC = cc
CFLAGS = -Wall -Wextra -Werror
NAME = libmy.a
SRC = ./srcs/my_putchar.c ./srcs/my_strcmp.c ./srcs/my_swap.c ./srcs/my_putstr.c ./srcs/my_strlen.c
OBJS = $(SRC:.c=.o)
INC = -Iincludes/
HEADER = ./includes/my.h
아래 부분은 rule을 지정한 것이다.
먼저 all 아래에는 명령어가 없다. 일종의 프로토타입 선언이라고 생각하면 편한데, 쉘에 아무것도 입력되지 않는다는 의미이므로 필요한 파일, 즉 dependencies에 최종적으로 만들어질 파일의 이름을 명시해준 것이다. 이렇게만 말하면 이해가 가지 않을테니 그 다음 설명을 들어보자.
all을 위해서는 NAME, 즉 libmy.a가 필요하다. 하지만 현재 libmy.a를 찾을 수 없으니, NAME을 만들기 위한, 즉 NAME이 target인 rule이 있는지 찾는다.
all : $(NAME)
그 다음 rule에 NAME이 target인 규칙이 있다. 그런데 현재 상태에서는 OBJS도 없다. 따라서 OBJS를 만들기 위한 rule이 있는지 찾는다.
$(NAME) : $(OBJS)
ar -rscu $(NAME) $(OBJS)
그 다음 rule에 .o로 끝나는 모든 파일 즉, OBJS가 target인 규칙이 있다. 이 dependencies에 있는 파일들은 모두 있다. 따라서 이 명령이 먼저 실행된다.
%.o : %.c $(HEADER)
$(CC) $(CFLAGS) -c $(INC) $< -o $@
그러면 OBJS는 생성됐으니 다시 이 규칙이 실행되고
$(NAME) : $(OBJS)
ar -rscu $(NAME) $(OBJS)
NAME이 있으니 all이 실행되는 것이다. 그리고 이때는 쉘에 아무런 명령이 없으니 결과적으로 NAME, 즉 우리의 최종 target인 libmy.a가 생성되고 끝나는 것이다.
all : $(NAME)
이 흐름을 본다면 target을 만드는데 필요한 재료 파일들의 이름이 왜 dependencies인지 알게 될 것이다. 정말로 저 재료파일들이 없으면 못만들기 때문에 target은 재료파일들에 종속적인 것이다.
아래 규칙들은 각각 목적파일을 지우고, 목적파일과 최종 타겟인 NAME을 지우고, 모두 삭제했다가 다시 all하는 규칙들이다.
clean :
rm -f $(OBJS)
fclean : clean
rm -f $(NAME)
re : fclean all
. Phony Target 지정하기
phony는 가짜라는 의미로, 실제 파일 이름과 충돌하는 문제를 해결할 수 있다.
.PHONY의 dependencies는 make명령어와 함께 쓰였을 때는 그 dependencies의 command가 무조건적으로 실행된다..PHONY: all clean fclean re
위의 예시에서 .PHONY의 dependencies로 작성된 all, clean, fclean, re는 이미 그 디렉토리에 같은 이름의 파일이 존재해도 그것과 상관없이 Makefile의 command가 수행된다는 것이다.
1. 매크로의 정의는 '='를 포함하는 하나의 문장이다.
왼쪽에는 매크로 이름, 오른쪽에는 정의문자열이 온다. 매크로 이름은 주로 대문자로 작성한다.
NAME = libexample
2. #은 주석문이다.
주석을 작성할 때는 #으로 한다.
NAME = libexample #주석
3. 여러행을 사용할 때는 \를 이용한다.
CC = cc -Wall -Wextra \
-Werror
4. 매크로를 참조할 때는 괄호나 중괄호를 둘러싸고 $를 붙힌다.
이는 앞에서 다루었으니 넘어간다.
5. 중복된 정의는 마지막 정의를 이용한다.
A = a
A = aa
--> A에는 aa가 저장된다.
6. 매크로를 정의할 때 이전에 정의된 매크로를 참조할 수 있다.
FRUIT = apple
DINNER = eat $(FRUIT) #eat apple
- $@ 현재 Target 이름
- $* 확장자가 없는 현재의 Target
- $% 대상의 이름 (해당 규칙 대상이 archive인 경우)
- $< 현재 Target이 의존하는 대상들 중 변경된 것들의 목록
- $? 현재 Target이 의존하는 대상들 중 변경된 것들의 목록
- $^ 현재 Target이 의존하는 대상들의 전체 목록
이미 정의된 매크로의 내용을 조그만 바꾸어야 할 때 유용하게 사용할 수 있다.
$(MACRO_NAME:OLD=NEW)과 같은 형식을 이용하여 매크로의 일부만 바꿀 수 있다.
예를 들어 여러가지의 .c파일이 있는데 이를 모두 .o 파일로 바꾸기 위해서는 다음과 같이 적용할 수 있다.
SRC = putchar.c strcmp.c swap.c putstr.c strlen.c
OBJS = $(SRC:.c = .o)
#OBJS에는 putchar.o strcmp.o swap.o putstr.o strlen.o가 저장된다.
make : Makefile 실행
make clean : 오브젝 파일 삭제
make fclean : 오브젝 파일과 실행 파일 삭제
make re : make fclean 실행 후 다시 make