(미완성)평범한 개발자의 C 프로그래밍 이야기

make - 좀더 복잡하게

빌드 순서를 생각해보면 configure는 프로젝트를 빌드할 플랫폼에 대한 정보를 얻습니다. 그리고 make가 실행되면서 make에 지정된 옵션에 따라 빌드가 시작됩니다. 따라서 실질적으로 빌드 프로세스를 조정할 수 있는 것은 make의 옵션입니다. 따라서 make의 옵션을 인식해서 옵션에 맞게 빌드를 진행할 수 있도록 Makefile을 작성해보겠습니다.

우리가 가장 먼저 만들 파일은 calib.mk.in입니다. .in이라는 확장자에서 알 수 있듯이 configure을 통해 calib.mk 파일을 생성합니다. calib.mk 파일에는 빌드에 관련된 모든 내용을 저장합니다. 빌드할 소스 파일의 이름, 위치, 빌드 옵션 뿐 아니라 빌드중 생성되는 오브젝트 파일들이 저장될 위치, 최종 생성될 라이브러리 이름까지 저장됩니다. 최대한 빌드와 관련된 모든 정보를 calib.mk에 모아서 각 서브 디렉토리의 Makefile은 calib.mk를 참조해서 빌드 관련 정보를 얻을 수 있습니다. 서브 디렉토리의 Makefile에는 서브 디렉토리에서만 실행되는 명령들이 저장됩니다. 모듈간의 독립성을 높이면서도 각 모듈의 빌드를 통일되게 관리할 수 있습니다.

이전에서 설명했듯이 빌드와 관련된 파일을 한곳에 모으는 것이 중요합니다. 빌드과 배포 등은 따로 담당자를 두어서 제품의 각 모듈 개발자들은 자신들이 담당한 소스에 집중할 수 있도록 해주어야합니다. 그래야 모든 모듈이 같은 옵션으로 빌드되고 통합될 수 있습니다. 또한 빌드와 관련된 사항을 수정할 때도 각 모듈별로 작업하지 않고 한 곳에 모인 빌드 관련 파일들만 수정하게되는 장점이 있습니다.

먼저 다음과 같이 configure.in 파일에 calib.mk를 추가합니다.


AC_CONFIG_FILES([config.mk calib.mk])

다음은 calib.mk.in 파일의 내용입니다.

# making calib
# There values are used by every Makefile
# so that they are defined here.
CALIB_SRCS = sys_info.c hello.c
CALIB_OBJS = $(CALIB_SRCS:.c=.o)

CALIB_NAME = libca.a
빌드하려는 파일들의 파일 이름과 소스 파일을 관리합니다. 이 파일에서는 calib이라는 제품을 빌드하려고 하며, calib이라는 제품은 최종 생성 파일이 libca.a 이고, 관련된 소스는 sys_info.c와 hello.c 입니다. 상황에 따라 여러개의 최종 생성 파일이 있을 수 있고, 각 최종 생성 파일마다 관련된 소스를 정의해줄 수 있습니다.


# BUILD_MODE setup
# eg) make build_mode=debug
AVAILABLE_BUILD_MODE = debug release
DEFAULT_BUILD_MODE = debug
빌드를 디버그 모드와 릴리즈 모드로 나눠서 진행할 수 있도록 합니다. 개발중에는 자주 디버그 모드로 빌드하므로 기본 설정은 디버그 모드로 합니다.

ifneq ($(DEFAULT_BUILD_MODE),)
 ifeq ($(origin build_mode), undefined)
  BUILD_MODE = $(DEFAULT_BUILD_MODE)
 else
  BUILD_MODE = $(filter $(build_mode), $(AVAILABLE_BUILD_MODE))
  ifneq ($(BUILD_MODE), $(build_mode))
   $(error Unknown build mode: $(build_mode))
  endif
 endif
endif
make를 실행할 때 build_mode 옵션을 지정하지 않았다면, origin 함수가 undefined를 반환해줍니다. 그럴때는 기본 값으로 지정됩니다. build_mode 옵션을 지정했다면, AVAILABLE_BUILD_MODE 중에 있는 값인지 확인한 후 BUILD_MODE 변수에 값을 저장합니다.


