OS를 만들면서 지금까지는 makefile을 거의 배치 파일 수준으로 사용하고 있었습니다. 이게 무슨 말이냐 하면... 디렉토리에서 소스 파일 이름만 달랑 추출하고, .C 파일과 같은 소스 파일이 수정되었을 때만 커널을 다시 빌드한다는 이야기입니다. 귀차니즘때문에 이렇게 쓰시는 분들이 많을 것 같다는... ^^;;;; 이렇게 되면 헤더 파일이 수정되었을 때, 해당 헤더 파일을 당겨쓰는 .C 파일들이 자동으로 빌드되지 않기 때문에 문제가 생깁니다.

뭐, 사실 혼자 테스트 할때는 매번 Clean해서 전부 삭제하고 다시 빌드하면 되니 신경을 안썼는데... 거사(?)를 치루려다 보니 이런것 하나하나가 신경 쓰이더군요. 결국 makefile을 뒤엎어서 의존성 관련 부분을 추가했습니다. 거의 3시간 정도 이것만 한 것 같네요. ㅠㅠ

C 파일로 부터 의존성 정보를 뽑는건 아주 간단합니다. "gcc -c -M Main.c Main2.c > a.txt" 와 같이 옵션을 주면 아래와 같이 이쁘게 출력해 줍니다. 이렇게 생성한 a.txt 파일을 makefile에 포함시켜주면 나머지는 규칙에 따라서 알아서 빌드해 줍니다. :)
Main.o: ../Source/Main.c ../Source/Main.h
Main2.o: ../Source/Main2.c

makefile에 포함시키려면 다음과 같이 써주면 됩니다. $(wildcard a.txt)는 디렉토리에서 a.txt 패턴이 들어간 파일명들을 스페이스바로 구분한 리스트를 나타내며, ifeq는 두 항목을 비교하는 것입니다. 즉 아래 코드는 같은 디렉토리에 a.txt가 있으면 해당 파일을 makefile에 포함시키라는 의미입니다.
ifeq (a.txt, $(wildcard a.txt))
include a.txt
endif

위의 내용을 추가해서 만든 makefile을 실행한 것입니다. 의존 관계에 대한 정보가 아주 자알~ 생성되고 있음을 볼 수 있습니다. ;)
C:\MINT64\01.Kernel32>make dep
make -C Temp -f ../makefile InternalDependancy
make[1]: Entering directory `/c/MINT64/01.Kernel32/Temp'
gcc.exe -c -M ../Source/Main.c ../Source/Main2.c > .dependancy
make[1]: Leaving directory `/c/MINT64/01.Kernel32/Temp'

아아~ 이거 원... 마음이 급해서 날림으로 만들었더니, 이런데서 시간을 많이 잡아먹는군요. ㅎㅎ 하루를 그냥 날린 것 같습니다. ㅠㅠ 나머지 작업은 할 수 없이 내일 해야겠네요. ;)

그럼 다들 좋은 밤 되세요 ;)

02 간단한 Make 사용법

원문 : https://kkamagui.tistory.com/84

들어가기 전에...

0.시작하면서...

통합 개발툴(IDE)를 이용해서 개발하는 경우에 굳이 Makefile을 생성할 필요는 없지만 IDE를 사용하지 않고 개발할 경우, 많은 소스파일을 관리하기위해서 makefile이 필수이다. make는 실제로 굉장히 많은 기능을 가지고 있지만, 그중 몇가지 유용한 기능만 알아도 훨씬 코드 관리가 편리해 진다.

이제 그 몇가지에 대해서 알아보자. @0@)/~

1.makefile의 기본 형식

makefile은 기본적으로 아래와 같은 형식으로 이루어진다.

Target File : Source File(Dependency) 
    <tab> command

좌측의 Target File은 make command의 실행 결과로 생성할 파일이고 Source File은 make command에 사용될 파일이다. 아래의 command는 탭으로 한칸 띄우는데 이 탭이 command의 구분자의 역할을 한다. 만약 Abc.c 파일을 gcc -c로 컴파일 해서 Abc.o 파일을 생성한다면 아래와 같이 된다(주의-대소문자 구분함).

Abc.o : Abc.c 
    <tab> gcc -c Abc.o Abc.c

실제로 여러 파일을 생성한다고 가정하면 위와 같은 코드들이 아래와 같이 나열된다.

# 이건 주석입니다.
Abc.o : Abc.c 
    <tab>gcc -c Abc.o \\   <- 멀티라인 사용법
        Abc.c
