(수정시간) 2016-07-26 마크다운 연습도 할 겸 아래글 내용을 가져옵니다. 오래된 글이라 새로 적어두는 것도 의미가 있진 않을까요? 개인용 블로그이니 조금 이해해 주시길.. > 원본소스-Makefile 만들기

— 아래 —

환경

설명을 위해 아래와 같이 6개의 소스 파일이 있다고 하겠습니다.

  • main.c와 main.h
  • tcp.c와 tcp.h
  • rs232.c와 rs232.h

  여기서 생각해야 될 점은 main.c를 언제 컴파일하느냐 하는 것입니다. main.c 는 rs232.c의 함수와 tcp.c의 함수를 사용하기 때문에 main.c 자체가 변경되는 것 외에도 rs232.h와 tcp.h가 변할 때에도 컴파일하는 것이 안전합니다.

  • main.c 자체가 수정되었을 경우
  • main.h 가 수정되었을 경우
  • rs232.h 또는 tcp.h 가 변했을 경우

  tcp.c와 rs232.c는 자기 자신이 바뀌거나 해당 헤더파일이 변경되면 다시 컴파일이 되도록 합니다. 이제 이를 위한 간단한 Makefile을 보시겠습니다.

간단한 Makefile 만들기

  Makefile 의 구조는 아래와 같습니다.

목표: 아래 명령을 실행하게 되는 모든 조건에 해당되는 파일 목록     실행 명령어

  이와 같은 구조로 필요한 만큼 나열하면 됩니다. 다음은 실제 Makefile 내용입니다.

명령어 앞에는 반드시 탭 문자로 간격을 띄워야 합니다. 공백으로 띄우시면 make 실행 시 에러가 발생합니다.

명령어 설명
sample : main.o tcp.o rs232.o main.o tcp.o, rs232.o 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.
gcc -lm -o sample main.o tcp.o rs232.o main.o, tcp.o, rs232.o로 실행파일 sample 를 만듭니다.
sample : main.o tcp.o rs232.o main.o tcp.o, rs232.o 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.
sample : main.o tcp.o rs232.o main.o tcp.o, rs232.o 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.

sample : main.o tcp.o rs232.o     gcc -lm -o sample main.o tcp.o rs232.o  <– 명령어, 즉 gcc 앞에는 tab 키로 들여 쓰기를 해야합니다.

main.o : main.c main.h rs232.h tcp.h     gcc -c main.c       <– sample 에서 링크할 것이므로 여기에서는 -c를 사용하여 컴파일만 하겠습니다.

tcp.o : tcp.c tcp.h     gcc -c tcp.c

rs232.o : rs232.c rs232.h    gcc -c rs232.c

main.o tcp.o, rs232.o 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.
     gcc -lm -o sample main.o tcp.o rs232.o main.o, tcp.o, rs232.o로 실행파일 sample 를 만듭니다.
main.o : main.c main.h rs232.h tcp.h main.c, main.h, rs232.h tcp.h 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.
     gcc -c main.c main.c 를 컴파일해서 main.o를 생성합니다.
tcp.o : tcp.c tcp.h tcp.c, tcp.h 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.
     gcc -c tcp.c tcp.c 를 컴파일해서 tcp.o를 생성합니다.
rs232.o : rs232.c rs232.h rs232.c, rs232.h 파일 중 변경되는 파일이 있다면 아래의 명령을 실행합니다.
     gcc -c rs232.c rs232.c 를 컴파일해서 rs232.o를 생성합니다.

  대충 이해가 되시나요? 뭐야 이거? 하시는 분이 계실지 모르겠습니다. 위이 예제는 아주 무식하지만 직관적으로 바로 이해되는 아주 간단한 Makefile 예제가 되겠습니다.

이제부터는 make 로 컴파일 완료

  이제 쉘에서 make 라는 명령으로 간단히 컴파일하고 실행 파일을 만들 수 있습니다.

make

   편하죠? 그런데 Makefile 을 이렇게 만들면 타이핑을 해야할 것이 너무 많지요. 대부분의 프로그래머는 아주 게으릅니다. 타이핑으로 먹고 사는 사람들이 타이핑 많은 거, 좋와하지 않죠.

  이제, Makefile 만의 매크로를 이용하여 타이핑하는 횟수를 줄여 보겠습니다.

특수문자

문자 내용
$@ 목표 이름
$* 목표 이름에서 확장자가 없는 이름
$< 조건 파일 중 첫번째 파일
$? 목표 파일 보다 더 최근에 갱신된 파일 이름

자, 소개한 매크로를 이용하여 Makefile의 내용을 좀더 간단하게 만들 수 있습니다. 링크하는 부분을 먼저 보겠습니다.

