금요일, 2월 06, 2015

make 유틸리티 강좌 (2/4)

make 유틸리티 강좌 (2/4)



번  호 : 195
게시자 : 임대영   (RAXIS   )
등록일 : 1995-10-25 03:25
제  목 : [강좌]  make  [2]                    
---------------------------------------------------------------------
                    make 유틸리티 강좌  - 2 -

                        작성 : RAXIS ( 임대영 )
---------------------------------------------------------------------

2번째 강좌가 늦어진 점 죄송하게 생각합니다. 오늘의 강좌내용입니다.

목차
        3. 매크로( Macro ) 와 Suffix 규칙
             3.1 매크로의 정의 ( What is macro )
             3.2 미리정해져이는 매크로 ( Pre-defined macro )
             3.3 확장자 규칙 ( Suffix rule )
             3.4 내부 매크로 ( Internal macro )

-------------------------------------------------------------------
3. 매크로( Macro ) 와 Suffix 규칙
-------------------------------------------------------------------

******************************************
  3.1 매크로의 정의( What is Macro )
******************************************

앞 강좌에서 매크로에 대해서 대충 언급을 했다. 프로그램을 짜본 사람이나 로터스,한글,엑셀등등의 모든 패키지에서 매크로라는 것을 사용하게 된다. 은연중에 매크로의 정의는 대충 짐작하고 있을것이다. 이미 알고 있는바와 같이 매크로는 특정한 코드를 간단화시켜 포현한것에 지나지 않는다. Makefile 에서 사용되는 매크로는 비교적 그 사용법이 간단하기 때문에 금방 익혀서 사용할 정도가 된다.

매크로의 정의는 프로그램을 작성할때 변수를 지정하는 것처럼 하면 된다. 그리고 매크로를 사용하기 위해서는 $(..) 을 이용하면 된다. 아래는 매크로의 간단한 예제이다.