Abb.o : Abb.c 
    <tab> gcc -c Abb.o Abb.c

A.exe : Abc.o Abb.o 
    <tab> gcc -o A.exe Abb.o Abc.o
...... 반복.....

주석은 #로 시작하고 멀티라인은 \를 마지막 라인에 추가하면 된다. 아주 간단하지 않는가? 위와 같이 파일을 생성하여 makefile을 만든 후, make.exe를 실행하면 자동으로 makefile을 읽어서 위의 조건에 따라 파일을 생성해 준다.

만약 어떤 Target File이 생성되기 위해 다른 파일이 Target File이 필요한 경우 makefile이 알아서 필요한 파일 순서대로 결과 파일을 만든 후 최종 결과물을 만들어 준다. 위의 makefile을 수행하면 A.exe와 각종 .o 파일들이 생긴다.

2.매크로(Macro)의 사용

만약 소스파일이 손으로 칠 수 없을 만큼 많다면? 또는 어떤 Target File을 생성하는데 규칙이 일정하다면?(.c 파일을 .o 파일로 바꾸는 것과 같은 경우) 과연 이런 경우에도 일일이 다 손으로 써줘야 할까?

매크로를 이용하면 이것을 편리하게 할 수 있다. GNU-Make에서 매크로는 아래와 같이 정의하고 사용한다.

OBJECT = A.o B.o C.o
CC = gcc
A.exe : $(OBJECT)
    <tab> $(CC) -o A.exe $(OBJECT)

OBJECT 매크로가 없다면 일일이 손으로 저 파일들을 다 쳐줘야 할 것을 OBJECT 매크로를 정의함으로써 편리하게 해결했다.

그럼 이제 좀더 이것을 확장해 보자. 일반적으로 .c 파일을 .o 파일로 만드는 규칙은 거의 동일하다. 컴파일 옵션에 -c 옵션을 줄 것이며 A.o A.c와 같은 같은 파일명을 사용하고 확장자만 다르다. 이것을 어떻게 간단하게 할 수 있을까? 답은 패턴 규칙을 이용하는 것이다. %를 사용하는 아래를 보자.

%.o : %.c 
    <tab> gcc -c $@ $<

위와 같이 하면, .c가 들어간 파일은 .o로 바꾸어 주는데 gcc -c 옵션을 적용하여 .o 파일을 생성한다. 즉 %는 확장자를 제외한 파일명을 의미한다. $@와 같은 생소한 문자를 볼 수 있는데, 이것은 make에서 미리 정의해둔 매크로로 아래와 같은 것들이 있다.

  • $@ : Target File. 파일명 및 확장자 포함. 왼쪽 전체 패턴을 치환
  • $< : Source File. 파일명 및 확장자 포함. 오른쪽 첫번째 패턴을 치환
  • $* : 파일명만 포함
  • $^ : Source File. 오른쪽 전체 리스트를 치환

그럼 패턴 규칙을 이용할때 파일명 그대로만 사용 가능할까? 답은 "그렇지 않다" 이다. 아래와 같은 사용도 가능하다.

%\_debug.o : %.c
    <tab> gcc -c $@ $<

위처럼하면 파일이름에 _debug가 붙은 파일을 생성할 수 있다. 저 규칙을 잘 이용하면 여러가지 종류의 파일을 생성할 수 있다.
자 그러면 매크로로 정의한 내용 중에 필요한 내용만 따로 치환할 수 는 없을까? 물론 가능하다. 아래는 .o를 .c로 치환하는 문장이다.

OBJECT = a.o b.o c.o d.o
SOURCE = $(OBJECT:.o=.c)

아주 간단하지 않은가? @0@)/~ 정말 놀랍다. @0@)/~~!!!!

그럼 만약 target에서 사용될 이름과 동일한 파일명이 존재한다면? 즉 make clean과 같이 clean을 사용해야 하는데 마침 이름이 clean인 항목이 있다면 어떻게 할까? make clean을 하면 해당 항목이 컴파일 될 것인데... 우리가 원하는 것은 그것이 아니다...

이런 경우 아래와 같이 사용하면 된다.

.PHONY : clean

.PHONY가 의미하는 것은 clean이 build의 대상이 아님을 말한다.

