Search
Duplicate

[Makefile] Makefile 의존성 처리

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Makefile
태그
Scrap
8 more properties

make 주요 옵션

-d (--debug) : Makefile 수행과 관련된 모든 정보 출력
-p (--print-data-base) : 내부적으로 세팅되어 있는 값 출력
-r (--no-builtin-rules) : 내부적으로 세팅되어 있는 값 제거
-k (--keep-going) : 에러가 발생해도 멈추지 말고 계속 진행
-f 파일 : 명시된 파일을 사용하여 make 수행
-C 디렉토리 : 명시된 디렉토리로 이동하여 make 수행
makefile 동작을 확인하고 싶을 때 -d-p 옵션을 사용해보는 것을 권장한다.

내장 매크로의 활용

make -p를 사용하여 내부 매크로를 확인할 수 있다.
make -r을 사용하여 내부 매크로를 제거할 수 있다.
# 매크로 정의 RM = rm -f CC = cc AR = ar CFLAGS = -Wall -Wextra -Werror ARFLAGS = rsc SRCS = main.c read.c write.c OBJS = main.o read.o write.o NAME = test.a # 타겟 정의 all: $(NAME) $(NAME): $(OBJS) $(AR) $(ARFLAGS) $(NAME) $(OBJS) clean: $(RM) $(OBJS) fclean: clean $(RM) $(NAME) re: fclean $(MAKE) all .PHONY: all clean fclean re # 의존성 정의 main.o: io.h main.c $(CC) $(CFLAGS) -c main.c read.o: io.h read.c $(CC) $(CFLAGS) -c read.c write.o: io.h write.c $(CC) $(CFLAGS) -c write.c
Makefile
# 매크로 정의 RM = rm -f # default. 생략 가능 CC = cc # default. 생략 가능 AR = ar # default. 생략 가능 CFLAGS = -Wall -Wextra -Werror ARFLAGS = rsc SRCS = main.c read.c write.c OBJS = $(SRCS:.c=.o) # 치환 참조 NAME = test.a # 타겟 정의 all: $(NAME) $(NAME): $(OBJS) $(AR) $(ARFLAGS) $@ $^ clean: $(RM) $(OBJS) fclean: clean $(RM) $(NAME) re: fclean $(MAKE) all .PHONY: all clean fclean re # 의존성 정의 (전체 생략 가능)
Makefile

의존성 생략

.SUFFIEXES 내장 매크로에 의해 확장자 규칙 패턴을 검사하는 항목이다.
내부적으로 기본으로 등록된 확장자가 있으면 내부 변수를 활용할 수 있다.
# 기본 값 .SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .m .r .y .l .ym .yl .s .S .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch .web .sh .elc .el CC = cc COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c %.o: %.c $(COMPILE.c) $(OUTPUT_OPTION) $< ...
Makefile

오브젝트 생성 %.o : %.c

CFLAGS: C 컴파일 시 사용될 플래그 (예: -Wall -Wextra -Werror)
CXXFLAGS : C++ 컴파일 시 사용할 플래그
CPPFLAGS: 전처리기(cpp)를 호출해야 할 때 사용될 플래그 (예: -I./include)
# 기본 값 CC = cc CFLAGS = CPPFLAGS = TARGET_ARCH = OUTPUT_OPTION = -o $@ COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c %.o: %.c $(COMPILE.c) $(OUTPUT_OPTION) $< .c.o: $(COMPILE.c) $(OUTPUT_OPTION) $<
Makefile
# 활용 예시 OBJS = main.o read.o write.o CFLAGS = -Wall -Wextra -Werror # 기본값 덮어쓰기 CPPFLAGS = -I. # 기본값 덮어쓰기
Makefile
$(OBJS)에 대한 규칙이 없으므로 .SUFFIXES에 의해 %.o: %.c 규칙이 자동 수행됨

정적 라이브러리 생성 (%) : %