sample : main.o tcp.o rs232.o     gcc -lm -o <font color="#000000">**sample**</font> main.o tcp.o rs232.o

  여기서 $@는 무엇일까요? 네, sample이 되겠습니다. 그러므로 아래와 같이 수정할 수 있습니다.

sample : main.o tcp.o rs232.o     gcc -lm -o **$@** main.o tcp.o rs232.o

  이해 되시죠. 이 번에는 $*오 $<, $? 에 대해서 말씀드리겠습니다.

tcp.o : tcp.c tcp.h     gcc -c **tcp.c**

  $는 목표 이름에서 확장자를 제거한 이름이니까 .o를 뺀 tcp가 되겠습니다. 그러므로 $를 이용하면 이렇게 수정할 수 있습니다

tcp.o : tcp.c tcp.h     gcc -c **$*.c**

  이해 되시죠? $<는 조건에 열거된 파일 목록 중 첫번재를 의미합니다. 그러므로 $<는 조건 파일인 tcp.c tcp.h 중에서 첫번째 파일의 이름에 해당되므로 tcp.c 가 되겠습니다. 그러므로 아래와 같이 바꿀 수도 있습니다. 저 같은 경우 제일 많이 사용합니다.

tcp.o : tcp.c tcp.h     gcc -c **$\<**

  $? 는 “현재 목표 파일 파일 보다 더 최근에 갱신된 파일 이름”을 나타내는 매크로입니다. 뜻은 알겠는데, 도대체 어디에 사용하는지 필요성을 잘 모르겠네요. 여하튼 예로 따져 보변 tcp.o 후에 tcp.h 가 수정되었다면 tcp.h가 된다는 것입니다.

확장자 규칙 .c.o

  일반적으로 .o 는 .c 로 만들어 지므로 실은 위의 예를 아래와 같이 명령 실행 없이 작성해도 make가 실행이 됩니다.

sample : main.o tcp.o rs232.o     gcc -lm -o $@ main.o tcp.o rs232.o

main.o : main.c main.h rs232.h tcp.h **-> 명령 실행이 없다.**
tcp.o : tcp.c tcp.h **-> 명령 실행이 없다.**
rs232.o : rs232.c rs232.h **-> 명령 실행이 없다.**

  에이~ 그런데 왜 처음부터 복작하게 썼어? 하시겠지만, 이는 $*, $<, $? 를 설명 드리기 위함도 있지만 위와 같이 처리하면 컴파일은 되지만 컴파일에 대한 상세한 옵션을 처리할 수가 없습니다. 위와 같이 Makefile을 작성하고 make 를 실행하면 아마도 아래와 같이 단순한 모습으로 컴파일 될 것입니다.

]$make cc -c -o main.o mainc cc -c -o tcp.o tcp.c cc -c -o rs232.o rs232.c

  요렇게 말이죠. 그러나 컴파일 할 때에는 인클루드 경로명을 지정하는 것과 같은 옵션을 사용해야 합니다. 이렇게 자동으로 진행되는 것만을 의지할 수 없습니다. 그래서 아래와 같이 .o 를 어떻게 만들어 낼지를 make 에 알려 줍니다.

sample : main.o tcp.o rs232.o     gcc -lm -o $@ main.o tcp.o rs232.o

**.c.o:     gcc -I/home/jwjw/prjs/include -g -c $\<**

main.o : main.c main.h rs232.h tcp.h tcp.o : tcp.c tcp.h rs232.o : rs232.c rs232.h

  이제 make를 실행하면 아래와 같이 컴파일되는 모습을 보실 수 있습니다.

gcc -I/home/jwjw/prjs/include -g -c main.c gcc -I/home/jwjw/prjs/include -g -c tcp.c gcc -I/home/jwjw/prjs/include -g -c rs232.c gcc -lm -o sample main.o tcp.o rs232.o

  또한 소스 파일이 Makefile과 같은 폴더 안에 있다면 아예 아래와 같이 작성하셔도 컴파일이 됩니다.

sample : main.o tcp.o rs232.o     gcc -lm -o $@ main.o tcp.o rs232.o .c.o:     gcc -I/home/jwjw/prjs/include -g -c $<

  그러나 문제는 각 소스에 대한 컴파일 조건이 매우 단순해 지게 됩니다. 즉, main.c 는 main.c 자신이 수정될 때만 컴파일이 됩니다. 위의 예에서 처럼 rs232.h가 수정되거나 아예 관계가 아주 깊은 main.h 가 수정되더라도 main.c 는 재 컴파일이 안됩니다. 소스끼리 관계가 있다며 하단 부분을 서술해 주셔야 합니다.

sample : main.o tcp.o rs232.o     gcc -lm -o $@ main.o tcp.o rs232.o .c.o:     gcc -I/home/jwjw/prjs/include -g -c $<

