级别: 初级 Paul Duvall, CTO, Stelligent Incorporated
2007 年 11 月 22 日 Ant 无疑是用于 Java™ 平台的标准构建工具;但是,其它一些构建工具也可以为 XML 提供它一直所缺乏的更具表达性的范例。在这一期的
让开发自动化
中,自动化专家介绍了构建在 Ruby 之上的构建平台 Raven,它可以利用功能全面的编程语言和以构建为中心的域特定语言(Domain Specific Language)的简单性。
我手头上有一个 Java 项目,在过去几年的时间里,我小心翼翼地为之维护一个 Ant 构建脚本。Ant 能够执行大量任务,这一点我十分欣赏;然而,我常常发现,Ant 脚本的 XML 语法编写起来有些麻烦。而且,在 可表达性 方面,Ant 的 XML 部分还存在限制。实际上,当我发现自己需要更高程度的灵活性(例如在条件逻辑方面)时,我常常不得不在 Ant 的 script 任务中编写(例如使用 Groovy)定制任务或嵌入式逻辑。
告别表达乏力
 |
关于本系列 作为开发人员,我们的工作就是为终端用户实现过程自动化;然而,很多开发人员却忽略了将自己的开发过程自动化的机会。为此,我编写了
让开发自动化
这个系列的文章,专门探讨软件开发过程自动化的实际应用,并教您何时 以及如何 成功地应用自动化。
|
|
数年前,当我在寻找可表达性更强的软件构建工具时,我听说过 Rake。Rake 是用于使用功能全面的 Ruby 编程语言构建软件的一种域特定语言。通过 Rake,我可以轻松获得正在寻找的可表达性。例如,我不必编写定制任务,因为 Rake 构建脚本是 Ruby(而不是 XML);因此,如果我需要某种条件逻辑,那么很容易在需要的地方编写这种逻辑。而且,Rake 采用基于依赖的任务系统,这类似于 Ant 及其前辈 make 中的任务系统。例如,如果输入 rake deploy,Rake 将调用之前设置的依赖任务,例如编译,运行测试等等。而且 Rake 非常智能,它只运行一次依赖性任务(就像 Ant 一样)。
对于大多数 Java 项目来说,Rake 本身并不能提供多大帮助;然而,一个相对较新的项目 Raven 将 Rake 的功能和可表达性与用于构建 Java 应用程序的特定任务结合起来。有了 Raven,Java 开发人员就有了一个真正可行的构建平台,该平台可提供特定于 Java 的特性,例如使用 javac 进行编译以及使用 jar 和 war 归档二进制文件,同时还拥有 Ruby 编程语言的丰富的可表达性。
 |