ARFLAGS: ar 실행 시 사용할 플래그 (예: rsc)
# 기본 값 AR = ar ARFLAGS = rv %.a: (%) : % $(AR) $(ARFLAGS) $@ $<
Makefile
# 활용 예시1 - 권장하지 않음 (해결방법 찾으면 댓글 부탁드립니다!) NAME = libft.a $(NAME) : $(patsubst %,$(NAME)(%),$(OBJS)) # relink 발생...!!
Makefile
# 활용 예시2 - 권장 NAME = libft.a %.a: $(AR) $(ARFLAGS) $@ $? # 기본 명령어 덮어쓰기 $(NAME): $(OBJS)
Makefile
libft.a의 레시피가 없으므로 .SUFFIXES에 의해 %.a 규칙이 자동 수행됨

바이너리 생성 % : %.o

라이브러리를 링킹할 때 순서가 중요하므로 LDLIBS를 사용하는것을 권장한다.
main.o > libftprintf.a > libft.a 순서대로 의존한다면 다음과 같이 실행된다.
$ cc main.o -lftprint -lft # 성공 $ cc main.o -lft -lftprint # 오류 $ cc -lftprint -lft main.o # 오류
Shell
LDFLAGS: 링커 ld를 호출해야 할 때 사용될 플래그. (예: -L./lift).
LDLIBS: 링커 ld를 호출해야 할 때 사용될 플래그 또는 이름. (예: -lft)
LOADLIBES: 더 이상 사용되지 않음.
# 기본 값 CC = cc LDFLAGS = TARGET_ARCH = LOADLIBES = LDLIBS = LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH) %: %.o $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ .o: $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
Makefile
# 예시 LDFLAGS = -L. LDLIBS = -lft OBJS = main.o read.o write.o NAME = push_swap $(NAME): $(OBJS)
Makefile
$(OBJS)에 대한 규칙이 없으므로 .SUFFIXES에 의해 %.o: %.c규칙이 자동 수행됨
$(NAME)의 레시피가 없으므로 .SUFFIXES에 의해 %: %.o 규칙이 자동 수행됨

헤더 변경 시 재컴파일

CC 명령어의 -MMD -MP 옵션을 사용하여 의존성 파일을 자동으로 생성할 수 있다.
-MMD : 컴파일 시 의존성 파일 생성
-MP : 의존성 파일에 .PHONY 타겟 추가
$ cc -Wall -Wextra -Werror -MMD -MP -c main.c $ vi main.d main.o: io.h main.c io.h:
Makefile
.DEFAULT_GOAL := all # 제일 상단의 타겟이 아닌 all을 기본 타겟으로 설정 CFLAGS = -Wall -Wextra -Werror -MMD -MP ARFLAGS = rsc SRCS = main.c read.c write.c OBJS = $(SRCS:.c=.o) DEPS = $(SRCS:.c=.d) -include $(DEPS) # $(DEPS) 파일의 내용을 Makefile에 include # '-' 생략 시, .d 파일이 없을 때 make 오류가 발생하므로 주의! NAME = test.a all: $(NAME) $(NAME): $(OBJS) $(AR) $(ARFLAGS) $@ $^ clean: $(RM) $(OBJS) $(DEPS) fclean: clean $(RM) $(NAME) re: fclean $(MAKE) all .PHONY: all bonus clean fclean re
Makefile
이제 헤더파일을 수정하고 make를 수행하면 라이브러리가 재컴파일 되는 것을 확인할 수 있다!

옵션에 따른 소스코드 처리 (all, bonus)

make 수행 시 전달되는 타겟 목록은 $(MAKECMDGOALS)라는 변수에 저장된다.
$(if 조건문, 참, 거짓) : 조건에 따라 참 또는 거짓 반환
$(filter 문자열1, $(문자열2)) : 문자열2에서 문자열1이 있는 지 검색
.DEFAULT_GOAL := all CFLAGS = -Wall -Wextra -Werror -MMD -MP ARFLAGS = rsc src_m = mandatory.c src_b = bonus.c SRCS = $(src_m) $(if $(filter bonus, $(MAKECMDGOALS)), $(src_b)) OBJS = $(SRCS:.c=.o) DEPS = $(SRCS:.c=.d) -include $(DEPS) NAME = test.a all: $(NAME) $(NAME): $(OBJS) $(AR) $(ARFLAGS) $@ $^ clean: $(RM) $(wildcard *.o) $(wildcard *.d) fclean: clean $(RM) $(NAME) re: fclean $(MAKE) $(if $(filter bonus, $(MAKECMDGOALS)), bonus, all) .PHONY: all bonus clean fclean re
Makefile