main.o: main.c main.h rs232.h rs232.o: rs232.c rs232.h tcp.o: tcp.c tcp.h

  그래도 많이 줄어 들었죠? 그러나 이게 다가 아닙니다.

매크로로 치환

  예제의 main.o tcp.o rs232.o 의 파일 이름이 2번 중복되어 있습니다. 두번 타이핑을 해야 하는데, 앞서 말씀드렸듯이 프로그래머는 게으릅니다. 2번~? 귀찮습니다. 아래와 같이 수정해 봅시다.

OBJS = main.o tcp.o rs232.o

sample : $(OBJS)     gcc -lm -o $@ **$(OBJS)** .c.o:     gcc -I/home/jwjw/prjs/include -g -c $<

 또는 $^ 를 이용하여 아래와 같이 수정할 수 도 있습니다. $^는 조건에 있는 모든 파일 이름을 대신하는 매크로입니다.

OBJS = main.o tcp.o rs232.o

sample : $(OBJS)     gcc -lm -o $@ **$^** .c.o:     gcc -I/home/jwjw/prjs/include -g -c $<

 하는 김에 컴파일 옵션과 링크 옵션도 매크로로 치환해 보겠습니다.

OBJS = main.o tcp.o rs232.o CC = -I/home/jwjw/prjs/include -g -c

sample : $(OBJS)     gcc -lm -o $@ **$^** .c.o:     gcc $(CC) $<

 이렇게 매크로로 치환하여 Makefile 의 윗 행에 모아 두면, 내용 전체를 볼 필요 없이 매크로 부분만 보거나 수정해도 되기 때문에 편리합니다. 이래서 아래와 같이 수정하여 완성할 수 있습니다.

TARGET = sample OBJS = main.o tcp.o rs232.o CC = -I/home/jwjw/prjs/include -g -c

$(TARGET : $(OBJS)     gcc -lm -o $@ **$^** .c.o:     gcc $(CC) $<

main.o: main.c main.h rs232.h rs232.o: rs232.c rs232.h tcp.o: tcp.c tcp.h

gccmakedep

  다른 것들은 모두 편리하고 좋은 것 같은데, Makefile 하단에 있는 파일 간의 종속에 대한 정보를 모두 타이핑해서 넣어야 할까요? 파일이 많을 경우 어떻게 일일이 입력할 수 있있겠습니까?  당연한 말씀입니다. 게으른 프로그래머에게는 말도 안되죠. 그래서 이와 같은 귀찮은 작업을 make에 떠 넘기겠습니다. 바로 파일간의 의존성을 찾아서 그 내용을 직접 구성해 달라고 요청하는 것이죠.

  이렇게 파일의 의존성을 검색해서 그 내용을 작성해 주는 것이 gccmakedep 입니다. 아래와 같이 수정해서 make dep를 실행합니다.

TARGET = sample OBJS = main.o tcp.o rs232.o**SRCS = $(OBJS:.o=.c)** CC = -I/home/jwjw/prjs/include -g -c

$(TARGET): $(OBJS)     gcc -lm -o $@ $^

.c.o:     gcc $(CC) $<

**dep :     gccmakedep $(SRCS)**

  이렇게 추가 작성해서 make dep 를 실행하시면 make는 컴파일과 링크 작업 대신에 라벨 dep: 밑의 명령을 실행합니다. 새로 만들어진 SRCS는 OBJS에 열거된 파일 모록에 대해서 확장자를 .o를 .c로 바뀐 목록을 가지게 됩니다. gccmakedep는 소스 파일을 가지고 의존성을 검색할 수 있기 때문이죠.

]$ make dep ]$ vi Makefile

TARGET = sample OBJS = main.o tcp.o rs232.o SRCS = $(OBJS:.o=.c) CC = -I/home/jwjw/prjs/include -g -c     $(TARGET): $(OBJS) gcc -lm -o $@ $^ .c.o:     gcc $(CC) $< dep : gccmakedep $(SRCS)

# DO NOT DELETE main.o: main.c /usr/include/stdio.h ......... tcp.o: tcp.c /usr/include/stdio.h ......... rs232.o: rs232.c /usr/include/stdio.h .........

  하단에# DO NOT DELETE 행과 함께 밑으로 각 .o 에 대한 관련 파일 목록이 자동으로 생성되는 것을 보실 수 있습니다. 이제 make를 실행하면 위 정보에 맞추어 컴파일하게 됩니다.

결언

  어떻게 이해가 되십니까? 마지막 부분은 매크로로 되어 있어서 암호같이 보이는데, 지금 보시면 암호 풀이가 되시는지 모르겠습니다. 소개해 드린 것 보다 더 편리한 부분도 있습니다만 내용이 너무 많아 질 것 같아서 여기서 줄입니다.  긴 내용을 읽어 주셔서 감사합니다.