共计 10322 个字符,预计需要花费 26 分钟才能阅读完成。
Bash 实例-探讨 Gentoo ebuild 零碎
日期:2006-10-25 作者:Daniel Robbins 来自:IBM DW 中国
Daniel Robbins 在其最初一篇 Bash 实例文章中具体讲述了 Gentoo Linux ebuild 零碎,这个展现 bash 能力的极佳范例。循序渐进地,他为您展现如何实现 ebuild 零碎,并涉及很多不便的 bash 技术和设计策略。在本文开端,您将很好地把握制作齐全基于 bash 的利用所波及的技术,并开始为本人的主动构建零碎编码。
进入 ebuild 零碎
我真是始终期待着这第三篇、也是最初一篇 Bash 实例 文章,因为既然曾经在 第 1 篇和 第 2 篇 中讲述了 bash 编程根底,就能够集中讲述象 bash 利用开发和程序设计这样更高级的主题。在本文中,将通过我花了许多工夫来编码和细化的我的项目,Gentoo Linux ebuild 零碎,来给您大量理论的、事实世界的 bash 开发教训。
我是 Gentoo Linux(目前还是 beta 版的下一代 Linux OS)的首席设计师。我的次要责任之一就是确保所有二进制包(相似于 RPM)都正确创立并一起应用。正如您可能晓得的,规范 Linux 零碎不是由一棵对立的源树组成(象 BSD),而实际上是由超过 25 个协同工作的外围包组成。这其中包含:
包 | 形容 |
---|---|
linux | 理论内核 |
util-linux | 与 Linux 相干的杂项程序汇合 |
e2fsprogs | 与 ext2 文件系统相干的实用程序汇合 |
glibc | GNU C 库 |
每个包都位于各自的 tar 压缩包中,并由不同的独立开发人员或开发小组保护。要创立一个发行版,必须对每个包别离进行下载、编译和打包解决。每次要修复、降级或改良包时,都必须反复编译和打包步骤(并且,包的确更新得很快)。为了帮忙打消创立和更新包所波及的反复步骤,我创立了 ebuild 零碎,该零碎简直全用 bash 编写。为了减少您的 bash 常识,我将循序渐进地为您演示如何实现该 ebuild 零碎的解包和编译局部。在解释每一步时,还将探讨为什么要作出某些设计决定。在本文开端,您不仅将极好地把握大型 bash 编程我的项目,还实现了残缺主动构建零碎的很大一部分。
为什么抉择 bash?
Bash 是 Gentoo Linux ebuild 零碎的根本组件。抉择它做为 ebuild 的次要语言有几个起因。首先,其语法不简单,并且为人们所相熟,这特地适宜于调用内部程序。主动构建零碎是主动调用内部程序的“胶合代码”,而 bash 非常适合于这种类型的利用。第二,Bash 对函数的反对容许 ebuild 零碎应用模块化、易于了解的代码。第三,ebuild 零碎利用了 bash 对环境变量的反对,容许包保护人员和开发人员在运行时对其进行不便的在线配置。
构建过程回顾
在探讨 ebuild 零碎之前,让咱们回顾一下编译和安装包都关涉些什么。例如,让咱们看一下 “sed” 包,这个作为所有 Linux 版本一部分的规范 GNU 文本流编辑实用程序。首先,下载源代码 tar 压缩包 (sed-3.02.tar.gz)(请参阅 参考资料)。咱们将把这个档案存储在 /usr/src/distfiles 中,将应用环境变量 “$DISTDIR” 来援用该目录。”$DISTDIR” 是所有原始源代码 tar 压缩包所在的目录,它是一个大型源代码库。
下一步是创立名为 “work” 的长期目录,该目录寄存曾经解压的源代码。当前将应用 “$WORKDIR” 环境变量援用该目录。要做到这点,进入有写权限的目录,而后输出:
将 sed 解压缩到长期目录
$ mkdir work
$ cd work
$ tar xzf /usr/src/distfiles/sed-3.02.tar.gz
而后,解压缩 tar 压缩包,创立一个蕴含所有源代码、名为 sed-3.02 的目录。当前将应用环境变量 “$SRCDIR” 援用 sed-3.02 目录。要编译程序,输出:
将 sed 解压缩到长期目录
$ cd sed-3.02
$ ./configure --prefix=/usr(autoconf 生成适当的 make 文件,这要花一些工夫)$ make
(从源代码编译包,也要花一点工夫)
因为在本文中只讲述解包和编译步骤,所以将略过 “make install” 步骤。如果要编写 bash 脚本来执行所有这些步骤,则代码可能相似于:
要执行解包/编译过程的样本 bash 脚本
#!/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
使代码通用
尽管能够应用这个主动编译脚本,但它不是很灵便。基本上,bash 脚本只蕴含在命令行输出的所有命令列表。尽管能够应用这种解决方案,然而,最好做一个只通过更改几行就能够疾速解包和编译任何包的实用脚本。这样,包保护人员将新包增加到发行版所需的工作就大为缩小。让咱们先尝试一下应用许多不同的环境变量来实现,使构建脚本更加实用:
新的、更通用的脚本
#!/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
曾经向代码中增加了很多环境变量,然而,它基本上还是执行同一性能。然而,如果当初要要编译任何规范的 GNU 基于 autoconf 的源代码 tar 压缩包,只需简略地将该文件复制到一个新文件(用适合的名称来反映它所编译的新包名),而后将 “$A” 和 “$P” 的值更改成新值即可。所有其它环境变量都主动调整成正确设置,并且脚本按料想工作。尽管这很不便,然而代码还有改良余地。这段代码比咱们开始创立的 “transcript” 脚本要长很多。既然任何编程我的项目的指标之一是缩小用户复杂度,所以最好大幅度缩短代码,或者至多更好地组织代码。能够用一个奇妙的办法来做到这点 — 将代码拆成两个独自文件。将该文件存为 “sed-3.02.ebuild”:
sed-3.02.ebuild
#the sed ebuild file -- very simple!
P=sed-3.02
A=${P}.tar.gz
第一个文件不重要,只蕴含那些必须在每个包中配置的环境变量。上面是第二个文件,它蕴含操作的次要局部。将它存为 “ebuild”,并使它成为可执行文件:
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
既然曾经将构建零碎拆成两个文件,我敢打赌,您肯定在想它的工作原理。基本上,要编译 sed,输出:
$ ./ebuild sed-3.02.ebuild
当执行 “ebuild” 时,它首先试图 “source” 变量 “$1″。这是什么意思?还记得 前一篇文章 所讲的吗:”$1” 是第一个命令行自变量 — 在这里,是 “sed-3.02.ebuild”。在 bash 中,”source” 命令从文件中读入 bash 语句,而后执行它们,就好象它们间接呈现在 “source” 命令所在的文件中一样。因而,”source ${1}” 导致 “ebuild” 脚本执行在 “sed-3.02.ebuild” 中定义 “$P” 和 “$A” 的命令。这种设计更改的确不便,因为如果要编译另一个程序,而不是 sed,能够简略地创立一个新的 .ebuild 文件,而后将其作为自变量传递给 “ebuild” 脚本。通过这种形式,.ebuild 文件最终非常简单,而将 ebuild 零碎简单的操作局部存在一处,即 “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 脚本再承受一个命令行自变量:”compile”、”unpack” 或 “all”。这个命令行自变量通知 ebuild 脚本要执行构建过程的哪一步。通过这种形式,能够通知 ebuild 解包档案,但不进行编译(以便在开始编译之前查看源代码档案)。要做到这点,将增加一条 case 语句,该语句将测试 “$2″,而后依据其值执行不同操作。代码如下:
ebuild,修定本 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() {
#make sure we're in the right directory
cd ${SRCDIR}
if [! -d "${SRCDIR}" ]
then
echo "${SRCDIR} does not exist -- please unpack first."
exit 1
fi
./configure --prefix=/usr
make
}
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
曾经做了很多改变,上面来回顾一下。首先,将编译和解包步骤放入各自的函数中,其函数名别离为 ebuild_compile() 和 ebuild_unpack()。这是个好的步骤,因为代码正变得越来越简单,而新函数提供了肯定的模块性,使代码更有条理。在每个函数的第一行,显式 “cd” 到想要的目录,因为,随着代码变得越来越模块化而不是线形化,呈现忽略而在谬误的当前工作目录中执行函数的可能性也变大。”cd” 命令显式地使咱们处于正确的地位,并避免当前呈现谬误 – 这是重要的步骤,特地是在函数中删除文件时更是如此。
另外,还在 ebuild_compile() 函数的开始处增加了一个有用的查看。当初,它查看以确保 “$SRCDIR” 存在,如果不存在,则打印一条通知用户首先解包档案而后退出的谬误音讯。如果违心,能够扭转这种行为,以便在 “amp;$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 脚本第二版中最显著的改变之一就是代码开端新的 case 语句。这条 case 语句只是查看第二个命令行自变量,而后依据其值执行正确操作。如果当初输出:
$ 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 脚本,以解包和编译所青睐的程序。如果这样做,迟早会遇到一些不应用 autoconf (“./configure”) 的源代码,或者可能遇到其它应用非标准编译过程的脚本。须要再对 ebuild 零碎做一些改变,以适应这些程序。然而在做之前,最好先想一下如何实现。
将 “./configure –prefix=/usr; make” 硬编码到编译阶段的妙处之一是:大多数时候,它能够正确工作。然而,还必须使 ebuild 零碎适应那些不应用 autoconf 或失常 make 文件的源代码。要解决这个问题,倡议 ebuild 脚本缺省执行以下操作:
- 如果在 “${SRCDIR}” 中有一个配置脚本,则按如下执行它:
./configure --prefix=/usr
否则,跳过这步。 - 执行以下命令:
make
既然 ebuild 只在 configure 理论存在时才运行它,当初能够主动地适应那些不应用 autoconf 但有规范 make 文件的程序。然而,在简略的 “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
}
当初这样做的起因可能还不是很显著,然而,再忍受一下。尽管这段代码与 ebuild 前一版的工作形式简直雷同,然而当初能够做一些以前无奈做的 — 能够在 sed-3.02.ebuild 中笼罩 user_compile()。因而,如果缺省的 user_compile() 不满足要求,能够在 .ebuild 文件中定义一个新的,使其蕴含编译包所必须的命令。例如,这里有一个 e2fsprogs-1.18 的 ebuild 文件,它须要一个略有不同的 “./configure” 行:
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 文件中的任何定制 user_compile() 函数,而应用缺省的 user_compile() 函数。
ebuild 脚本又怎么晓得要应用哪个 user_compile() 函数呢?实际上,这很简略。ebuild 脚本中,在执行 e2fsprogs-1.18.ebuild 文件之前定义缺省 user_compile() 函数。如果在 e2fsprogs-1.18.ebuild 中有一个 user_compile(),则它笼罩后面定义的缺省版本。如果没有,则应用缺省 user_compile() 函数。
这是好工具,咱们曾经增加了很多灵活性,而无需任何简单代码(如果不需要的话)。在这里就不讲了,然而,还应该对 ebuild_unpack() 做相似批改,以便用户能够笼罩缺省解包过程。如果要做任何修补,或者文件蕴含在多个档案中,则这十分不便。还有个好主见是批改解包代码,以便它能够缺省辨认由 bzip2 压缩的 tar 压缩包。
配置文件
目前为止,曾经讲了很多不不便的 bash 技术,当初再讲一个。通常,如果程序在 /etc 中有一个配置文件是很不便的。侥幸的是,用 bash 做到这点很容易。只需创立以下文件,而后并其存为 /etc/ebuild.conf 即可:
/ect/ebuild.conf
# /etc/ebuild.conf: set system-wide ebuild options in this file
# MAKEOPTS are options passed to make
MAKEOPTS="-j2"
在该例中,只包含了一个配置选项,然而,您能够包含更多。bash 的一个妙处是:通过执行该文件,就能够对它进行语法分析。在大多数解释型语言中,都能够应用这个设计诀窍。执行 /etc/ebuild.conf 之后,在 ebuild 脚本中定义 “$MAKEOPTS”。将利用它容许用户向 make 传递选项。通常,将应用该选项来容许用户通知 ebuild 执行 并行 make。
什么是并行 make?
为了进步多处理器零碎的编译速度,make 反对并行编译程序。这意味着,make 同时编译用户指定数目的源文件(以便应用多处理器零碎中的额定处理器),而不是一次只编译一个源文件。通过向 make 传递 -j # 选项来启用并行 make,如下所示:
make -j4 MAKE="make -j4"
这行代码批示 make 同时编译四个程序。MAKE="make -j4"
自变量通知 make,向其启动的任何子 make 过程传递 -j4 选项。
这里是 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。另外,还要留神,在缺省 user_compile() 函数中应用 “$MAKEOPTS”。您可能在想,这管用吗 – 毕竟,在执行实际上当时定义 “$MAKEOPTS” 的 /etc/ebuild.conf 之前,咱们援用了 “$MAKEOPTS”。对咱们来说侥幸的是,这没有问题,因为变量扩大只在执行 user_compile() 时才产生。在执行 user_compile() 时,曾经执行了 /etc/ebuild.conf,并且 “$MAKEOPTS” 也被设置成正确的值。
结束语
本文曾经讲述了很多 bash 编程技术,然而,只涉及到 bash 能力的一些皮毛。例如,Gentoo Linux ebuild 产品不仅主动解包和编译每个包,还能够:
- 如果在 “$DISTDIR” 没找到源代码,则主动下载
- 通过应用 MD5 音讯摘要,验证源代码没有受到破坏
- 如果申请,则将编译过的利用程序安装到正在应用的文件系统,并记录所有装置的文件,以便日后能够不便地将包卸载。
- 如果申请,则将编译过的应用程序打包成 tar 压缩包(以您心愿的模式压缩),以便当前能够在另一台计算机上,或者在基于 CD 的装置过程中(如果在构建发行版 CD)装置。
另外,ebuild 零碎产品还有几个全局配置选项,容许用户指定选项,例如在编译过程中应用什么优化标记,在那些反对它的包中是否应该缺省启用可选的包反对(例如 GNOME 和 slang)。
显然,bash 能够实现的性能远比本系列文章中所涉及的要多。对于这个不堪设想的工具,心愿您曾经学到了很多,并鼓励您应用 bash 来放慢和加强开发我的项目。
原文链接:http://www-128.ibm.com/develo…