[ 참고 - 매크로의 사용에서 ${..} $(..) $.. 를 모두 사용할수 있습니다.
[        그러나 대부분의 책에서는 $(..) 을 사용하라는 군요.

----Makefile [예제 4]--------------------------

OBJS = main.o read.o write.o

test : $(OBJS)                   <------(1)
        gcc -o test $(OBJS)

     ..........
----------------------------------------------

강좌 1에서 다루었던 예제와 거의 비슷하다. 매크로는 사실상 복잡한것을 간단하게 표시한것에 지나지 않는다. (1) 번을 매크로를 안쓰고 표현한다면 아마 아래와 같이 될것이다.

----Makefile [예제 5]--------------------------

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

----------------------------------------------

[ 참고 - [예제 5]가 더 쉽지 않느냐고 반문하는 사람은 매크로의 위력을
[        잘 모르는 사람입니다. 거의 모든 소프트웨어에서 매크로를 지원
[        하는 이유를 한번 잘 생각해봅시다. [예제 4] 의 (1) 부분이 이해
[        하기 난해하다고 하실지는 모르겠지만, 대충 형식이 정해져 있기
[        때문에 조금만 익숙해 지면 오히려 더 편할겁니다.

make 에 관해 설명한 책에 다음과 같은 명언(?) 이 나온다.

    Macro makes Makfile happy. ( 매크로는 Makefile 을 기쁘게 만든다.)

이 말은 Makefile을 작성함에 있어 매크로를 잘만 이용하면 복잡한 작업도 아주 간단하게 작성할수 있음을 말해주는 말이 아닐까 생각한다. 매크로에 대해서는 더이상 말할것이 없다. ( 너무 간단하죠 ? ) 이제 남은것은 여러분들이 자신의 매크로를 어떻게 구성하느냐이다. 어떤것을 매크로로 정의해햐 할지는 여러분들의 자유이며, 나중에 전반적인 지침을 설명할 것이다.

***************************************************
3.2 미리 정해져있는 매크로 ( Pre-defined macro )
***************************************************

여러분들보다 머리가 약간 더 좋은 사람들이 make 라는것을 만들면서 미리 정해놓은 매크로들이 있다. 'make -p' 라고 타이핑해보면 make 에서 미리 세팅되어 있던 모든 값들( 매크로, 화경변수(environment)등등)이 엄청 스크롤 된다. 이값들을 보고 미리 주눅이 들 필요는 없다. 어차피 대부분의 내용들은 우리가 재정의 해주어야 하기 때문에 결론적으로 말하면 우리가 모두 작성한다고 생각하는것이 맘 편하다.

아래에는 대부분이 UNIX 계열의 make 에서 미리 정해져 있는 매크로들중 몇가지만 나열해본 것이다.

---- Predefined Macro [예제 6] --------------------------

ASFLAGS =                    <-- as 명령어의 옵션 세팅
AS      = as
CFLAGS  =                    <-- gcc 의 옵션 세팅
CC      = cc (= gcc )
CPPFLAGS =                   <-- g++ 의 옵션
CXX     = g++
LDLFAGS =                    <-- ld 의 옵션 세팅
LD      = ld
LFLAGS  =                    <-- lex 의 옵션 세팅
LEX     = lex
YFLAGS  =                    <-- yacc 의 옵션 세팅
YACC    = yacc
MAKE_COMMAND    = make

--------------------------------------------------------

[ 참고 - 직접 make -p 를 해서 한번 확인해보세요. 과연 make 는 내부적
[        으로 어떤 변수들을 사용하고 있는지 알아봅시다. 매크로는 관습
[        적으로 대분자로 작성되니까 이점에 유의해서 보세요.
[        make는 쉘상에서 정의한 환경변수값들을 그대로 이용한다는 것을
[        알고 계시기 바랍니다.

위에 열거한 매크로는 make 에서 정의된 매크로중 그야말로 일부에 지나지 않는다. 하지만 프로그램을 작상함에 있어 가장 많이 사용하게될 매크로 들이다. 이들 매크로는 사용자에 의해 재정의 가능하다. 가령 gcc 의 옵션 중에 디버그 정보를 표시하는 '-g' 옵션을 넣고 싶다면, 아래와 같이 재정의 한다.

   CFLAGS = -g

[예제 6] 의 각종 FLAG 매크로 들은 대부분 우리가 필요에 의해 세팅해 주어야 하는 값들이다. 왜 굳이 make 에서 값도 정의되지 않은 매크로를 우리가 정의 해서 써야 하는지 의문을 던질지도 모른다. 우리가 더 이쁜 이름으로 매크로를 정의할수도 있다고 하면서....

여기서 한가지 사실을 생각해봐야 할것이다. make에서 위에 나온 것들을 왜 미리 정해두었을까 ? ( 왜일까요? ) make 에서 이들 매크로를 제공하고 있는 이유는 내부적으로 이들 매크로를 사용하게 되기 때문이다. 어떻게 이용하는지는 확장자 규칙(Surfix rule)을 설명하면서 해답을 제공할 것이다.  이제 [예제 4] 의 Makefile을 매크로를 이용하여 깔끔하게(?) 작성해보자.

----Makefile [예제 7]--------------------------

OBJECTS = main.o read.o write.o
SRCS    = main.c read.c write.c  <-- 없어도 됨

CC      = gcc       <-- gcc 로 세팅
CFLAGS  = -g -c     <-- gcc 의 옵션에 -g 추가

TARGET  = test      <--- 결과화일을 test 라고 지정

$(TARGET) : $(OBJECTS)
        $(CC) -o $(TARGET) $(OBJECTS)

clean  :
        rm -rf $(OBJECTS) $(TARGET) core

main.o : io.h main.c       <-- (1)
read.o : io.h read.c
write.o: io.h write.c
----------------------------------------------

위의 Makfile 을 동작시켜보자.

% make
gcc -g -c main.c -o main.o
gcc -g -c read.c -o read.o
gcc -g -c write.c -o write.o
gcc -o test main.o read.o write.o  <- OK

% make clean
rm -rf main.o read.o write.o test core  <- OK

그런데 여기서 한가지 이상한 점을 발견하게 될것이다. .c 화일을 .o 화일로 바꾸는 부분이 없는데 어떻게 컴파일이 되었을까? 빼먹고 타이핑 못한것은 아닐까하고 ...  절대아님 !

앞에서 CFLAGS 같은 매크로는 make 화일의 내부에서 이용된다고 하였다. 그렇다면 make 는 과연 어디에서 이용을 할까?  바로 컴파일 하는곳에서 이용을 하는것이다. 따라서 우리는 CFLAGS 를 세팅해주기만 하면 make 가 알아서 컴파일을 수행하는 것이다. ( 얼마나 편리합니까.)

[ 참고 - 확장자 규칙 에서 다시 한번 자세히 설명을 하겠습니다.

(1) 에 해당하는 부분은 어떤 화일이 어디에 의존하고 있는지를 표시해 주기 위해서 꼭 필요하다. .c 화일을 컴파일하는 부분은 일괄적인 루틴으로 작성할수 있기 때문에 이들 화일간의 의존관계(dependency)를 따로 표시해주어야 한다.

[ 참고 - 화일간의 의존관계를 자동으로 작성해주는 유틸리티가 있습니다.
[        이것은 다음 번 강좌에서 다루기로 합니다.

***********************************
  3.3 확장자 규칙 ( Suffix rule )
***********************************

확장자 규칙이란 간단히 말해서 화일의 확장자를 보고, 그에 따라 적절한 연산을 수행시키는 규칙이라고 말할수 있다. 가령 .c 화일은 일반적으로 C 소스코드를 가르키며, .o 화일은 목적화일(Object file)을 말하고있다. 그리고 당연히 .c 화일은 컴파일 되어서 .o 화일이 되어야 하는것이다.

여기서 한가지 매크로가 등장하게 된다. .SUFFIXES 라고 하는 매크로인데 우리가 make 화일에게 주의깊게 처리할 화일들의 확장자를 등록해 준다고 이해하면 될것이다.

.SUFFIXES = .c .o

위의 표현은 .c 와 .o 확장자를 가진 화일들을 확장자 규칙에 의거해서 처리될수 있도록 해준다. .SUFFIXES 매크로를 이용한 예제를 살펴보자.

----Makefile [예제 8]--------------------------

.SUFFIXES = .c .o

OBJECTS = main.o read.o write.o
SRCS    = main.c read.c write.c

CC      = gcc    
CFLAGS  = -g -c

TARGET  = test

$(TARGET) : $(OBJECTS)
        $(CC) -o $(TARGET) $(OBJECTS)

clean  :
        rm -rf $(OBJECTS) $(TARGET) core

main.o : io.h main.c    
read.o : io.h read.c
write.o: io.h write.c
----------------------------------------------

위의 Makfile 을 동작시켜보자.

% make
gcc -g -c main.c -o main.o
gcc -g -c read.c -o read.o
gcc -g -c write.c -o write.o
gcc -o test main.o read.o write.o  <- OK

확장자 규칙에 의해서 make 는 화일들간의 확장자를 자동으로 인식해서 필요한 작업을 수행한다. 즉 아래의 루틴이 자동적으로 동작을 하게된다.

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

[ 참고 -  gmake 에서는 약간 다르게 정의 되어 있지만, 우선은 같다고
[         이해합시다. $< , $@ 에 대해서는 곧 설명합니다.

우리가 .SUFFIXES = .c .o 라고 했기 때문에 make 내부에서는 미리 정의된 .c( C 소스 )를 컴파일해서 .o(목적화일)을 만들어내는 루틴이 자동적으로 동작하게 되어 있다.  CC 와 CFLAGS 도 우리가 정의한 대로 치환될 것임은 의심할 여지가 없다.

make 내부에서 기본적으로 서비스를 제공해주는 확장자들의 리스트를 열거해보면 아래와 같다. 각 확장자에 따른 자세한 설명은 생략한다.

.out .a .ln .o .c .cc .C .p .f .F .r .y .l .s .S .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch .web .sh .elc .elf

Makefile 내부에서 .SUFFIXES 매크로의 값을 세팅해주면 내부적으로 정의된 확장자의 연산이 동작을 하게 된다. 따라서 확장자 규칙은 make 가 어느 확장자를 가진 화일들을 처리할것인가를 정해주는 것이라고 생각할 수 있다.

그러나 이것은 필자만의 생각이지만... make 에서 자동적으로 확장자를 알아서 해주는 것이 좋긴 하지만, 필자는 일부러 위의 .c.o 에 해당되는 부분을 그냥 정의해서 쓰길 더 좋아한다. 이것은 지금까지의 습관상 그렇지만 왠지 우리가 정의하는것이 더 자유롭게(flexible) 사용할수 있을것 같기 때문이다. 그리고 이런 기능은 우리가 작성을 해봐야 make의 메카니즘을 더 잘 이해할수 있다고 생각한다.

[예제 8] 의 내용을 약간 바꾸어보자.

----Makefile [예제 9]--------------------------

.SUFFIXES = .c .o

OBJECTS = main.o read.o write.o
SRCS    = main.c read.c write.c

CC      = gcc    
CFLAGS  = -g -c
INC     = -I/home/raxis/include  <-- include 패스 추가

TARGET  = test

$(TARGET) : $(OBJECTS)
        $(CC) -o $(TARGET) $(OBJECTS)

.c.o :                          <-- 우리가 확장자 규칙을 구현
        $(CC) $(INC) $(CFLAGS) $<

clean  :
        rm -rf $(OBJECTS) $(TARGET) core

main.o : io.h main.c
read.o : io.h read.c
write.o : io.h write.c
----------------------------------------------

% make
gcc -I/home/raxis/include -g -c main.c
gcc -I/home/raxis/include -g -c read.c
gcc -I/home/raxis/include -g -c write.c
gcc -o test main.o read.o write.o  <-- OK

[예제 8] 과 [예제 9] 의 차이는 그저 .c.o 부분을 누가 처리하느냐이다. 그리고 [예제 9]에서는 INC 라는 매크로를 추가시켜서 컴파일시에 이용하도록 하였다.

***************************************
  3.4 내부 매크로 ( Internal macro )
***************************************

make 에서는 내부 매크로라는 것이 있다. 이것은 우리가 맘대로 정할수 있는 매크로는 절대 아니다. 대신 매크로를 연산, 처리하는데 쓰이는 매크로 라고 하는 것이 더 적당할 것이다.

---- Internal Macro  [예제 10]--------------------

$*  <-- 확장자가 없는 현재의 목표화일(Target)

$@  <-- 현재의 목표화일(Target)

$<  <--  현재의 목표화일(Target)보다 더 최근에 갱신된 화일이름

$?  <--  현재의 목표화일(Target)보다 더 최근에 갱신된 화일이름
-------------------------------------------------

[ 참고 -- 책에서는 $< 와 $? 를 약간 구분하고 있지만 거의 같다고 봐도
[         무방할 것입니다.

각 내부 매크로에 대한 예를 보기로 한다.

main.o : main.c io.h
        gcc -c $*.c

$* 는 확장자가 없는 현재의 목표화일이므로 $* 는 결국 main 에 해당한다.

test : $(OBJS)
        gcc -o  $@  $*.c

$@ 는 현재의 목표화일이다. 즉 test 에 해당된다.

.c.o :
        gcc -c $<      ( 또는 gcc -c $*.c )

$< 는 현재의 목표화일보다 더 최근에 갱신된 화일이름이라고 하였다. .o 화일보다 더 최근에 갱신된 .c 화일은 자동적으로 컴파일이 된다. 가령 main.o 를 만들고 난 다음에 main.c 를 갱신하게 되면 main.c는 $< 의 작용에 의해 새롭게 컴파일이 된다.

[ 참고 - 이제 [예제 9] 을 이해할수 있겠습니까.?

[ 참고 - Makfile 화일을 작성해놓고, 그냥 make 만 치시면 make 는
[        Makefile 의 네용을 살펴보다가 첫번째 목표화일에 해당되는
[        것을 실행시키게 됩니다. 따라서 위의 예제에서는 make test
[        라고 해도 같은 결과를 내게 됩니다. 반면 clean 에 해당하는
[        부분을 윗부분에 두게되면 make 는 항상 make clean 을 수행
[        하게 됩니다.
[
[       % make  <-- make clean 이 실행됨
[        rm -rf main.o read.o write.o test core
[    
[       % make test <-- 강제적으로 test 가 생성되게 한다.
[       gcc -I/home/raxis/include -g -c main.c
[       gcc -I/home/raxis/include -g -c read.c
[       gcc -I/home/raxis/include -g -c write.c
[       gcc -o test write.c main.o read.o write.o  <-- OK


Makefile 의 이해를 돕기 위해서 Makefile 을 하나더 작성해보기로 한다. make.tex 화일을 make.dvi 로 만든다음 이것을 다시 make.ps 로 만드는 것이다. 보통의 순서라면 아래와 같다.

% latex make.tex    <-- make.dvi 가 만들어진다.

% dvips make.dvi -o  <-- make.ps 가 만들어진다.

보통의 가장 간단한 Makefile 을 작성해보면 아래와 같다.

---- Makefile [예제 11]--------------------

make.ps : make.dvi
     dvips make.dvi -o

make.dvi : make.tex
        latex make.tex
------------------------------------------

위와 같은 일을 하는 Makefile 을 다르게 한번 작성해보자. 매크로를 어느정도 사용해보기로 하며, 확장자 규칙을 한번 적용해보기로 한다.

---- Makefile [예제 12]--------------------

.SUFFIXES = .tex .dvi

TEX       = latex         <-- TEX 매크로를 재정의

PSFILE  = make.ps
DVIFILE = make.dvi

$(PSFILE) : $(DVIFILE)
        dvips $(DVIFILE) -o

make.ps  : make.dvi
make.dvi : make.tex
------------------------------------------

[예제 12] 에서는 .tex 와 .dvi 를 처리하는 루틴이 자동적으로 동작을 하게 된다. Makfile 을 한번 동작시켜보자.

% make
latex make.tex
....
dvips make.dvi -o  <-- OK

[예제 11] 과 [예제 12] 는 하는일은 같다. 하지만 [예제 12]는 매크로를 사용함으로써 나중에 내용을 바꿀때 [예제 11]보다 편하다는것을 이해 했으면......

--다음편 예고-------------------------------------------------------
무엇인가를 글로 설명한다는 것이 참 힘드네요..

사실 오늘 한것만 가지고도 Makefile 에 대한 어느 정도의 지식을 갖추었다고 말할수가 있습니다. 자신만의 Makefile 을 한번 작성해보시죠. 프로그램도 괜찮고, .tex 화일을 .ps 화일로 만드는 과정을 Makefile 로 만들어 보는것도 좋은 연습이 될것입니다.

다음편에서 여러분에게 소개해 드릴것은 make 옵션, makefile 작성 지침 (guideline), make 사용시에 나타나는 에러의 원인과 그 대처방법이 될 것 같군요. ( 아직 확정된것은 아니지만서두.) Makefile 에 관한 입문 과정은 다음편으로 끝내고. 4강부터는 약간 고급스러운 기능을 강좌하도록 하겠습니다. 많이 읽어주세요.  댕스~ 였습니다.

댓글 없음:

댓글 쓰기