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

make - configure.in 작성

configure의 가장 큰 목적은 플랫폼 환경을 분석해서 프로그램이 플랫폼 환경에 상관없이 일정하게 동작하도록 해주는 것입니다. 사용자가 프로그램을 빌드하고자하는 환경은 아주 다양합니다. 운영체제가 32비트이거나 64비트일 수 있고, 헤더 파일의 위치가 다르거나, 운영체제 자체가 다를 수 있습니다. 또 프로그램을 빌드하기 위해서는 libabc.so 라는 라이브러리가 있어야하고, abc.h라는 헤더가 있어야 하는데 이런 다양한 환경을 찾아내서 지금 빌드할 플랫폼이 어떤 환경인지를 찾아내는 것이 configure의 임무입니다. 


configure는 실행 프로그램은 아니고 쉘 스크립트입니다. 그리고 configure라는 쉘 스크립트를 간단하게 만들 수 있도록 해주는 프로그램이 autoconf입니다. configure.in 이라는 텍스트 파일에 autoconf의 사용 문법에 맞게 스크립트 파일을 작성하고 autoconf를 실행하면 configure 파일을 생성해줍니다.

따라서 우리도 이런 목적에 맞게 플랫폼의 환경을 찾아내도록 configure.in 파일을 작성해야합니다. configure.in을 작성할 때 일반적으로 다음과 같은 일을 하도록 작성합니다.

- 빌드/실행에 필요한 유틸리티의 설치 유무 확인
- 컴파일러/링커 등 빌드툴의 버전과 옵션 확인
- 헤더 파일 존재 확인
- 운영체제 비트 확인
- 확인된 사항들을 Makefile이나 .h 로 저장

참고로 configure.in의 주석은 dnl이나 #로 시작합니다. 예제 파일에서는 dnl #로 주석을 표시했는데 emacs에서 dnl을 잘 인식하지 못해서 #표시를 추가해준 것입니다. dnl만 써도 주석으로 인식합니다.

그리고 autoconf에서 제공하는 매크로는 굉장히 다양합니다. 대부분 AC_로 시작하는 이름을 가지고 있습니다. 너무나 다양하기 때문에 일일이 찾기도 힘듭니다. 저는 autoconf의 메뉴얼을 잠깐 찾아보고 원하는 매크로를 찾기 어려우면 그냥 bash 쉘 스크립트로 작성해서 configure.in에 집어넣습니다. autoconf 버전에 따라 실행이 안되는 매크로도 있습니다. 너무 모든 매크로를 사용하려고 할 필요없이 쉘 스크립트를 이용하는 편이 더 간단할 때가 있습니다.

예제 파일을 보면서 설명을 하겠습니다. 예제 파일을 보면 각 매크로의 사용 방법을 간략하게 아실 수 있을 것입니다. 자세한 사용법은 메뉴얼을 보시면 됩니다만 사용 예를 알면 메뉴얼 보시기에도 더 편할 것입니다.

 

dnl # Initialize configure
AC_INIT(calib, 1.0)
configure를 초기화합니다. AC_INIT은 configure.in 파일에서 가장 먼저 실행되야 할 매크로입니다. calib은 제가 임의로 지정한 패키지 이름이고, 1.0은 calib 패키지의 버전입니다. 자신이 원하는 이름과 버전을 사용하면 됩니다. 패키지 이름은 configure --help를 실행해서 확인할 수 있습니다. 이 예제를 실행하면 다음과 같은 메시지가 출력됩니다.