외부 Makefile 활용

외부 Makefile의 이름은 아무거나 지정해도 상관없지만 보통 .mk 확장자를 붙인다.

include

C에서의 include와 동일하다.
include 파일명 : 필수적으로 include 하는 파일 (파일이 없으면 오류 발생)
-include 파일명 : 선택적으로 include 하는 파일 (파일이 없어도 된다)
$ vi Rules.mk CFLAGS = -Wall -Wextra -Werror ARFLAGS = rsc ...
Makefile
$ vi Makefile include 파일명 ...
Makefile

make -f

한 번의 make를 통해 2개 이상의 결과물을 만들 때 유용하다.
$ vi Makefile all: server client server: $(MAKE) -f Makefile.server client: $(MAKE) -f Makefile.client
Makefile
위와 같이 파일을 분리하는 경우 코드가 깔끔하고 관리하기 편해진다.
또한, vimdiff 명령어로 한 눈에 차이점이 파악되어 추후 분석 시간이 줄어든다.

make -C

하위 디렉토리의 Makefile을 수행할 때 주로 사용된다.
SERVER = server CLIENT = client all bonus clean fclean re: $(MAKE) -C lib $@ $(MAKE) -C src $@ $(SERVER) $(CLIENT): $(MAKE) -C lib $(MAKE) -C src $@ .PHONY: all clean fclean re bonus
Makefile

[참고] 하위 라이브러리 빌드

.DEFAULT_GOAL = all CFLAGS = -Wall -Wextra -Werror -MMD -MP CPPFLAGS = -I./include LDFLAGS = -L./lib LDLIBS = -lftprintf -lft LIBFT = libft/libft.a $(LIBFT): $(MAKE) -C $(@D) ################################################################## PUSHSWAP = push_swap PUSHSWAP_SRCS = common.c push_swap.c PUSHSWAP_OBJS = $(PUSHSWAP_SRCS:.c=.o) PUSHSWAP_DEPS = $(PUSHSWAP_SRCS:.c=.d) -include $(PUSHSWAP_DEPS) $(PUSHSWAP): $(PUSHSWAP_OBJS) $(PUSHSWAP_OBJS): $(LIBFT) ################################################################## CHECKER = checker CHECKER_SRCS = common.c checker.c CHECKER_OBJS = $(CHECKER_SRCS:.c=.o) CHECKER_DEPS = $(CHECKER_SRCS:.c=.d) -include $(CHECKER_DEPS) $(CHECKER): $(CHECKER_OBJS) $(CHECKER_OBJS): $(LIBFT) ################################################################## all: $(MAKE) -C $(dir $(LIBFT)) $(MAKE) $(PUSHSWAP) bonus: $(MAKE) -C $(dir $(LIBFT)) $(MAKE) $(CHECKER) clean: $(MAKE) -C $(dir $(LIBFT)) clean $(RM) $(wildcard *.o) $(wildcard *d) fclean: clean $(RM) $(LIBFT) $(PUSHSWAP) $(CHECKER) re: fclean $(MAKE) all .PHONY: all clean fclean re bonus
Makefile

[참고] 계층형 구조 빌드

구조
. ├── Rules.mk ├── Makefile ├── include ├── lib │ ├── Makefile │ ├── ftprintf │ │ ├── Makefile │ │ ├── ... │ │ └── ft_printf.h │ └── libft │ ├── Makefile │ ├── ... │ └── libft.h └── src ├── Makefile ├── Makefile.client ├── Makefile.server ├── client.c └── server.c
Makefile
Rules.mk : 공통 규칙 작성
Makefile : 최상위 디렉토리 Makefile
include : 헤더 파일 링크를 위한 빈 디렉토리
lib : 라이브러리 파일 링크를 위한 디렉토리
src :

참고