노무현 대통령 배너


2006. 8. 10. 10:39

[응용] Makefile 작성법

출처: http://kelp.or.kr/korweblog/stories.php?story=04/09/03/6714246

[응용] Makefile 작성법
글쓴이 : 유영창 (2004년 09월 03일 오전 05:25) 읽은수: 5,485 [ 임베디드강좌/유영창 인쇄용 페이지 ]
ADK001 Makefile 작성법
===================================

1. 개요

이 문서는 ESP-NS에서 동작하는 응용 프로그램을 작성하기 위해서
Makefile을 만드는 방법을 정리한 문서입니다.

작성자 : 유영창
frog@falinux.com
작성일 : 2004년 9월 3일
수정일 :

관련된 ADK( Application Developer Kit ) 디렉토리

adk/sample/lib_Makefile
adk/sample/app_Makefile

2. Makefile 작성법

리눅스에서 동작하는 프로그램을 작성하기 위해서는 반드시 크로스 컴파일
과정을 거쳐야 합니다.

아주 간단한 hello.c 라는 프로그램이 다음과 같은 소스로 작성되었다고 한다면

#include

int main( int argc, char **argv )
{
printf( "hellon" );
}

이것을 컴파일 하기 위해서 다음과 같이 처리하면됩니다.

# armv5l-linux-gcc -o hello hello.c

프로그램 소스에 에러가 없어서 정상적으로 이 명령이 수행되었다면
수행 결과로서 hello 라는 프로그램 실행파일이 생성됩니다.

물론 이 실행파일은 PC 시스템에서는 동작하지 않습니다. arm 용 명령
코드를 가지는 실행파일이기 때문입니다. 그래서 이것을 실행해 보려면
ESP-NS 보드에 옮긴후에 수행해야 합니다.

이렇게 프로그램 소스가 하나일 경우에는 간단히 위와 같은 명령으로
처리해도 되지만 저 같은 게으른 사람에게는 위 명령을 컴파일을 할때
마다 매번 타이핑 하려면 죽음입니다. 물론 명령행에서 위 화살표키를
이용하는 방법도 있지만 그것도 여러가지 명령을 수행했다면 컴파일 명령만
따로 찾는것도 귀찮습니다.

더구나 여러가지 소스로 이루어진 프로그램을 컴파일한다면 그냥 때려치고
싶은 마음이 생깁니다.

이렇게 게으른 사람들에게는 Makefile을 만들어서 make 명령으로 하는 것이
아무래도 스트레스를 덜 받아서 수명연장에 도움이 됩니다.

Makefile을 만들때 딱 한번만 스트레스를 받으면 되기 때문입니다.

그.러.나.

Makefile을 어떻게 만들어야 하는가를 알아 보려고 인터넷을 뒤지거나
책을 사보면 그 방대한 양에 포기하고 싶은 마음이 굴뚝 같아 집니다.
도리어 그냥 타이핑 치는것이 마음이 편할수 있읍니다.

그.래.서

저는 아예 고정적으로 Makefile을 하나 만들어서 매번 수정해서 사용합니다.
그래서 여러분에게도 제가 사용하는 방법을 권장합니다. 여기서 소개하는
Makefile이 마음에 안 드신다면 직접 만들어서 사용해도 무방합니다. ㅜㅜ

제가 주로 사용하는 Makefile은 두가지 입니다.
하나는 자주 사용하는 루틴을 모아서 라이브러리로 만들기 위한 것과
응용 프로그램을 작성하기 위해서 사용하는 Makefile입니다.

2.1 라이브러리를 만드는 Makefile

제가 라이브러리를 만들기 위해서 사용하는 Mafefile은 다음과 같습니다.

==[Makefile]=====================================================================
# Compile Option

TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)

# --- 기본적으로 수정되는 내용들 ! -----------------------------------

TARGET = libsample.a
OBJS = func1.o

INCLUDEDIR =

#---------------------------------------------------------------------

# --- 크로스 컴파일과 관련된 내용은 여기를 수정할것 ! ----------------
CROSS_PREFIX = /usr/armv5l-linux
CC = armv5l-linux-gcc
CXX = armv5l-linux-gcc
AR = armv5l-linux-ar rc
AR2 = armv5l-linux-ranlib
RANLIB = armv5l-linux-ranlib
LD = armv5l-linux-ld
NM = armv5l-linux-nm
STRIP = armv5l-linux-strip
OBJCOPY = armv5l-linux-objcopy
#---------------------------------------------------------------------

SRCS = $(OBJS:.o=.c)

CFLAGS += $(INCLUDEDIRS)
CFLAGS += -Wall -O2 -g
CPPFLAGS +=
CXXFLAGS +=

INCLUDEDIRS += -I./ -I$(TOPDIR)/include -I$(CROSS_PREFIX)/include $(INCLUDEDIR)

