使用 Aap 自动化任务
许多人使用 Makefile 和 shell 脚本来自动化任务,在这些任务中,文件的更改需要采取行动。您编辑一个文件,然后调用make希望完成所有必要的行动以生效更改。通常您需要调整 Makefile 以使其正确,最终使用touch来解决缺失的依赖关系。结果,当其他人查看您精心调整的 Makefile 时,他们很难理解它是如何工作的。
与 make 相比,使用 Aap 可以更轻松、更可靠地完成这些任务。例如,Aap 具有内置的互联网支持。下载和上传由 Aap 负责处理,无需指定命令或保留时间戳文件。通过自动找出依赖关系并使用签名而不是时间戳来实现可靠性。使用 Aap,指定您想要完成的工作更简单,并且您可以减少错误。您仍然可以在需要时回退到使用 shell 命令。本文介绍了 Aap 在行动中的两个示例:维护网站和构建程序。当然,Aap 可以做更多的事情,但这些主题应该足以让您入门。
要使用 Aap,您需要 Python 1.5 或更高版本。万一您的系统上没有 Python,请从 Python.org 下载,或从您的 Linux 发行版 CD 或更新系统安装它。
安装 Aap 可以通过四个简单的步骤完成。首先下载最新的 Aap zip 压缩包(请参阅在线资源部分)。然后,在临时目录中解压压缩包(unzip aap-1.053.zip)。如果您是 root 用户,请运行./aap install。如果您是普通用户,请使用以下命令将压缩包安装到您的主目录中./aap install PREFIX=$HOME。有关下载和安装 Aap 的更多信息,请参阅资源。Aap 以 GNU GPL 下的开源软件形式分发。
在安装了当前的 Aap 软件后,让我们看看如何在基本示例任务中使用它。您设计了一个简单的网站,其中包含 HTML 文件和图像。这些文件位于您的本地计算机上,您需要将它们上传到 Web 服务器。列表 1 显示了执行此操作的 Aap 脚本,称为 recipe(配方)。
列表 1. 用于将文件上传到 Web 服务器的 Recipe
# The list of files to be uploaded. Files = index.html info.html download.html images/*.png # The publish attribute tells where to upload to. :attr {publish = scp://my.server.net/html/%file%} $Files # When executed without a target: publish the files. all : publish
将 recipe 存储在名为 main.aap 的文件中。此文件对于 make 就像 Makefile 一样——是要执行的默认文件。运行aap不带参数将在当前目录中执行 main.aap recipe。
Aap recipe 中的注释以 # 开头,并持续到行尾,就像在 Makefile 或 shell 脚本中一样。recipe 中的第一个有效行是赋值;要上传的文件列表被赋值给 Files 变量。没有反斜杠或标点符号来标记赋值的结束。Aap 通过使用的缩进量来识别命令延续。乍一看这可能很奇怪,但您很快就会习惯它。此方法避免了通常的标点符号错误,并强制执行易于阅读的布局。Aap 允许您使用制表符或空格来缩进行。
:attr 行是 Aap 命令。所有 Aap 命令都以冒号开头,以便于识别。此命令将 publish 属性添加到其参数中。publish 属性告诉 Aap 在发布文件时将文件上传到哪里。此处使用的方法是 scp://,安全复制。其他支持的方法是 rsync:// 和 ftp://。:attr 的最后一个参数是 $Files,即 Files 变量的值。该属性附加到 $Files 中的每个项目。
当您不带参数运行 Aap 时,它会更新目标 all。最后一行指定默认目标 all 依赖于 publish。这是一个特殊的目标,它告诉 Aap 上传所有具有 publish 属性的项目。
现在您可以编辑 HTML 文件,添加图片并在本地查看它们。一旦您满意,执行aap. Aap 会找出哪些文件已更改并上传它们。使用签名(校验和);因此,如果您恢复文件的旧版本,它仍然可以正常工作。如果您使用的是 make,则必须 touch 恢复的文件以更新其时间戳。
如果您想尝试此示例,但没有要上传到的服务器,则可以使用 publish 属性file:/tmp/html/%file%。使用它,Aap 将创建 /tmp/html 目录(如果需要)。
警告:Aap 不会删除服务器上不再使用的文件。再说一遍,make 也不这样做。您必须手动删除这些文件。希望自动删除功能很快会添加到 Aap 中。
通配符用于选择图像images/*.png。这很方便,但存在包含您不想上传的图像的危险。显式命名每个文件可以避免这种陷阱,但您可能会忘记图像。鉴于这是一个常见问题,Aap 提供了一个从 HTML 文件中提取图像文件名的函数。列表 2 显示了如何做到这一点;调用了 Python 函数 get_html_images,反引号括起来的是 Python 表达式。Aap 计算表达式并将结果(图像文件名)放在其位置。但是,get_html_images() 函数的功能有限。它仅适用于带有相对路径名的图像的纯 HTML 文件。
列表 2. 从 HTML 文件中获取图像文件名
# The list of files to be uploaded. Files = index.html info.html download.html Files += `get_html_images(Files)` # The publish attribute tells where to upload to. :attr {publish = scp://my.server.net/html/%file%} $Files # When executed without a target: publish the files. all : publish
大多数 HTML 文件由页眉、标题、主要内容和页脚组成。显然,您不想每次都键入公共部分。一个简单的解决方案是连接多个文件。列表 3 显示了实现此功能的 recipe。使用了五个部分:页眉、标题、中间部分、内容和页脚。标题和内容对于每个页面都不同,但其他三个部分是相同的。
列表 3. 从五个部分生成 HTML 文件
Files = index.html info.html download.html :rule %.html : header.part %_title.part middle.part %.part footer.part :cat $source >! $target :update $Files Files += `get_html_images(Files)` :attr {publish = scp://my.server.net/html/%file%} $Files all : publish
列表 2 和列表 3 之间的主要区别是在列表 3 中添加了 :rule 命令。它指定目标(HTML 文件)依赖于五个源文件(这些部分),并列出了从源文件构建目标的命令。% 字符代替文件名使用,类似于 * 通配符。规则中的所有 % 字符都代表相同的名称。因此,对于 index.html,% 代表 index。然后,源文件包括 index_title.part 和 index.part。
:rule 行下面是缩进的语句块,当需要更新规则的目标时,将执行这些语句。因此,recipe 有两个级别:顶层命令在读取 recipe 时执行,而规则的命令块稍后在需要时执行。
:cat 命令连接文件,与 UNIX cat 命令相同。实际上,它可以做更多的事情,例如从指定的 URL 读取文件。在规则中,$source 代表整个源文件列表。
HTML 文件需要在获取它们包含的图像文件列表之前生成。为了正确执行此操作,在调用 get_html_images() 之前调用了 :update 命令。HTML 文件使用定义的规则进行更新。这位于 recipe 的顶层,因此始终在 Aap 读取 recipe 时完成。
现在您有这么多文件,Aap 如何跟踪需要完成的工作?Aap 与依赖关系一起工作,与 make 相同。它从您在命令行上指定的目标开始。当没有给出目标时,假定为 all。然后,Aap 查找那些依赖关系和规则,其中此目标出现在冒号之前。冒号基本上意味着依赖于;冒号之后是目标依赖的源文件。然后检查每个源文件,Aap 找到它们作为目标出现的规则。这将递归地继续,直到找不到更多规则。结果是依赖关系树。然后,Aap 执行需要构建的那些依赖关系的命令,从树的末端开始(深度优先)。这听起来很复杂,不是吗?因为 Aap 负责处理这一点,您只需确保指定每个目标所依赖的源文件。Aap 会找出需要做什么。
作为一个不错的补充,让我们在 HTML 文件中添加一个时间戳,以便您可以在网站上看到页面上次生成的时间。将字符串 @TIMESTAMP@ 放在文件 footer.part 中的某个位置。列表 4 显示了将此字符串替换为当前日期的规则。recipe 的其余部分如列表 3 所示。:eval 命令计算 Python 表达式,string.replace 是用于将一个字符串替换为另一个字符串的标准 Python 函数。这样,您可以使用任何 Python 表达式来过滤文本。HTML 页面通过 :eval 命令进行管道传输,就像使用 shell 一样。
列表 4. 用于在生成的 HTML 文件中放置时间戳的规则
:rule %.html : header.part %_title.part middle.part %.part footer.part :print Generating $-target :cat $source | :eval string.replace(stdin, '@TIMESTAMP@', _no.DATESTR) >! $target
第一次使用新规则时,所有 HTML 文件都会更新。这是因为 Aap 记住了命令的签名。因此,您无需担心在更改 recipe 中的命令后强制生成文件。
当对网页进行小的更改时,每次都上传整个文件是对带宽的浪费。一种高效上传的好方法是使用 rsync。它仅上传已更改的文件部分。当 Aap 在 publish 属性中找到 rsync:// 时,它会使用 rsync。默认情况下,rsync 通过 SSH 连接使用。您可以通过设置 $RSYNC 变量来更改此设置。
rsync 不是标准命令。如果系统上不存在,您会遇到 Aap 的一个不错的功能——您可以选择安装 rsync
% aap Aap: Uploading ['index.html'] to rsync://my.server.net/html/index.html Cannot find package "rsync"! 1. Let Aap attempt installing the package 2. Retry (install it yourself first) q. Quit Choice:
Aap 具有一种机制,可以在需要时安装软件包,方法是从 Aap 网站下载一个 recipe,该 recipe 指定如何安装软件包。Aap 的下载功能在这里派上用场。软件包的安装方式取决于您的系统;并非所有系统都受支持。安装 rsync 后,Aap 开始上传文件。
Aap 包括从 C 和 C++ 代码构建程序的支持。这是一行 recipe,用于从四个 C 源文件构建名为 myprog 的程序
:program myprog : main.c common.c various.c args.c
尽管 recipe 很简单,但 Aap 解决了几个问题
依赖关系是自动计算出来的。您无需指定包含的头文件或执行make depend.
此 recipe 在大多数系统上无需修改即可工作。Aap 会找到要使用的编译器和链接器,并计算出它们需要的参数。
对象文件存储在每个系统的单独构建目录中。您可以构建多个版本而无需清理。
Aap 创建一个日志文件 AAPDIR/log,其中包含有关发生情况的详细信息。如果您的构建失败并且输出滚动出屏幕,则无需使用重定向的输出重复构建命令。
自动添加了一些默认目标aap install安装程序,以及aap clean删除生成的文件。
可以使用 make 和一些额外的工具来完成相同的工作。但是 Makefile 会长得多且不可移植;维护起来也需要更多的精力。
列表 5. 构建发布和调试变体
:variant Build release OPTIMIZE = 4 Target = myprog debug DEBUG = yes Target = myprogd :program $Target : main.c common.c various.c args.c
:variant 命令的第一行指定用于选择要构建的变体的变量名。您可以在命令行上设置此变量;aap Build=debug构建调试版本。如果不带参数,则构建发布变体,因为它首先被提及。
缩进量标识 :variant 命令的其他部分。可能的值缩进较少;每个值使用的命令缩进更多。您被迫对齐这些部分,这使它们更易于阅读。
发布变体设置 OPTIMIZE 变量。这是一个介于 0 到 9 之间的数字,指示要完成的优化量。它会自动转换为正在使用的编译器的正确参数。调试变体将 DEBUG 设置为 yes。默认值为 no。Target 变量保存结果程序的名称。这两个变体使用不同的名称,因此这两个程序可以同时存在。
以这种方式使用变体的一个好处是,每个变体的对象文件都自动存储在单独的构建目录中。在两个变体之间切换时,您应该注意到 Aap 不会重建所有文件。
对于 C 和 C++ 以外的语言,您需要导入语言模块。Aap 包含一些标准模块。例如,这是如何从 D 源代码构建;D 是一种新的编程语言
:import d :program myprog : main.d common.d various.d args.d
:import d 命令用于加载对 D 语言的支持。否则,此过程与从 C 源代码构建类似。
您可以自己编写一个模块来添加对语言的支持。由于 Aap 是开源的,因此鼓励您提交模块以包含在 Aap 发行版中。在此之前,将文件放入 Aap 模块目录中;这可以作为插件使用。
构建 KDE 应用程序涉及使用许多工具,包括使用 Qt Designer 创建对话框,从用户界面描述生成头文件以及生成进程间通信代码。尽管如此,构建 KDE 应用程序的 recipe 可以像这样简单
:import kde :program logger : main.cpp logwidget.ui dcop.h {filetype = skel} {var_OBJSUF = _skel.o}
在三个输入文件中,main.cpp 可以直接编译。Qt Designer 文件 logwidget.ui 首先需要由 uic 处理以生成 include 文件;然后必须使用 moc。Aap 识别 .ui 后缀并处理所有这些。处理这种从 ui 到 h 到 moc 到对象文件的多步编译是 Aap 的一个有用功能。在 Makefile 中执行相同的操作需要更明确的规则。
dcop.h 文件包含特殊的 KDE 项目,但具有正常的后缀。它无法自动识别。因此,显式指定了 filetype 属性。:program 命令还需要知道对象文件的名称,该名称使用 var_OBJSUF 属性指定。您无需显式指定正在使用的 KDE 工具;复杂性隐藏在 KDE 模块中。这比使用 automake 复杂得多。
到目前为止,您已经使用了高级 Aap 命令来快速指定需要完成的工作。对于非标准任务,您需要详细说明依赖关系和命令。这主要像 Makefile 一样工作。除了 shell 命令外,您还可以使用可移植的 Aap 命令。如果这还不够,您可以添加 Python 脚本。
列表 6 显示了低级 recipe 的外观。此处明确给出了每个依赖关系——all 依赖于 hello,hello 从 hello.c 编译,而 hello.c 是从头生成的。
列表 6. 将 Aap 用作 make 替代品
all : hello # Manually compile the hello program. hello : hello.c :sys cc -o $target $source # Clumsy way to generate a C program. hello.c: :print Generating $target :print >! $target $(#)include $(<)stdio.h$(>) :print >> $target main() { :print >> $target printf("Hello World!\n"); :print >> $target return 0; :print >> $target }
由于 recipe 中的构建命令是 Aap 命令,因此您需要使用 :sys 来执行 shell(系统)命令。在示例中,:sys cc执行 C 编译器。显然,这仅在具有 cc 命令的系统上有效。使用 shell 命令会降低 recipe 的可移植性。
hello.c 文件是使用 :print 命令生成的。第一行使用>! $target来覆盖现有的 hello.c 文件。如果没有感叹号,如果文件已存在,您将收到错误消息。此行还包含 $(#),它转义了 # 字符的特殊含义以开始注释。同样,$(<) 和 $(>) 用于获取 < 和 > 字符,而不是重定向。
hello.c 文件在尚不存在时生成;未指定源文件依赖关系。该文件也可以在另一种情况下生成——如果您更改了 :print 命令之一,因为它更改了构建命令的签名。当构建命令更改时,Aap 知道必须重建目标。
该文件是使用 Aap 命令生成的;无需使用 shell 命令。因此,recipe 的这一部分可以在任何系统上工作。但是 Aap 命令的数量是有限的。当您需要更多功能并且还需要可移植性时,可以使用 Python 脚本。
Aap recipe 中的所有流程控制都是使用 Python 完成的,列表 7 说明了一个将补丁应用于 Vim 的 recipe 示例。循环用于生成补丁文件名列表,从 vim-6.2.001 开始计数到最后一个补丁号,用 LASTPATCH 指定。每个补丁文件都将被下载和应用。done/$*Patches 中的 $* 用于 rc 样式的变量扩展;done/ 被添加到 Patches 中的每个项目的前面。
列表 7. 使用 Python 创建名称列表
LASTPATCH = 144 # Generate a list of patch filenames. @Patches = '' @for i in range(1, int(LASTPATCH) + 1): @ Patches = Patches + ("6.2.%03d " % i) # Default target: apply all patches. all: done/$*Patches # Make sure the two directories exist. :mkdir {force} patches done # Rule for applying a patch. :rule done/% : patches/% {fetch = ftp://ftp.vim.org/pub/vim/%file%} :sys patch < $source :touch $target
通常,您不需要在 recipe 中使用太多 Python,但很高兴知道在出现复杂任务时可以完成它们。
我们已经提到,如果 Aap 在您的系统上找不到 rsync,它可以为您安装 rsync。软件包安装机制也可以直接调用。例如,要安装 Agide,请使用命令aap --install agide。Agide 是 A-A-P GUI IDE,是 A-A-P 项目的另一部分。您可以使用它来使用 Vim 和 gdb 构建和调试程序。它仍处于开发的早期阶段,但足以开发和调试 C 程序。
目前有几个软件包可用,随着时间的推移,将添加更多软件包。当前软件包列表可以在 www.a-a-p.org/packages.html 上找到。Aap 本身也可以安装。可以使用以下命令更新到最新版本aap --install aap。此命令会覆盖任何现有的 Aap 版本。如果您的系统有软件包管理器,您可能应该改用它。
现在您已经了解了可以使用 Aap 自动化的任务。当您开始试验时,您可以在全面的文档中找到很多帮助。您可以在 Aap 网站上以多种形式找到它(请参阅资源)。这些页面解释了本文中无法包含的许多内容,例如使用 CVS 进行版本控制、自动配置等等。
本文的资源: /article/7458。
Bram Moolenaar 是 Aap 的项目负责人和主要作者。他主要以他在文本编辑器 Vim 上的工作而闻名。Bram 在 Aap 上的工作由 Stichting NLnet www.NLnet.nl 资助。您可以在 www.Moolenaar.net 找到他的主页。