이번에는 make 실행 시 입력값에 의해 Active하게 플래그를 변경해서 실행해 보자. makefile안에 여러 모드로 컴파일하는 기능도 넣어보자. 아무옵션이 없이 그냥 make만 실행되었을 때, 디폴트로 가는 레이블은 all 이다. 이 기능을 이용해서 디폴트 Target 파일을 지정해보고 clean 기능을 넣어서 .o 파일을 모두 지우게 하자.

all : A.exe

.... 어쩌구 저쩌구 ...

A.exe : $(OBJ) 
    <tab> $(CC) $(FLAG) $(OBJ)

.... 어쩌구 저쩌구 ...

clean :
    <tab> del \*.o

위 처럼 Source File 즉 Dependency에 아무것도 넣지 않거나 만들 Target File 명을 넣으면 된다. A.exe를 만들고 싶으면 "make"라고 입력하던지 "make all"이라고 입력하면 되고 clean 기능을 동작 시키려면 "make clean"이라고 입력하면 된다.

조금 더 응용해서 make 시에 컴파일 플래그를 조절하도록 해보자

ifdef FULL
FLAG = -f -c
endif

위와 같이 한 뒤에 "make FULL=yes" 라고 실행하면 FULL 플래그를 설정하여 FLAG를 활성화 시킬 수 있다.

3.디렉토리의 변경

makefile을 생성하여 사용할때 여러개의 프로젝트를 동시에 빌드하여 하나의 output이 나와야 할 경우가 있다. 이런 경우 하위 폴더에 프로젝트들을 넣어두고 상위 폴더에 하위 폴더를 다 make 하는 형식을 사용하는데, 이때 각 프로젝트 폴더로 이동해서 해당 폴더의 makefile을 실행해야한다.

이때 사용하는 옵션이 -C 이고 make를 실행하기 전에 폴더를 변경하라는 옵션이다.

$> make -C "Boot" -f makefile

위의 명령은 Boot 폴더로 폴더를 이용해서 make를 수행하라는 명령이다.

4.출력 메시지

make 도중에 메시지를 화면에 출력하고 싶으면 @echo "메시지" 의 형태로 사용하면 된다.

@echo "빌드중입니다..."

5.마무리-makefile 적용

5.1 Build 파일을 직접 선택

자 이제 이것을 실제로 적용해 보자. 아래의 makefile은 make에 대해서 잘 몰랐을 때, 노가다로 작성한 파일이다. 상당히 불현하고 특히 자주 파일이 추가되거나 변경되는 Custom 폴더의 경우 파일이 추가될때 마다 일일이 명령을 추가해줘야하는 단점이 있었다.

#   Kernel Make File  
#  
#   Written KKAMAGUI, [http://kkamagui.egloos.com](http://kkamagui.egloos.com/)
all: Kkernel

# Compile 옵션  
GCC = gcc -ffreestanding -c  
NASM = nasm -f coff  
FWDIR = FW/  
CUSTOMDIR = Custom/

# Frame Work 파일  
# Compile할 파일 이름 다 적기  
A.o: $(FWDIR)Asm.asm  
    $(NASM) -o A.o $(FWDIR)Asm.asm  
Is.o: $(FWDIR)Isr.asm  
    $(NASM) -o Is.o $(FWDIR)Isr.asm  
K.o: $(FWDIR)Kernel.c  
    $(GCC) -o K.o $(FWDIR)Kernel.c  
D.o: $(FWDIR)Descriptor.c  
    $(GCC) -o D.o $(FWDIR)Descriptor.c  
Int.o: $(FWDIR)Interrupt.c  
    $(GCC) -o Int.o $(FWDIR)Interrupt.c  
Key.o: $(FWDIR)Keyboard.c  
    $(GCC) -o Key.o $(FWDIR)Keyboard.c  
Stdlib.o : $(FWDIR)stdlib.c  
    $(GCC) -o Stdlib.o $(FWDIR)Stdlib.c  
Task.o : $(FWDIR)Task.c  
    $(GCC) -o Task.o $(FWDIR)Task.c

# 응용 프로그램 파일  
FW.o : $(CUSTOMDIR)Framework.c  
    $(GCC) -o FW.o $(CUSTOMDIR)FrameWork.c  
KShell.o : $(CUSTOMDIR)KShell.c  
    $(GCC) -o KShell.o $(CUSTOMDIR)KShell.c  
Sched.o : $(CUSTOMDIR)Scheduler.c  
    $(GCC) -o Sched.o $(CUSTOMDIR)Scheduler.c