#
# Compilation target for C files
#
%.o:%.c
@echo "Compiling $< ..."
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<

#
# Compilation target for C++ files
#
%.o:%.cc
@echo "C++ compiling $< ..."
$(CXX) -c $(CFLAGS) $(CXXFLAGS) -o $@ $<

all : $(TARGET)

$(TARGET) : $(OBJS)
$(RM) $(TARGET)
$(AR) $(TARGET) $(OBJS)
$(AR2) $(TARGET)

dep :
$(CC) -M $(INCLUDEDIRS) $(SRCS) > .depend

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

ifeq (.depend,$(wildcard .depend))
include .depend
endif
==[Makefile]=====================================================================

이 Makfile을 사용할때 주로 수정하는 부분은 다음과 같습니다.

TARGET = libsample.a
OBJS = func1.o

INCLUDEDIR =

TARGET은 최종적으로 만들려고 하는 라이브러리 파일명입니다

라이브러리 파일명은 일정한 규칙이 있읍니다. 처음에 시작하는 것은 lib로 시작해야 하고
끝날때는 .a 로 끝나야 합니다.

예를 sample 이라는 이름을 가진 라이브러리 파일을 만들려고 했다면 libsample.a 라는 이름을
지정해야합니다. 컴파일 결과로 만들어지는 파일명은 당연히 libsample.a 입니다 .

이렇게 지정하는 것은 나중에 응용 프로그램을 만들때 지정하는 명칭 때문입니다.

OBJS은 TARGET에 지정한 라이브러리에 포함되는 오브젝트 명을 나열합니다. 여러 파일로
구성될때는 빈칸으로 오브젝트 파일을 구분합니다.

위 예에서는 하나의 파일에 대한 라이브러리를 만들었지만 func1.c 와 func2.c 로
구성되는 라이브러리를 만든다면 다음과 같이 지정해야 합니다.

OBJS = func1.o func2.o

너무 많은 소스 파일로 구성된다면 아무래도 여러줄로 나누어서 쓰는 것이 좋지요
이럴때는 다음 처럼 하면됩니다.

OBJS = func1.o
func2.o

이렇게 할때 중요한것은 '' 뒤에 바로 리턴으로 나누어져야 한다는 겁니다
만약에 뒤에 빈 공백 문자나 다른 문자가 있으면 문제가 됩니다.

INCLUDEDIR은 소스가 참조하는 헤더파일 디렉토리를 추가 할때 사용합니다.
예를 들어 /test/include 라는 디렉토리를 추가 하고 싶으면

INCLUDEDIR = -I/test/include

라고 하면 됩니다. ( -I 의 영문자는 아이[I] 입니다. 엘[L]소문자와 무척 헷갈리므로 조심하십시오 )

기본적으로 이 Makefile은 다음과 같은 디렉토리의 헤더파일들이 참조됩니다.

1. Makefile 이 포함된 디렉토리에 있는 헤더파일들
2. Makefile 이 포함된 상위 디렉토리 밑에 있는 include 디렉토리
3. 크로스 컴파일러에서 제공하는 include 디렉토리
ESP-NS는 /usr/armv5l-linux/include 가 됩니다.

이 Makefile을 이용하여 컴파일한다면 다음과 같은 과정을 거치면 됩니다.

# make clean
# make dep
# make

make clean은 이전에 생성된 오브젝트 파일을 지우기 위해서 사용합니다.
make dep는 컴파일 대상이 되는 헤더파일들의 의존성 체크를 위한 파일 목록을 만들기 위해서
사용합니다. 이 명령의 수행결과로 .depend가 생깁니다.

make dep는 소스상에 새로운 헤더파일을 추가 했을때 수행해 주면됩니다. 물론 자신이 만들어서
수정될 수 있는 헤더파일을 추가했을 경우만 해주면됩니다.

보통 프로그램을 작성하다보면 헤더파일을 수정했는데 컴파일 타켓 대상이 되지 않아서 자신이
의도한 결과와 다른 결과를 만들수 있읍니다. 그래서 헤더파일이 변경되었을때 해당 헤더파일을
참조하는 모든 소스가 수정되도록 하기 위해서 이 명령을 사용합니다.

make clean은 보통 잘 사용하지 않아도 되지만 전체 소스를 재 컴파일 하고 싶을때 사용하면 됩니다.
한번 컴파일 되어서 수정되지 않는 소스의 오브젝트 파일은 컴파일 대상에서 제외 되기 때문에
가끔 의도하지 않는 일이 생길때가 있읍니다. 그럴때 확실하게 전체를 컴파일 하고 싶을때 사용하면
됩니다.

보통은 make 명령 하나로 끝낼수 있읍니다.