`configure' configures calib 1.0 to adapt to many kinds of systems.

 

dnl # Check required autoconf version
AC_PREREQ([2.13])
이 파일은 2.13이상의 autoconf에서만 처리될 수 있습니다.


dnl # platform output file
AC_CONFIG_HEADER([./include/platform.h])
configure.in에서는 일반적인 쉘 스크립트와 같이 변수를 만들어서 사용할 수 있습니다. 그리고 이 변수들의 값을 다른 파일에 써놓을 수 있습니다. 이렇게 변수들을 출력할 헤더파일의 이름을 지정합니다. ./include/platform.h.in 파일에 원하는 변수 이름을 써놓으면 configure가 파일을 읽고 ./include/platform.h를 생성해줍니다.


AC_PROG_AWK
gnu awk 툴이 설치되어있는지 확인합니다.

dnl # nasm install check
AC_PATH_PROG([NASM], [nasm])
nasm이라는 어셈블러가 설치되어 있는지 확인합니다. 환경 변수 PATH에 지정된 디렉토리들을 찾아다니면서 nasm이라는 실행파일을 확인합니다. 파일을 찾으면 NASM 변수에 /usr/bin/nasm과 같이 파일의 절대 경로를 저장합니다.


if test -z "$NASM"; then
    AC_MSG_ERROR([Cannot find Netwide Assembler])
fi
AC_MSG_ERROR은 에러 메시지를 출력하고 configure를 중단하는 매크로입니다. nasm 파일을 찾지 못하면 NASM이라는 변수에 아무것도 저장되지 않으므로 에러를 확인할 수 있습니다. 이와같이 쉘 스크립트와 autoconf의 매크로를 적절히 섞어서 사용할 수 있습니다.


dnl # nasm version check
AC_MSG_CHECKING([whether NASM version >= 2.0 for 64bit assembly])
AC_MSG_CHECKING 매크로는 configure 실행시 무엇인가를 확인중이라는 안내 메시지입니다. 이 메시지는 checking 으로 시작되고 ...로 끝나는 메시지를 출력합니다. 따라서 이 코드는 checking whether NASM version >= 2.0 for 64bit assembly... 메시지를 출력합니다. 이 다음으로는 yes나 no를 출력하는 것이 일반적인 형태입니다.


${NASM} -v | grep version > nasm_version.tmp
nasm -v 명령을 실행해서 버전 정보를 파일에 기록합니다. NASM version 2.07 compiled on Nov  5 2009 와 같은 메시지가 저장됩니다.


result=`cat nasm_version.tmp | awk '{print $3}'`
세번째 컬럼에 있는 2.07이라는 숫자를 뽑아냅니다.


nasm_major=`echo $result | awk -F . '{print $1}'`
rm -f nasm_version.tmp
nasm_major 변수에 버전 숫자를 저장하고 파일을 지웁니다.


if test "$nasm_major" -lt 2; then
    AC_MSG_ERROR([nasm version is below than 2.0])
else
    AC_MSG_RESULT([yes])
fi
2.07은 2보다 크므로 yes라는 메시지를 출력합니다. 만약 버전이 맞지 않다면 AC_MSG_ERROR매크로가 실행됩니다. AC_MSG_ERROR은 메시지를 출력하고 configure 의 실행을 중단시킵니다.


AC_SUBST(NASM)
config.mk 파일에 NASM이라는 변수의 값을 저장합니다. 자세한 사항은 config.mk 작성에서 다시 설명하겠습니다.


dnl # gcc install
AC_PROG_CC
gcc가 설치되어있는지 확인합니다. gcc가 설치되어있으면 CC변수를 gcc로 설정합니다.


if test -z "$CC"; then
    CC=gcc
fi
CC변수가 따로 설정되어있지 않으면 gcc를 저장합니다. AC_PROG_CC가 CC변수를 설정하겠지만, 만약을 대비해서 넣은 코드입니다. 리눅스 하나의 플랫폼에서만 사용할 때는 이렇게 중복된 코드를 넣을 필요가 없습니다. 하지만 다양한 유닉스 플랫폼에서 동작할 때는 이렇게 다시한번 확인하는 코드가 필요합니다. 플랫폼에따라 어떻게 동작할지 확실치가 않기 때문입니다. 10개의 플랫폼에서 동작해도 1개의 플랫폼에서 약간 다르게 동작한다면, 여기에도 대비해야합니다.


dnl
AC_MSG_CHECKING([whether GCC version >= 4.0])
gcc의 버전 확인을 시작합니다.


${CC} -v 2> gcc_version.tmp
버전 메시지를 파일에 저장합니다.


result=`grep "gcc version" gcc_version.tmp | awk '{print $3}'`
여러 줄의 메시지에서 gcc version이 포함된 부분을 따로 저장합니다.


rm -f gcc_version.tmp
이제 파일은 필요없습니다.


gcc_major=`echo $result | awk -F . '{print $1}'`
gcc_minor=`echo $result | awk -F . '{print $2}'`
gcc_minor_minor=`echo $result | awk -F . '{print $3}'`
.로 구분된 숫자중에서 첫번째 숫자가 메이저 번호입니다. 두번째는 마이너번호입니다.


if test "$gcc_major" -ge 4 && test "$gcc_minor" -ge 0
    then
    AC_MSG_RESULT([yes ("$result")])
else
    echo "current gcc is \"${CC}\"-${gcc_major}.${gcc_minor}.${gcc_minor_minor}"
    echo "you can define gcc as \"CC=your-gcc ./configure\""
    AC_MSG_ERROR([current gcc version cannot process 64bit address])
fi
버전이 4.0 이상이 되어야 통과합니다. 버전이 4.0보다 낮으면 현재 gcc의 버전을 알려주면서 configure를 중단합니다. 꼭 4.0 이상일 필요는 없지만 64비트 컴파일을 위해서 4.0 이상을 쓰면 좋습니다. 4.0이하에서는 메모리 어드레싱과 관련해서 가끔 에러가 발생한다고 알려져있습니다.

 

AC_SUBST(CC)
config.mk에 CC의 값을 저장합니다.


dnl # ld support x86_64
AC_MSG_CHECKING([whether ld supports elf_x86_64])
링커 ld가 64비트를 지원하는지 확인하겠습니다.


if test -z "${LD}" ; then
    LD=ld
fi
LD변수에 링커의 이름 ld를 저장합니다.


result=`${LD} -V | grep x86_64`
대문자 V 옵션은 ld가 지원하는 플랫폼의 리스트를 출력합니다. 이 중에 x86_64가 있는지 확인합니다.


if test -z "$result" ; then
    echo "current ld is \"${LD}\""
    echo "you can define ld as \"LD=your-ld ./configure\""
    AC_MSG_ERROR([current ld do not support elf_x86_64, please install 64bit ld])
else
    AC_MSG_RESULT([yes])
fi

AC_SUBST(LD)
x86_64를 지원하면 config.mk에 LD 값을 저장합니다.


AC_SUBST(OBJCOPY)
dnl # objcopy support x86_64
AC_MSG_CHECKING([whether objcopy supports elf64-x86-64])

if test -z "$OBJCOPY" ; then
    OBJCOPY=objcopy
fi

result=`${OBJCOPY} -h | grep elf64-x86-64`

if test -z "$result" ; then
    echo "current objcopy is \"${OBJCOPY}\""
    echo "you can define objcopy as \"OBJCOPY=your-objcopy ./configure\""
    AC_MSG_ERROR([current objcopy do not support elf-x86-64, please install
                  64bit objcopy])
else
    AC_MSG_RESULT([yes])
fi

AC_SUBST(OBJCOPY)
gcc,ld,objcopy 모두 64비트 x86_64 플랫폼을 지원하는지 확인합니다.


dnl # extra tools
AC_PATH_PROG([DD], [dd])
AC_PATH_PROG([NM], [nm])
dd와 nm 툴을 확인합니다. 꼭 빌드과정에 사용되는 것은 아니지만 개발하다보면 자주 쓰는 툴이므로 항상 체크해줍니다.


AC_SUBST(DD)
AC_SUBST(NM)

dnl # headers
AC_CHECK_HEADERS(execinfo.h mcheck.h malloc.h pthread.h stdio.h stdlib.h string.h signal.h unistd.h stdint.h)
AC_CHECK_HEADERS(sys/types.h sys/stat.h sys/fcntl.h sys/wait.h)
헤더 파일이 설치되어있는지 확인합니다.


dnl # host

AC_CANONICAL_HOST()
현재 빌드하고있는 플랫폼의 타입을 알아냅니다. host, host_os, host_cpu, host_vendor 등의 변수에 플랫폼 값을 저장합니다. 예를 들어 제가 사용하는 노트북에서는 host변수의 값이  x86_64-unknown-linux-gnu입니다. -로 구분해서 첫번째 필드의 값 x86_64가 host_cpu의 값입니다. 두번째 필드 unknown은 host_vendor 값이고, linux_gnu는 host_os 변수의 값입니다.


echo ""
echo "HOST PLATFORM: ${host}"
echo "HOST OS:       ${host_os}"
echo "HOST CPU:      ${host_cpu}"
echo "HOST VENDOR:   ${host_vendor}"
echo ""

CALIB_CFG_CPU="$host_cpu"
CALIB_CFG_CPU_BIT=0
CALIB_CFG_OS="$host_os"
이름이 CALIB_CFG_로 시작되는 변수들은 다른 Makefile나 C 소스들이 플랫폼의 상태를 알 수 있도록 config.mk나 platform.h 파일에 출력될 변수들입니다.


case "${host}" in
    i[3456]86-* )
        CALIB_CFG_CPU_BIT=32
        ;;
    amd64-*-* )
        CALIB_CFG_CPU_BIT=64
        ;;
    x86_64-* )
        CALIB_CFG_CPU_BIT=64
        ;;
esac
host값에 따라 CPU의 비트를 알 수 있습니다.


AC_DEFINE_UNQUOTED(CALIB_CFG_OS, "$CALIB_CFG_OS")
AC_DEFINE_UNQUOTED(CALIB_CFG_CPU_$CALIB_CFG_CPU, 1)
AC_DEFINE_UNQUOTED(CALIB_CFG_CPU, "$CALIB_CFG_CPU")
AC_DEFINE_UNQUOTED(CALIB_CFG_CPU_BIT_$CALIB_CFG_CPU_BIT, 1)
AC_DEFINE_UNQUOTED(CALIB_CFG_CPU_BIT, "$CALIB_CFG_CPU_BIT")
platform.h.in에는 #undef CALIB_CFG_OS 등으로 변수들이 선언되어 있습니다. AC_DEFINE_UNQUOTED 매크로를 사용하면 변수 선언을 #define으로 바꾸고 그 값을 정의합니다. 결국 platform.h에는 #define CALIB_CFG_OS linux-gnu로 저장됩니다.


AC_SUBST(CALIB_CFG_CPU)
AC_SUBST(CALIB_CFG_CPU_BIT)
AC_SUBST(CALIB_CFG_AVAIL_COMPILE_BIT)
config.mk에 저장될 변수들입니다.

AC_CONFIG_FILES([config.mk])
AC_OUTPUT
config.mk를 작성합니다.


dnl # remove temporary files
rm -f ./config.cache ./config.log ./config.status
configure의 동작에 필요없는 파일은 지웁니다.


이렇게 configure.in 파일을 작성했다면 config.mk.in을 작성합니다. config.mk.in은 다른 Makefile들이 참조해서 쓸 수 있는 변수들을 선언하는 파일입니다.


# This file is set by configure
HOST=@host@
OS=@host_os@
CPU=@CALIB_CFG_CPU@
NASM=@NASM@
CC=@CC@
LD=@LD@
OBJCOPY=@OBJCOPY@
DD=@DD@
NM=@NM@
변수이름=변수값 형태로 변수를 선언합니다. 변수는 Makefile에서 사용할 변수입니다. configure.in에서 만든 변수들은 @변수이름@으로 참조됩니다. configure는 실행이 완료된 후 자신이 가진 변수 값을 config.mk.in에 출력해서 config.mk 파일을 생성합니다. configure에서 조사한 플랫폼 정보나 툴에 대한 정보 등을 빌드 프로세스에서 사용하고자할 경우에 이렇게 .mk파일을 작성하게됩니다. 앞으로 Makefile을 만들 때 이 정보들을 사용해서 어디에 있는 어떤 툴을 사용할지 결정하게 됩니다.

예를 들어 제 컴퓨터에서 생성된 config.mk를 보여드리겠습니다.

# This file is set by configure
HOST=x86_64-unknown-linux-gnu
OS=linux-gnu
CPU=x86_64
NASM=/usr/bin/nasm
GCC64=gcc
LD64=ld
OBJCOPY64=objcopy
DD=/bin/dd
NM=/usr/bin/nm

이제 platform.h.in 파일을 만들겠습니다. .mk파일이 Makefile에 포함되고 빌드 과정에서 사용된다면 .h 파일은 소스가 참고할 정보들을 저장한다는 차이가 있습니다. configure에서 알아낸 정보를 소스 파일에 전달하게 됩니다.

platform.h.in 파일을 보겠습니다.

#undef CALIB_CFG_OS
#undef CALIB_CFG_CPU_x86_64
#undef CALIB_CFG_CPU_i386
#undef CALIB_CFG_CPU_AMD64
#undef CALIB_CFG_CPU
#undef CALIB_CFG_CPU_BIT
#undef CALIB_CFG_CPU_BIT_32
#undef CALIB_CFG_CPU_BIT_64
configure에서 정의한 변수들이 #undef으로 선언되어 있습니다. configure.in 파일에서 보듯이 AC_DEFINE_UNQUOTED 매크로를 이용해서 #undef를 #define으로 바꾸고 정의할 값을 지정합니다.

예를 들면 configure.in 파일에서 AC_DEFINE_UNQUOTED(CALIB_CFG_OS, "$CALIB_CFG_OS")를 호출했었습니다. platform.h.in에서 CALIB_CFG_OS 선언을 #define으로 바꾸고 상수 값을 configure에서 만든 변수중에서 CALIB_CFG_OS 변수의 값으로 설정하라는 것입니다. 변수 이름이 같아서 혼란스럽지만 동일한 변수이름을 사용해서 같은 정보라는 것을 알기 쉽도록 하기 위해 일부러 같은 이름을 사용했습니다.

제 컴퓨터에서 생성된 platform.h 파일을 보여드리겠습니다.

/* ./include/platform.h.  Generated from platform.h.in by configure.  */
#define CALIB_CFG_OS "linux-gnu"
#define CALIB_CFG_CPU_x86_64 1
/* #undef CALIB_CFG_CPU_i386 */
/* #undef CALIB_CFG_CPU_AMD64 */
#define CALIB_CFG_CPU "x86_64"
#define CALIB_CFG_CPU_BIT "64"
/* #undef CALIB_CFG_CPU_BIT_32 */
#define CALIB_CFG_CPU_BIT_64 1


지금까지 include, lib, src, test 디렉토리를 만들고 configure.in, config.mk, include/platform.h.in 파일을 만들었습니다. 이제 configure.in을 가지고 configure 파일을 만들어보겠습니다. configure.in 파일이 있는 디렉토리에서 autoconf 명령을 실행합니다. 아무런 메시지가 없이 종료되었다면 정상적으로 실행된 것입니다. configure.in 파일에 문제가 있다면 문제가 발생한 위치와 설명을 출력합니다. ls를 실행해보면 autom4te.cache 디렉토리와 config.log, configure 파일이 생성된 것을 확인할 수 있습니다. 이제 configure를 실행합니다.

경우에따라 configure를 실행했는데 다음과 같이 install-sh 파일이나 config.sub 파일이 없다는 에러가 발생할 수 있습니다. 

configure: error: cannot find install-sh, install.sh, or shtool in "." "./.." "./../.."
configure: error: cannot run /bin/bash ./config.sub

이럴때는 automake --add-missing 명령을 실행해보시기 바랍니다. automake 파일이 자동으로 config.guess, config.sub, install-sh 등 configure의 실행에 필요한 파일들을 설치해줍니다.

configure.in: no proper invocation of AM_INIT_AUTOMAKE was found.
configure.in: You should verify that configure.in invokes AM_INIT_AUTOMAKE,
configure.in: that aclocal.m4 is present in the top-level directory,
configure.in: and that aclocal.m4 was recently regenerated (using aclocal).
configure.in:121: installing `./config.guess'
configure.in:121: installing `./config.sub'
configure.in:121: installing `./install-sh'
automake: no `Makefile.am' found for any configure output

몇몇 버전의 automake는 install-sh 파일을 설치하지 않는 버그를 가지고 있습니다. 그럴때는 automake의 소스 파일안에있는 install-sh 파일을 직접 복사해서 사용하면 됩니다.

configure가 실행되면 최종적으로 다음과 같은 파일들을 가지게 됩니다. config.mk와 include/platform.h가 제대로 생성되었는지 파일 내용을 확인하시기 바랍니다.


$ ls *
config.guess  config.mk  config.mk.in  config.sub  configure  configure.in  install-sh

autom4te.cache/:
output.0  requests  traces.0

include/:
platform.h  platform.h.in

lib/:

src/:

test/:

댓글

댓글 본문
작성자
비밀번호
  1. 좋은글 감사합니다.^^
  2. gurugio
    전체 예제 소스는 여기에서 다운받으세요.
    https://github.com......2.2
버전 관리
gurugio
현재 버전
선택 버전
graphittie 자세히 보기