Part 1과 Part 2를 통해 배쉬 프로그래밍의 기초를 충분히 다졌다고 생각한다. 이제는 좀더 수준높은 주제를 다뤄보도록 하자. 배쉬 애플리케이션 개발과 프로그램 디자인은 어떤가? 이 글을 통해, 코딩과 정리 작업에 많은 시간을 투자했던 Gentoo Linux ebuild 시스템 프로젝트를 소개하고 실질적인 배쉬 개발 경험을 나누고자 한다.
나는 차세대 리눅스인 Gentoo Linux의 수석 아키텍트이다. 주요한 임무 중 하나는 모든 바이너리 패키지가 적절히 만들어져 원활히 작동할 수 있도록 하는 것이다. 표준 리눅스 시스템은 하나의 통일된 소스 트리로 구성되어있는 것이 아니라 25+ 핵심 패키지가 함께 작동한다. 다음은 패키지에 포함된 것 들이다:
| 패키지 | 설명 |
| linux | 실제 커널 |
| util-linux | 다양한 리눅스 관련 프로그램 모음 |
| e2fsprogs | ext2 파일시스템 관련 유틸리티 모음 |
| glibc | GNU C 라이브러리 |
각 패키지는 tarball에 있고 개발자들이나 개발 팀들이 관리한다. 배포판을 만들기 위해서는, 각 패키지의 다운로드, 컴파일, 패키징이 개별적으로 수행되어야 한다. 패키지가 픽스되고 업그레이드 될 때마다 컴파일과 패키징 단계는 반복되어야 한다. 패키지 만들기와 업데이트의 반복적인 단계를 줄이기 위해서 ebuild 시스템을 만들었다. 거의 모든것이 배쉬로 작성된 것이다. ebuild 시스템을 언패킹하고 컴파일하는 부분을 구현하는 방법을 단계적으로 설명하겠다.
배쉬는 Gentoo Linux ebuild system의 필수 컴포넌트이다. 여러가지 이유로 ebuild의 기본 언어로 선택되었다. 우선, 복잡하지 않고 익숙한 신택스를 갖추고 있어서 특별히 외부 프로그램을 호출하기에 알맞다. 자동 구현 시스템은 외부 프로그램 호출을 자동화 하는 "글루 코드(glue code)" 이고, 배쉬는 이러한 유형의 애플리케이션에 적합하다. 그리고, 배쉬는 ebuild 시스템이 모듈식의 이해하기 쉬운 코드가 되도록 함수를 지원한다. 마지막으로, ebuild 시스템은 배쉬가 환경 변수를 지원하는 것을 이용하여 패키지 관리자와 개발자가 쉽게 설정할 수 있도록 했다.
ebuild 시스템을 보기전에, 패키지를 컴파일하고 설치에 무엇이 포함되었는지를 살펴보자. 예제에서는, "sed" 패키지를 검토할 것이다. 이것은 리눅스 배포판의 일부분인 표준 GNU 텍스트 스트림 편집 유틸리티이다. 우선 source tarball (sed-3.02.tar.gz) (참고자료)을 다운로드 한다. 이 아카이브를 /usr/src/distfiles에 저장할 것이다. 이 디렉토리는 "$DISTDIR" 환경 변수를 사용할 때 참조할 것이다. "$DISTDIR"는 원래 source tarball이 있는 곳에 있는 디렉토리이다.
다음 단계는 "work"라는 임시 디렉토리를 만드는 것이다. 이것은 압축하지 않은 소스 저장소이다. 이 디렉토리를 "$WORKDIR" 환경 변수를 사용하면서 후에 참조할 것이다. 이를 위해, 쓰기 권한을 가지고 있는 곳의 디렉토리로 변경하고 다음을 타이핑 한다:
sed 압축 풀기
$ mkdir work $ cd work $ tar xzf /usr/src/distfiles/sed-3.02.tar.gz |
sed 압축 풀기
$ cd sed-3.02 $ ./configure --prefix=/usr (autoconf generates appropriate makefiles, this can take a while) $ make (the package is compiled from sources, also takes a bit of time) |
unpack/compile 프로세스를 수행하는 배쉬 스크립트
#!/usr/bin/env bash if [ -d work ] then # remove old work directory if it exists rm -rf work fi mkdir work cd work tar xzf /usr/src/distfiles/sed-3.02.tar.gz cd sed-3.02 ./configure --prefix=/usr make |
일반적인 스크립트
#!/usr/bin/env bash
# P is the package name
P=sed-3.02
# A is the archive name
A=${P}.tar.gz
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
export SRCDIR=${WORKDIR}/${P}
if [ -z "$DISTDIR" ]
then
# set DISTDIR to /usr/src/distfiles if not already set
DISTDIR=/usr/src/distfiles
fi
export DISTDIR
if [ -d ${WORKDIR} ]
then
# remove old work directory if it exists
rm -rf ${WORKDIR}
fi
mkdir ${WORKDIR}
cd ${WORKDIR}
tar xzf ${DISTDIR}/${A}
cd ${SRCDIR}
./configure --prefix=/usr
make |
sed-3.02.ebuild
#the sed ebuild file -- very simple!
P=sed-3.02
A=${P}.tar.gz
|
ebuild 스크립트
#!/usr/bin/env bash
if [ $# -ne 1 ]
then
echo "one argument expected."
exit 1
fi
if [ -e "$1" ]
then
source $1
else
echo "ebuild file $1 not found."
exit 1
fi
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
export SRCDIR=${WORKDIR}/${P}
if [ -z "$DISTDIR" ]
then
# set DISTDIR to /usr/src/distfiles if not already set
DISTDIR=/usr/src/distfiles
fi
export DISTDIR
if [ -d ${WORKDIR} ]
then
# remove old work directory if it exists
rm -rf ${WORKDIR}
fi
mkdir ${WORKDIR}
cd ${WORKDIR}
tar xzf ${DISTDIR}/${A}
cd ${SRCDIR}
./configure --prefix=/usr
make |
$ ./ebuild sed-3.02.ebuild
"ebuild"가 실행할 때 이것은 "$1" 변수를 "source" 한다. 이것은 무엇을 의미하는가? part 2에서, "$1"는 첫 번째 명령행 인자라는 것을 기억할 것이다. 이 경우, "sed-3.02.ebuild" 이다. 배쉬에서, "source" 명령어는 파일에서부터 배쉬 문장을 읽고 그들이 "source" 명령어가 있는 파일에 나타난것처럼 그들을 실행한다. 그래서, "source ${1}"는 "ebuild" 스크립트가 "sed-3.02.ebuild"에 있는 명령어를 실행하도록 한다. 이러한 디자인 변경은 쉽다. sed 대신 다른 프로그램을 컴파일하길 원한다면 새로운 .ebuild 파일을 만들어서 이것을 인자로 해서 "ebuild" 스크립트로 전달할 수 있다. 그러한 방식이, ebuild 파일을 간단하게 하며 ebuild system의 핵심부분은 한 장소, 즉 "ebuild" 스크립트에 저장된다. 이러한 방식으로 "ebuild" 스크립트를 편집함으로서 간단히 ebuild 시스템을 업그레이드 하면서 구현 세부사항은 ebuild 외부에 보관한다. 다음은 gzip용 ebuild 파일이다:
gzip-1.2.4a.ebuild
#another really simple ebuild script!
P=gzip-1.2.4a
A=${P}.tar.gz
|
ebuild, revision 2
#!/usr/bin/env bash
if [ $# -ne 2 ]
then
echo "Please specify two args - .ebuild file and unpack, compile or all"
exit 1
fi
if [ -z "$DISTDIR" ]
then
# set DISTDIR to /usr/src/distfiles if not already set
DISTDIR=/usr/src/distfiles
fi
export DISTDIR
ebuild_unpack() {
#make sure we're in the right directory
cd ${ORIGDIR}
if [ -d ${WORKDIR} ]
then
rm -rf ${WORKDIR}
fi
mkdir ${WORKDIR}
cd ${WORKDIR}
if [ ! -e ${DISTDIR}/${A} ]
then
echo "${DISTDIR}/${A} does not exist. Please download first."
exit 1
fi
tar xzf ${DISTDIR}/${A}
echo "Unpacked ${DISTDIR}/${A}."
#source is now correctly unpacked |
많은 변경이 이루어졌다. 우선, 우리는 컴파일과 언패킹 단계를 각자의 함수에 두고, ebuild_compile()와 ebuild_unpack()를 각각 호출했다. 코드는 점점 복잡해지고 새로운 함수는 어떤 것이든 조직화시키는 모듈화를 제공하기 때문에 이것은 좋은 현상이다. 각 함수의 첫 번째 라인에서 원하는 디렉토리로 "cd" 했다. "cd" 명령어는 분명히 올바른 장소에 위치시키고 실수를 방지한다.
또한, ebuild_compile() 함수의 시작에 "check"를 추가했다. 이것은 "$SRCDIR" 가 존재하는 지를 확인하고 그렇지 않다면 사용자에게 아카이브를 언패킹하고 종료하라는 에러 메시지를 프린팅한다. 원한다면 이러한 작동을 변경하여, "$SRCDIR"가 존재하지 않을 경우 ebuild 스크립트가 소스 아카이브를 자동으로 언패킹하도록 한다. ebuild_compile()을 다음의 코드로 대체함으로서 이를 수행할 수 있다:
ebuild_compile()
ebuild_compile() {
#make sure we're in the right directory
if [ ! -d "${SRCDIR}" ]
then
ebuild_unpack
fi
cd ${SRCDIR}
./configure --prefix=/usr
make
} |
$ ebuild sed-3.02.ebuild
실제로 에러 메시지를 받을 것이다. ebuild는 무엇을 해야할지에 대해 명령을 받아야 한다:
$ ebuild sed-3.02.ebuild unpack
또는
$ ebuild sed-3.02.ebuild compile
또는
$ ebuild sed-3.02.ebuild all
코드가 향상되고 기능적으로 되었다면 각자 선호하는 프로그램을 언패킹하고 컴파일 할 더 많은 ebuild 스크립트를 만들고 싶을 것이다. 여러분은 "./configure"를 사용하지 않는 소스를 생각할 것이고 아마도 비표준 컴파일 프로세스를 가진 다른 것을 생각하게 될 것이다. 프로그램을 자동화하기 위해서는 ebuild 시스템을 변경해야 한다. 하지만 그 전에 이를 어떻게 이뤄 나갈지를 생각해야 한다.
ebuild 시스템이 autoconf나 정상적인 Makefiles를 사용하지 않는 소스를 갖추도록 해야 한다. 이러한 문제를 풀기위해 ebuild 스크립트는 디폴트로 다음과 같은 것을 수행해야한다:
- "${SRCDIR}"에 설정 스크립트가 있다면 다음과 같이 실행한다:
./configure --prefix=/usr
그렇지 않다면, 이 단계를 생략한다.
- 다음의 명령어를 실행한다:
make
ebuild는 실제로 존재할 때 configure를 실행하기 때문에, autoconf를 사용하지 않고 표준 makefiles를 갖고있는 프로그램을 자동으로 갖출 수 있다. 하지만 "make" 가 몇 가지 소스에 대해 이 같은 트릭을 수행하지 않는다면 어떻게 하겠는가? 우리는 특정 코드를 이용하여 이러한 상황을 대처할 합리적인 방법이 필요하다. 이를 위해 ebuild_compile() 함수를 두 개의 함수로 변형할 것이다. 첫 번째 함수는 "부모" 함수라고 할 수 있는 ebuild_compile() 이다. 하지만 user_compile() 이라는 새로운 함수를 갖게 될 것이다:
ebuild_compile()를 두 개의 함수로 나누기
user_compile() {
#we're already in ${SRCDIR}
if [ -e configure ]
then
#run configure script if it exists
./configure --prefix=/usr
fi
#run make
make
}
ebuild_compile() {
if [ ! -d "${SRCDIR}" ]
then
echo "${SRCDIR} does not exist -- please unpack first."
exit 1
fi
#make sure we're in the right directory
cd ${SRCDIR}
user_compile
} |
e2fsprogs-1.18.ebuild
#this ebuild file overrides the default user_compile()
P=e2fsprogs-1.18
A=${P}.tar.gz
user_compile() {
./configure --enable-elf-shlibs
make
} |
e2fsprogs는 원하는 방식으로 정확히 컴파일 될 것이다. 하지만 대부분의 패키지의 경우 .ebuild 파일에서 custom user_compile() 함수를 생략할 수 있고 default user_compile() 함수가 대신 사용된다.
ebuild 스크립트가 어떤 user_compile() 함수를 사용할 지를
이 예제에서 하나의 설정 옵션만을 추가했다. 원한다면 더 많은 것을 추가해도 된다. /etc/ebuild.conf가 소싱되면, "$MAKEOPTS"는 ebuild 스크립트내에서 정의된다. 일반적으로 이 옵션은 사용자가 ebuild에게 병렬(parallel) make를 수행하도록 명령할 수 있도록 한다.
다음은 ebuild 프로그램의 마지막 버전이다:
ebuild
#!/usr/bin/env bash
if [ $# -ne 2 ]
then
echo "Please specify ebuild file and unpack, compile or all"
exit 1
fi
source /etc/ebuild.conf
if [ -z "$DISTDIR" ]
then
# set DISTDIR to /usr/src/distfiles if not already set
DISTDIR=/usr/src/distfiles
fi
export DISTDIR
ebuild_unpack() {
#make sure we're in the right directory
cd ${ORIGDIR}
if [ -d ${WORKDIR} ]
then
rm -rf ${WORKDIR}
fi
mkdir ${WORKDIR}
cd ${WORKDIR}
if [ ! -e ${DISTDIR}/${A} ]
then
echo "${DISTDIR}/${A} does not exist. Please download first."
exit 1
fi
tar xzf ${DISTDIR}/${A}
echo "Unpacked ${DISTDIR}/${A}."
#source is now correctly unpacked
}
user_compile() {
#we're already in ${SRCDIR}
if [ -e configure ]
then
#run configure script if it exists
./configure --prefix=/usr
fi
#run make
make $MAKEOPTS MAKE="make $MAKEOPTS"
}
ebuild_compile() {
if [ ! -d "${SRCDIR}" ]
then
echo "${SRCDIR} does not exist -- please unpack first."
exit 1
fi
#make sure we're in the right directory
cd ${SRCDIR}
user_compile
}
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
if [ -e "$1" ]
then
source $1
else
echo "Ebuild file $1 not found."
exit 1
fi
export SRCDIR=${WORKDIR}/${P}
case "${2}" in
unpack)
ebuild_unpack
;;
compile)
ebuild_compile
;;
all)
ebuild_unpack
ebuild_compile
;;
*)
echo "Please specify unpack, compile or all as the second arg"
exit 1
;;
esac
|
/etc/ebuild.conf 가 파일의 거의 시작 부분에서 "source"되었다는 것을 주목하라. 그리고 default user_compile() 함수에서 "$MAKEOPTS"를 사용했다는 것도 주목해라. 우리는 /etc/ebuild.conf를 소싱하기 전에 "$MAKEOPTS"를 참조했다. 다행히 user_compile()가 실행될 때 변수 확장이 발생하기 때문에 정상적일 수 있었다.
이 글을 통해 다양한 배쉬 프로그래밍 기술을 다뤘다. 하지만 배쉬의 표면만을 다루었을 뿐이다. 예를들어, Gentoo Linux ebuild 시스템은 각 패키지를 자동으로 언패킹하고 컴파일 할 뿐만아니라 다음과 같은 일도 수행할 수 있다:
- "$DISTDIR"에 소스가 없다면 소스를 자동으로 다운로드 한다.
- MD5 message digest를 사용하여 소스가 오염되지 않는다는 것을 확인한다.
- 컴파일된 애플리케이션을 파일시스템에 설치하고, 설치된 모든 파일을 기록하여 나중에 패키지를 쉽게 언인스톨(uninstall)할 수 있도록 한다.
- 컴파일된 애플리케이션을 tarball에 패키징하여 나중에 다른 컴퓨터에 설치 될 수 있도록 한다. 또는 CD 기반의 설치 프로세스동안에 설치될 수 있도록 한다.
이 글에서 다루었던 것 이상으로 배쉬가 더 많은 일을 수행한다. 툴을 다루는 기술을 익혀 개발 프로젝트를 발전시킬 수 있기를 기대한다.
- developerWorks worldwide 사이트에서 이 기사에 관한 영어원문.
- source tarball (sed-3.02.tar.gz) 다운로드: ftp://ftp.gnu.org/pub/gnu/sed.
-
"예제로
배우는 배쉬 프로그래밍: Part 1".
-
"예제로
배우는 배쉬 프로그래밍: Part 2".
-
Gentoo Project.
-
GNU의 배쉬 홈페이지.
Daniel Robbins는 Gentoo Project의 수석 아키텍트이자 Gentoo Technologies, inc.의 CEO이다. 또한 Linux Advanced Multimedia Project (LAMP)에 많은 도움을 주고 있다. Caldera OpenLinux Unleashed, SuSE Linux Unleashed, Samba Unleashed 등에 기고활동을 하고 있다.