저는 생성된 라이브러리 파일과 라이브러리 파일을 사용하기 위한 헤더파일을
자동으로 목적지로 복사하는 기능도 Makefile에 포함시켜서 사용하는데 여기서는 제거했읍니다.
아무래도 자동보다는 수동이 확실합니다. ^^

위에서 제시한 Makefile 은 func1.c 소스를 컴파일 해서 libsample.a 라는 파일을 만들게 됩니다.

2.2 응용 프로그램을 만드는 Makefile

제가 응용 프로그램을 만들기 위해서 사용하는 Mafefile은 다음과 같습니다.

==[Makefile]=====================================================================

# Compile Option

TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)

# --- 기본적으로 수정되는 내용들 ! -----------------------------------

TARGET = test
OBJS = main.o
LIBS = -lsample

INCLUDEDIR =
LIBDIR =

#---------------------------------------------------------------------

# --- 크로스 컴파일과 관련된 내용은 여기를 수정할것 ! ----------------
CROSS_PREFIX = /usr/armv5l-linux
CC = armv5l-linux-gcc
CXX = armv5l-linux-gcc
AR = armv5l-linux-ar rc
AR2 = armv5l-linux-ranlib
RANLIB = armv5l-linux-ranlib
LD = armv5l-linux-ld
NM = armv5l-linux-nm
STRIP = armv5l-linux-strip
OBJCOPY = armv5l-linux-objcopy
#---------------------------------------------------------------------

SRCS = $(OBJS:.o=.c)

CFLAGS += $(INCLUDEDIRS)
CFLAGS += -Wall -O2 -g
CPPFLAGS +=
CXXFLAGS +=

INCLUDEDIRS += -I./ -I$(TOPDIR)/include -I$(CROSS_PREFIX)/include $(INCLUDEDIR)
LDFLAGS += -L./ -L$(TOPDIR)/lib -L$(CROSS_PREFIX)/lib $(LIBDIR)

#
# Compilation target for C files
#
%.o:%.c
@echo "Compiling $< ..."
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<

#
# Compilation target for C++ files
#
%.o:%.cc
@echo "C++ compiling $< ..."
$(CXX) -c $(CFLAGS) $(CXXFLAGS) -o $@ $<

all : $(TARGET)

$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LIBS)
cp $(TARGET) /nfsesp/fgl/

dep :
$(CC) -M $(INCLUDEDIRS) $(SRCS) > .depend

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

ifeq (.depend,$(wildcard .depend))
include .depend
endif
==[Makefile]=====================================================================

이 Makefile 역시 라이브러리를 만드는 것과 무척 유사합니다.

이 Makfile을 사용할때 주로 수정하는 부분은 다음과 같습니다.

TARGET = test
OBJS = main.o
LIBS = -lsample

INCLUDEDIR =
LIBDIR =

TARGET은 최종적으로 만들려고 하는 실행파일명입니다. 여기서는 test 라는 실행파일명을 만듭니다.
윈도우 프로그램을 하시던 분들의 입장에서 보면 실행파일명에 확장자가 없는것이 이상하겠지만
리눅스에서는 확장자가 없는 파일이면 거의가 실행파일명이거나 실행가능한 스크립트일 가능성이
높습니다. 도리어 데이터 파일들에 확장자를 붙이는 것이 관례입니다.

OBJS은 TARGET에 지정한 응용프로그램을 구성하는 소스가 컴파일되어야 하는 오브젝트 명을 나열합니다.
이것 역시 라이브러리의 Makefile과 같은 방법으로 여러 파일로 지정합니다.

LIBS은 포함될 라이브러리를 파일을 지정할때 사용합니다.
위 예에서는 libsample.a 라는 라이브러리를 지정합것입니다.
라이브러리 파일은 지정한 것에 컴파일러가 앞에 'lib' 와 뒤에 '.a'를 자동으로 지정합니다.
이점을 주의해야 합니다.
이 예는 앞에서 만든 라이브러리 파일을 이용하기 때문에 -lsample만 지정하면 됩니다.
( -l 의 영문자는 엘[L]의 소문자로 옵션입니다 )

INCLUDEDIR은 소스가 참조하는 헤더파일 디렉토리를 추가 할때 사용합니다.
예를 들어 /test/include 라는 디렉토리를 추가 하고 싶으면

INCLUDEDIR = -I/test/include

라고 하면 됩니다. ( -I 의 영문자는 아이[I] 입니다. 엘[L]소문자와 무척 헷갈리므로 조심하십시오 )

LIBDIR은 라이브러리가 포함된 디렉토리를 추가 할때 사용합니다.

예를 들어 /test/lib 라는 디렉토리를 추가 하고 싶으면

LIBDIR = -L/test/lib

라고 하면 됩니다.