# COMPILE_BIT setup
# eg) make compile_bit=64
# AVAILABLE_COMPILE_BIT, DEFAULT_COMPILE_BIT are setup at configure.in
AVAILABLE_COMPILE_BIT=@CALIB_CFG_AVAIL_COMPILE_BIT@
DEFAULT_COMPILE_BIT=@CALIB_CFG_CPU_BIT@
컴파일 비트는 플랫폼 환경에 따라 달라질 수 있으므로 configure에서 설정한 값을 가져옵니다.

ifneq ($(DEFAULT_COMPILE_BIT),)
 ifeq ($(origin compile_bit), undefined)
  COMPILE_BIT = $(DEFAULT_COMPILE_BIT)
 else
  COMPILE_BIT = $(filter $(compile_bit), $(AVAILABLE_COMPILE_BIT))
  ifneq ($(COMPILE_BIT), $(compile_bit))
   $(error Unknown compile bit: $(compile_bit))
  endif
 endif
else
 $(error No setting for compile bit: $(DEFAULT_COMPILE_BIT))
endif
BUILD_MODE 변수를 설정하는 것과 동일하게 compile_bit 옵션이 지정되었는지, 지정되었으면 옳바른 값이 지정되었는지를 확인합니다.


SUB_DIRS = src lib test
서브 디렉토리는 lib, src, test로 총 3개입니다.


ROOT_DIR := @ROOT_DIR@
LIB_DIR = $(ROOT_DIR)/lib
SRC_DIR = $(ROOT_DIR)/src
TEST_DIR = $(ROOT_DIR)/test
configure는 소스 트리의 가장 상위에서 실행됩니다. 따라서 configure에서 소스의 최상위 디렉토리를 알려줍니다. 각 서브 디렉토리의 경로를 최상위 디렉토리의 경로를 바탕으로 알 수 있습니다.


#
# compile tools
#
CC=@CC@
LD=@LD@
AR=@AR@
configure가 찾아낸 컴파일러와 링커를 활용합니다.

INCLUDES_OPT += $(ROOT_DIR)/include
헤더가 저장된 디렉토리는 include 입니다.


INCLUDES_OPT += $(ROOT_DIR)/include

DEFINES_OPT += CALIB_CFG_BUILD_MODE=\"$(BUILD_MODE)\"

ifeq ($(BUILD_MODE), debug)
 DEFINES_OPT += CALIB_CFG_BUILD_MODE_DEBUG
else
 ifeq ($(BUILD_MODE), release)
  DEFINES_OPT += CALIB_CFG_BUILD_MODE_RELEASE
 endif
endif
각 소스에서 빌드 모드와 컴파일 비트를 알 수 있도록 매크로 상수를 만듭니다. 설정 값을 변수로 사용할 수 있도록 상수 값 CALIB_CFG_BUILD_MODE="debug", CALIB_CFG_COMPILE_BIT=64 을 만듭니다. 조건부 컴파일로 사용할 수 있도록 CALIB_CFG_BUILD_MODE_DEBUG, CALIB_CFG_BUILD_MODE_64 매크로도 정의합니다.


INCLUDES = $(addprefix -I,$(INCLUDES_OPT))
DEFINES = $(addprefix -D,$(DEFINES_OPT))
헤더 디렉토리와 매크로 상수를 빌드 옵션에 사용할 수 있도록 -I와 -D 옵션을 추가해서 컴파일 옵션을 완성합니다.

CFLAGS += -Wextra -Wall

ifeq ($(BUILD_MODE), debug)
 CFLAGS += -g
else
 CFLAGS += -O2
endif

ifeq ($(COMPILE_BIT), 64)
 CFLAGS += -m64
else
 CFLAGS += -m32
endif
컴파일러 옵션을 설정합니다. 디버그 모드에서는 -g, 릴리즈 모드에서는 -O2 옵션을 사용합니다. 컴파일 비트에 따라서 -m32와 -m64 중에 하나를 선택합니다.


ifeq ($(COMPILE_BIT), 64)
 LD_FLAGS += -m64