什么是 DSL? 域特定语言(DSL)是专门为特定任务设计的编程语言。
Java 语言和 Ruby 是通用的编程语言,而 Ant 和 Rake 是专用于构建软件的 DSL。Rake 被认为是一种内部 DSL,因为它是用 Ruby 编写的,并且可以使用 Ruby 进行扩展。而 Ant 是一种外部 DSL,因为它首先被解析,然后在另一种语言中执行(也就是说,首先解析 XML,然后通过 Java 程序来运行)。
|
|
Raven 是 Rake,Rake 是 Ruby
在将 Raven 用于 Java 项目之前,理解 Raven 与 Rake 之间的关系会有所帮助。图 1 显示了 Raven 的一个逻辑图,可以看到,Raven 使用 Rake、RubyGems 和 Ruby。相应地,在使用 Raven 时,可以使用本地 Rake 任务。而且,还可以在 Raven 脚本中使用 Ruby。RubyGems 是类似于 CPAN、RPM 或 APT 的一个包管理器,用于下载必需的 Ruby 包(以及 Java jar)和任何依赖包。
图 1. Raven 的逻辑图
虽然还不确定 Raven 是不是构建 Java 项目的最佳 平台,但是可以发现,在转向更自然的编程语言方面,Raven 提供了一个简单而强大的组合。要是让我来说,单凭不必依赖 XML 就可构建 Java 项目这一点,足以使 Raven 获得更多关注。
下载和安装 Raven
有两种方法可安装和配置 Raven,第一种方法是 Ruby 方法,第二种方法则更倾向于 Java,即使用
JRuby。我推荐 Java 方法,因为这样更容易配置。安装 Raven 和使之运行需要以下步骤:
- 下载 Raven/JRuby 发行版(参见 参考资料)。
- 将文件解压到工作站上的一个位置。
- 创建环境变量
JRUBY_HOME。
- 将
JRUBY_HOME/bin 添加到 PATH 环境变量中。
- 打开一个命令提示符,输入
jruby -version,以检查安装情况。如果完全安装成功,那么在命令窗口中应该看到类似于 ruby 1.8.5 (0) [java] 的内容。
开始使用 Raven
当然,下载和安装 Raven 只是小菜一碟。真正有趣的是在创建构建脚本时 Raven 具有的灵活性。在 Raven 术语中,构建文件被称作 Rakefiles,它们有一个简单的格式。实际上,如果您用 Ruby 编写过程序(或者见过 Ruby 代码),那么这些文件看上去会很熟悉。这正是 Raben 的美妙之处:它就是 Ruby — 只不过在其中添加了一些特殊的词。
为了尽快使用 Raven ,毫无疑问,首先我要执行最基本(但是也很重要)的任务,即编译。图 2 中的目录结构表示了我的 Java 项目的布局。如果知道第三方库和源代码所在的位置,就能更快上手了。
图 2. 现有 Java 项目的目录结构
注意图 2 中的 lib 结构,第三方 JAR 就在其中。在开始编译之前,必须创建一个路径,使用 Raven 做这件事再容易不过了,因为它支持通过它的 dependency 任务(实际上是 Raven 提供的一个名为 dependency 的 Ruby 方法)创建类路径,如清单 1 所示:
清单 1. 使用 Raven 创建类路径
require 'raven'
require 'rake'
dependency 'deps' do | task |
task.libs = Dir.glob('lib/**/*.*')
end
|
根据清单 1 中的定义,deps 依赖任务使用 Dir 类和 glob() 方法包括 lib 目录中的所有库。还可以使用这个依赖任务(通过 RubyGems)从一个储存库中下载 JAR(这类似于 Maven 的依赖管理机制)。
在清单 1 中,我特意在路径中包括了 lib 目录中的所有东西。我将这个任务命名为 deps,在尝试编译任何文件之前,都可以显式地执行该任务,如清单 2 所示:
清单 2. 编译很容易
javac 'compile' => 'deps' do |task|
task.build_path << "src"
end
|
可以看到,我通过 Raven 的 javac 任务执行编译。该任务名为 compile,它对清单 1 中的 deps 任务存在依赖。符号 => 的意思是,Raven 将在当前任务之前 运行该操作符右边的任务(这很像 Ant 中 target 元素的 depends 属性。
魔力就发生在 javac 任务的 do 和
end 之间。默认情况下,javac 任务使用
"src/main/java" 作为 build_path 的默认值。但是,由于我的 Java 项目并没有遵循这个惯例(还记得 图 2 吗?),我需要修改 build_path 属性,在这里它应该为 src,因为源代码就在其中。
为了测试 Raven 脚本,需要打开一个命令提示符,然后输入:rake compile。编译后会生成 .class 文件,这些文件被放到一个名为 target/classes 的目录中,该目录是由 javac Raven 任务自动生成的。
少就是多
这里要弄清楚一点。我不必编写很多代码就可以完成编译,因为 Raven 在幕后处理了一些事情,例如创建 target/classes 目录并将生成的类文件复制到这个位置。实际上,您可能已经忘了 清单 1 和 清单 2 中对应的 Ant 脚本是什么样子。这里提示一下,清单 3 显示了一个典型的使用 Ant 构建脚本的编译:
清单 3. 使用 Ant 编译 Java 源代码
<?xml version="1.0" encoding="iso-8859-1"?>
<project name="compile-code" default="all" basedir=".">
...
<target name="compile-src">
<mkdir dir="${classes.dir}"/>
<javac destdir="${classes.dir}" debug="true">
<src path="${src.dir}" />
<classpath refid="project.class.path"/>
</javac>
</target>
<project>
|
将清单 3 与 清单 1 和 清单 2 比较,可以看出一些明显的不同。Raven 不但废除了 XML,而且还假定一些基本的属性,使您在定义任务时需要指定的东西更少。而且,如清单 2 所示,如果有必要,Raven
允许重写假定的属性。
虽然从比较的角度来看清单 1 和清单 2 确实比较有趣,但编译只是构建脚本的有趣之处的一个开始。让我们来看一个更有趣的特性:打包 Java Web 应用程序。
简单的 war 文件生成
Web 归档(Web Archive,WAR)文件用于打包 Web 应用程序,其中包含诸如 servlet、第三方库和图像之类的各种资源。这些归档文件使用标准的目录命名模式,并且只接受某些文件,例如 web.xml(该文件映射 servlet 名称和其它配置方面),放置在 WEB-INF 目录中。
Raven 使 WAR 文件的创建变得非常简单。在清单 4 中,我以 'brewery.war' 作为名称使用 war 任务,默认情况下,该任务创建一个具有相同名称的 WAR 文件。webapp_dir 的默认位置是 src/main/webapp,但是由于我的目录结构不同于 Raven 的假定,因此需要通过 task.webapp_dir = 'src/web' 行对此进行修改。
清单 4. 创建部署到 Web 容器的 WAR
war 'brewery.war' => ['clean', 'compile', 'java-doc'] do |task|
task.webapp_dir = 'src/web'
end
|
注意,在创建 WAR 文件之前,Raven 必须首先运行 clean 任务,接着运行 compile 任务,然后再运行一个任务来创建一些文档。在这些任务成功运行之后,Raven 可以创建一个归档文件,您将看到,这里需要输入的东西很少。实际上,如果我的项目遵循 Raven 假定的默认目录布局的话,那么除了那些依赖任务之外,不再需要编写任务东西(也就是说,不需要 do 和 end 之间的代码!)。
不管您信不信,Raven 就这么简单 — Raven 的 war 任务处理目录的创建,文件的放置,以及 war 文件的生成。非常高效,不是吗?
 |
是 Raven、Rake 还是 Ruby?
所有都是!Rake 是使用 Ruby 构建项目的一种 DSL。Raven 则使用 Rake 来为构建 Java 项目提供特定于 Java 的功能。就像 Rake 一样,通过 Raven 可以在构建脚本中充分使用 Ruby 编程语言。
|
|
轻松地生成文档
生成项目文档对于向团队成员宣传有用信息通常会有所帮助。如果别人计划使用您正在编写的框架代码或实用程序,那么您最终需要发布 Javadoc,这些 Javadoc 是描述相关源代码中的类、方法和公共实例变量的一些 HTML 文件。
通过 Raven,可以很快地利用 Javadoc 机制生成文档,如清单 5 所示:
清单 5. 使用 Raven 生成 JavaDoc 文档
javadoc 'java-doc' => 'deps' do |task|
task.build_path << "src"
end
|
请再次注意,由于我的项目没有遵循 Raven 假定的模型,因此必须修改 javadoc 任务的 build_path 属性。此外,该任务依赖于 deps 任务(在 清单 1 中)来包括所有类引用。
至此,您大概已经意识到,如果项目遵循 Raven 假定的布局,那么构建脚本甚至比我的还要简单!
更多特性!
虽然我只谈到了 Raven 中提供的一部分特性,但是还可以用这个创新的构建平台做更多的事情。特别地,Raven 为使用 RubyGems 机制处理构建依赖(即第三方库)提供了广泛的支持,RubyGems 在构建时自动下载版本化的二进制文件,而不是引用共享驱动。RubyGems 以类似于 Maven 或 Ivy 的方式处理第三方库。
除了 RubyGems 之外,Raven 还可以运行 JUnit 测试,构建一般的 JAR 文件,并且还可以清理目录。表 1 列出了 Raven 中一些较流行的、可方便构建 Java 项目的任务:
表 1. 流行(并且有用)的 Raven 任务
| Raven 任务 | 描述 |
|---|
javac
| 编译 Java 源文件,默认情况下将生成的类文件放入
target/classes 目录中。 |
jar
| 生成一个 JAR 文件,该文件通常是 .class 文件的一个集合。 |
war
| 生成一个可部署到 Java Web 容器的 Web 应用程序归档文件。 |
javadoc
| 根据源代码生成 Javadoc。 |
junit
| 执行 JUnit 测试。 |
jar_source
| 根据 Java 源文件创建一个 JAR 文件。 |
gem_wrap_inst
| 将 JAR 文件转换到 RubyGem,然后将该文件安装到一个存储库中。 |
随着 Raven 的成熟,表 1 中的列表会进一步扩大。此外,如果 Raven 中还缺少某种您需要的功能,您完全可以自己编写它!
结束语
本文演示了 Raven 的强大功能,其美妙之处在于,您可以在构建脚本中利用 Ruby 语言的强大功能和灵活性。Raven 是否会成为构建 Java 项目的流行平台不是我能决定的,但是我相信 Raven 是在推动业界朝着正确的方向前进。特别是,Raven 通过一种功能全面的命令式编程语言(而不是像 XML 那样的说明性语言)支持基于依赖的任务。尝试一下,您就会明白我的意思。
参考资料 学习
获得产品和技术
讨论
关于作者
对本文的评价
|