#Object 파일 이름 다 적기  
#아래의 순서대로 링크된다.  
OBJ = A.o K.o Is.o D.o Int.o Key.o Stdlib.o Task.o FW.o KShell.o Sched.o
Kkernel: $(OBJ)  
    ld $(OBJ) -o kkernel.bin --oformat binary -Ttext 0x100000
clean:  
    del \*.o

위의 makefile을 배운 것을 이용하여 간단하게 정리하면 아래와 같이 쓸 수 있다.

#   Kernel Make File  
#  
#   Written KKAMAGUI, [http://kkamagui.egloos.com](http://kkamagui.egloos.com/)
all: kernel

# Compile 옵션 및 폴더 설정  
GCC = gcc -ffreestanding -c  
NASM = nasm -f coff  
FWDIR = FW/  
CUSTOMDIR = Custom/

#Object 파일 이름 다 적기  
#아래의 순서대로 링크된다. 새로운 파일이 생기면 뒤에다 추가하자  
OBJ = Asm.o Kernel.o Isr.o Descriptor.o Interrupt.o Keyboard.o StdLib.o Task.o \\  
      FrameWork.o KShell.o Scheduler.o

#컴파일할 파일의 확장자에 따른 규칙
# FW 폴더 밑의 파일들 컴파일
%.o: $(FWDIR)%.asm  
    $(NASM) -o $@ $<  
%.o: $(FWDIR)%.c  
    $(GCC) -o $@ $<

# Custom 폴더 밑의 파일들 컴파일  
%.o : $(CUSTOMDIR)%.c  
    $(GCC) -o $@ $<

# 최종 링크  
kernel: $(OBJ)  
    ld $(OBJ) -o kkernel.bin --oformat binary -Ttext 0x100000
clean:  
    del \*.o

우와~ 정말 깔끔해졌다. 확장 또한 한부분만 추가하면 되니까 훨씬 간단해 졌다. makefile 사용해서 효율적인 프로젝트 관리를 해보자.

5.2 특정 폴더의 특정 확장자의 파일을 모두 빌드

이클립스 버전으로 옮겨가면서 makefile 또한 정리하였다. 자세한 내용은 21 OS 프레임워크 소스 릴리즈의 릴리즈 파일을 참고하도록하고 아래는 00Kernel 폴더의 makefile이다.

#   Kernel Make File  
#  
#   Written KKAMAGUI, [http://kkamagui.egloos.com](http://kkamagui.egloos.com/)
all: kernel

# Compile 옵션 및 폴더 설정  
GCC = djgcc -ffreestanding -c  
LD = djld  
NASM = nasm -f coff  
FWDIR = FW  
CUSTOMDIR = Custom  
SOURCEDIR = $(FWDIR) $(CUSTOMDIR)

#Object 파일 이름 다 적기  
#아래의 순서대로 링크된다. 새로운 파일이 생기면 뒤에 다 추가하자  
#커널에 꼭 필요한 Object 파일들. ASM.o 파일은 항상 제일 앞에 와야한다. 그 이유는  
#커널의 엔트리포인트가 있는 함수이기 때문이다.  
ESSENTIALOBJ = Asm.o Isr.o

# 디렉토리에 있는 C 파일들을 다 찾아서 넣도록 한다.  
CFILE = $(foreach dir,$(SOURCEDIR),$(notdir $(wildcard $(dir)/\*.c )))  
    CFILEOBJ = $(CFILE:.c=.o)  

#확장자 규칙을 이용해서 컴파일 한다.  
%.o: $(FWDIR)\\%.asm  
    $(NASM) -o $@ $<  
%.o: $(FWDIR)\\%.c  
    $(GCC) -o $@ $<  
%.o: $(CUSTOMDIR)\\%.asm  
    $(NASM) -o $@ $<  
%.o: $(CUSTOMDIR)\\%.c  
    $(GCC) -o $@ $<  

# 최종 링크  
kernel: $(ESSENTIALOBJ) $(CFILEOBJ)  
    @echo "==> Making Kernel..."  
    $(LD) $(ESSENTIALOBJ) $(CFILEOBJ) -o kkernel.bin --oformat binary -Ttext 0x100000  
    @echo "==> Complete"

clean:  
    del \*.o  
    del kkernel.bin

6.make 문법 quick reference

GNU make의 quick refernce는 http://www.viper.pe.kr/docs/make-ko/make-ko_15.html 에서 찾을 수 있다.

+ Recent posts