else
 LD_FLAGS += -m32
endif
링커에게 전달할 컴파일 비트 옵션입니다.


AR_FLAGS = sruv
정적 라이브러리를 만들기 위해서 ar에 지정할 옵션입니다.

 

다음은 ./Makefile의 내용입니다.

 

# config.mk is generated by configure
include calib.mk
include config.mk


all: $(SUB_DIRS)
    $(foreach dir,$(SUB_DIRS),make -C $(dir);)


clean:
    $(foreach dir,$(SUB_DIRS),make -C $(dir) clean;)
최초로 실행되는 Makefile답게 각 서브 디렉토리의 Makefile을 실행합니다. config.mk와 calib.mk를 포함해야합니다.

다음은 src/Makefile 입니다.

 

include ../calib.mk
include $(ROOT_DIR)/config.mk

all: $(CALIB_OBJS)

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

clean:
    rm $(CALIB_OBJS)
config.mk를 참조할 때 ROOT_DIR 변수를 사용해서 파일의 경로를 찾아갑니다. ROOT_DIR 변수가 calib.mk에 정의되어있으므로, calib.mk를 가장 먼저 포함해야합니다. src 디렉토리의 Makefile은 CALIB_OBJS에 정의된 오브젝트 파일을 생성하는 일을 합니다. 따라서 Makefile파일이 컴파일러를 호출하는 명령 한줄로만 이루어져있습니다. 또한 Makefile에 사용된 모든 옵션과 파일 이름 등등이 모두 calib.mk에 정의된 변수들을 사용하는 것을 알 수 있습니다. src 디렉토리 내부에서만 사용할 파일이나 기타 설정 등에 대한 사항을 정의해서 사용할 수 있지만, 전체 빌드에 필요한 모든 사항은 calib.mk에 정의된 변수들을 사용해야 합니다.

예를 들어 src디렉토리안에 또다른 서브 디렉토리인 m1, m2, m3가 있다고 했을 때, src/Makefile은 또다시 서브 디렉토리를 정의하고, 각 서브 디렉토리에서 생성될 결과물들을 정의할 수 있습니다. 하지만 최상위에서 전체 빌드에서 사용하도록 설정한 옵션을 수정해서는 안되고, 또 src보다 동일한 레벨인 lib나 test에서 사용해야하는 값을 src/Makefile에서 정의해서도 안됩니다. 즉, 상위 디렉토리에서 정의한 설정들만을 사용한다는 큰 원칙을 지켜야합니다. 이렇게 상하위 빌드에 대한 원칙을 가지고 빌드 프로세스를 만드는 것은 SW 개발에서 모듈간 독립성을 지키는 것과 일맥상통합니다.

 

다음은 lib/Makefile입니다.

 

include ../calib.mk
include $(ROOT_DIR)/config.mk

all: $(CALIB_NAME)

$(CALIB_NAME): $(addprefix $(SRC_DIR)/,$(CALIB_OBJS))
    ar sruv $@ $^


clean:
    rm $(CALIB_NAME)
최종 생성 파일은 CALIB_NAME 변수에 이름이 저장된대로 libca.a 파일입니다. 라이브러리를 빌드하기위한 의존성에 src에 저장된 소스 파일들의 이름이 설정됩니다. 따라서 소스 파일이 변경될때마다 라이브러리 파일을 새로 생성합니다. 소스 파일이 변경되지 않았으면 라이브러리를 다시 빌드하지 않습니다.


다음은 test/Makefile 입니다.


# config.mk is generated by configure
include ../calib.mk
include $(ROOT_DIR)/config.mk

all: test_hello test_sys_info

test_hello: test_hello.c ../lib/libca.a
    $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
    ./$@

test_sys_info: test_sys_info.c ../lib/libca.a
    $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
    ./$@

clean:
    rm -rf test_hello test_sys_info
테스트 소스와 라이브러리 파일을 가지고 링크해서 실행파일을 생성합니다.

댓글

댓글 본문
작성자
비밀번호
  1. Jun Seop So
    늦게나마 감사합니다 ㅎㅎ
  2. gurugio
graphittie 자세히 보기