23년 8월 22일 : MakeFile
최근 리눅스 상에서 프로그래밍을 하면서 Makefile을 이용하여 C 내지는 C++ 소스 코드를 실행 파일로 변환시켜야 하는 일들이 많아졌다. 생각난 김에 한 번쯤은 Makefile에 대해서 정리할 필요가 있어서 이 글을 쓰게 되었다.
우선 컴퓨터가 이해할 수 있는 것은 오직 0과 1 밖에 없다는 것이 출발점이다. 못할 것은 없지만 사람이 0과 1만을 가지고 필요한 프로그램을 작성하는 것은 매우 번거롭고 힘든 일일 것이다. 그렇기에 자연어에 가까운 프로그래밍 언어(C/C++, Java, Python 등)을 사용하여 코드를 짜고, 이를 컴파일러(Compiler)로 기계어로 번역하여 사용하게 된다. 소스 코드를 최종적으로 기계어로 번역하는 과정을 컴파일(Compile)이라고 한다.
하나의 소스 코드 파일에 모든 것을 넣어서 짤 수도 있다. 그러나 대부분의 경우, 가독성, 협업 등의 이유로 하나의 프로그램에 대해 여러 소스 코드 파일이 존재할 수 있다. 이들을 하나의 실행 가능한 파일로 만들기 위해서는 각각의 소스코드 파일을 컴파일하는 것 뿐만 아니라 이들을 하나로 합쳐줄 필요가 있다.(이를 링킹(Linking)이라 한다!) Makefile은 하나의 프로그램을 만들기 위해 필요한 컴파일 및 링킹을 기술해 놓은 레시피 파일이다. Makefile을 이용하면 좀 더 쉽게 프로그래밍을 만들 수 있을 것이다.(생각해보라!! 수 백, 수 천 가지의 소스코드를 일일이 컴파일하고 이들을 링킹하기 위해 한 줄, 한 줄 명령어를 적어야 한다면 정말 많은 시간을 소모해야할 것이다!!)
Makefile을 작성하기 위한 문법에 대해서 알아보자. 일일이 문법에 대해서 추상적으로 설명하기 보다는 하나의 예시를 들어 설명하는 것이 나을 것 같다. 문법에 대해서는 주석으로 설명하겠다. 다음은 내가 자작 OS를 따라 만들면서 사용하고 있는 Makefile이다.
TARGET = kernel.elf #변수 선언 방법 : 변수명=변수값, 이때 변수값은 문자열이다. 변수를 사용하려면 $(변수명)으로 사용한다
OBJS = main.o graphics.o mouse.o font.o hankaku.o newlib_support.o console.o \
pci.o asmfunc.o libcxx_support.o logger.o interrupt.o segment.o paging.o memory_manager.o \
window.o layer.o timer.o frame_buffer.o \
usb/memory.o usb/device.o usb/xhci/ring.o usb/xhci/trb.o usb/xhci/xhci.o \
usb/xhci/port.o usb/xhci/device.o usb/xhci/devmgr.o usb/xhci/registers.o \
usb/classdriver/base.o usb/classdriver/hid.o usb/classdriver/keyboard.o \
usb/classdriver/mouse.o
DEPENDS = $(join $(dir $(OBJS)),$(addprefix .,$(notdir $(OBJS:.o=.d))))
CPPFLAGS += -I.
CFLAGS += -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone
CXXFLAGS += -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone \
-fno-exceptions -fno-rtti -std=c++17
LDFLAGS += --entry KernelMain -z norelro --image-base 0x100000 --static
#기본적인 틀 :
#타깃 파일 : 의존 파일
# 만들기 위한 명령어
.PHONY: all # .PHONY : make (서브 명령어)와 make (파일명)을 구분하기 위해 사용 / .PHONY : 서브 명령어
all: $(TARGET)
.PHONY: clean
clean:
rm -rf *.o .*.d *.bin *.elf
kernel.elf: $(OBJS) Makefile
ld.lld $(LDFLAGS) -o kernel.elf $(OBJS) -lc -lc++ -lc++abi
# $@ : 타겟 이름에 대응됩니다.
# $< : 의존 파일 목록에 첫 번째 파일에 대응됩니다.
# $^ : 의존 파일 목록 전체에 대응됩니다.
# $? : 타겟 보다 최신인 의존 파일들에 대응됩니다.
# $+ : $^ 와 비슷하지만, 중복된 파일 이름들 까지 모두 포함합니다.
# %.~ : 특정 형식으로~, *와 비슷한 역할
%.o: %.cpp Makefile
clang++ $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
.%.d: %.cpp
clang++ $(CPPFLAGS) $(CXXFLAGS) -MM $< > $@
$(eval OBJ = $(<:.cpp=.o))
sed --in-place 's|$(notdir $(OBJ))|$(OBJ)|' $@
%.o: %.c Makefile
clang $(CPPFLAGS) $(CFLAGS) -c $< -o $@
.%.d: %.c
clang $(CPPFLAGS) $(CFLAGS) -MM $< > $@
$(eval OBJ = $(<:.c=.o))
sed --in-place 's|$(notdir $(OBJ))|$(OBJ)|' $@
%.o: %.asm Makefile
nasm -f elf64 -o $@ $<
hankaku.bin: hankaku.txt
python3 ../tools/makefont.py -o $@ $<
hankaku.o: hankaku.bin
objcopy -I binary -O elf64-x86-64 -B i386:x86-64 $< $@
.%.d: %.bin
touch $@
.PHONY: depends
depends:
$(MAKE) $(DEPENDS)
-include $(DEPENDS) # include (file) : 파일을 Makefile에 추가하시오/ -(l or L)/(directory path)로도 외부 라이브러리 추가 가능
# -include $(OBJS:.o=.d) OBJS의 .o 부분을 .d로 바꾸고 include할 것(링킹)
Makefile 문법에 대해서 간단하게 다루어 보았다. 주석으로 설명한 부분을 이용하여 Makefile을 만드는 것이 가능해졌다. CXXFLAGS, CFLAGS, LDFLAGS는 각각 C++에 대한 컴파일 옵션, C에 대한 컴파일 옵션, 링킹 옵션으로 Makefile 자체 보다는 컴파일러, 링커에 대한 내용이다. 이들에 대해서는 다음에 다루어 보는 것으로 하고 이 글을 마친다.
이 번 글의 출처 : https://modoocode.com/311
씹어먹는 C++ - <19 - 1. Make 사용 가이드 (Makefile 만들기)>
modoocode.com