기본적으로 이 Makefile은 다음과 같은 라이브러리 디렉토리를 참조합니다.

1. Makefile 이 포함된 디렉토리에 있는 라이브러리
2. Makefile 이 포함된 상위 디렉토리 밑에 있는 lib 디렉토리
3. 크로스 컴파일러에서 제공하는 lib 디렉토리
ESP-NS는 /usr/armv5l-linux/lib가 됩니다.

이 Makefile을 이용하여 컴파일한다면 다음과 같은 과정을 거치면 됩니다.

# make clean
# make dep
# make

이것 역시 라이브러리 Makefile과 같습니다. 글이 많은 것 처럼 보이기 위해서
다시 설명합니다. ^^

make clean은 이전에 생성된 오브젝트 파일을 지우기 위해서 사용합니다.
make dep는 컴파일 대상이 되는 헤더파일들의 의존성 체크를 위한 파일 목록을 만들기 위해서
사용합니다. 이 명령의 수행결과로 .depend가 생깁니다.

make dep는 소스상에 새로운 헤더파일을 추가 했을때 수행해 주면됩니다. 물론 자신이 만들어서
수정될 수 있는 헤더파일을 추가했을 경우만 해주면됩니다.

보통 프로그램을 작성하다보면 헤더파일을 수정했는데 컴파일 타켓 대상이 되지 않아서 자신이
의도한 결과와 다른 결과를 만들수 있읍니다. 그래서 헤더파일이 변경되었을때 해당 헤더파일을
참조하는 모든 소스가 수정되도록 하기 위해서 이 명령을 사용합니다.

make clean은 보통 잘 사용하지 않아도 되지만 전체 소스를 재 컴파일 하고 싶을때 사용하면 됩니다.
한번 컴파일 되어서 수정되지 않는 소스의 오브젝트 파일은 컴파일 대상에서 제외 되기 때문에
가끔 의도하지 않는 일이 생길때가 있읍니다. 그럴때 확실하게 전체를 컴파일 하고 싶을때 사용하면
됩니다.

보통은 make 명령 하나로 끝낼수 있읍니다.

저는 생성된 라이브러리 파일과 라이브러리 파일을 사용하기 위한 헤더파일을
자동으로 목적지로 복사하는 기능도 Makefile에 포함시켜서 사용하는데 여기서는 제거했읍니다.
아무래도 자동보다는 수동이 확실합니다. ^^

위에서 제시한 Makefile 은 main.c 소스를 컴파일 하고 libsample.a 라는 라이브러리를
포함혀여 test 라는 실행파일을 만들게 됩니다.


$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LIBS)
cp $(TARGET) /nfsesp/fgl/

부분에서

cp $(TARGET) /nfsesp/fgl/

은 nfs 환경을 구축해서 ESP 보드의 응용 프로그램을 디버깅한다면
자동으로 컴파일된 실행파일을 /nfsesp 라는 디렉토리에 옮기기 위해서
사용하는 것입니다

필요없다면 맨 앞에 '#'을 이용해서 주석처리하면 됩니다.
Makefile은 주석으로 사용되는 라인은 가장 처음 '#'이 포함되어야 합니다.

2.3 기타

잘 수정하지 않는 것중에

CFLAGS += -Wall -O2 -g
CPPFLAGS +=
CXXFLAGS +=

이 두가지가 있읍니다.
하지만 가끔 필요에 의해서 수정하는데 CFLAGS와 CPPFLAGS는 c나 c++ 소스에 대한 컴파일 조건에 대한 수정이 필요
할때 고치면 되고 CXXFLAGS 는 c++ 에 대한 컴파일 조건에 대한 수정이 필요할때 상용합니다.

잘 고치지 않는 부분이므로 여기서는 더이상의 설명을 하지 않으려 합니다. 자세한 것을 알고 싶은 분은
Makefile에 대하여 공부하시고 위 예를 분석하시면 답이 나올 겁니다. (우하하하)

참고로 -g 는 디버그 정보를 포함하라고 하는 것입니다
보통 이 옵션은 포함시키는 것이 좋습니다. 실행파일이 커지는 단점이 있지만 무시할만 하고
나중에 디버깅 단계에서 역 어셈블러를 한다든가 문제가 발생한 부분을 추적할때 좋으므로
가급적 그냥 두시기 바랍니다.

ESP 보드에 용량문제가 된다면 그때 제거하시면 됩니다.

마지막으로 주의 사항 한가지는 Makefile은 명령앞에 tab문자가 포함되어야 합니다.
혹시 이글의 예제를 복사하시다가 tab 문자가 제거 되는 에디터를 이용해서
편집하시면 낭패를 당하실수 있음을 엄중히 경고합니다.

첨부 파일: sample.zip sample.zip (7 KiB(6,834 Bytes))