从Apache Ant到Gradle再到Travis CI——构建!构建!构建!
概述
本文原载于我的博客,地址:https://blog.guoziyang.top/archives/17/
从上古时期的命令行构建,到当代形形色色的自动构建工具,再到未来科技的在线构建,您已经历许多。现在,开启您最伟大的探索吧:从Apache Ant到Gradle再到Travis CI!
无聊地玩了一下文明的梗……
但是项目的构建确实是项目生命周期中的一项重要的环节。远古时期,大家都是手动构建,大都是命令行。但当IDE出现之后,好多人就丧失了构建的能力,满足于一键编译运行,隐藏了诸多细节。但是,软件构造课程要求上明确表示,要求“脱离IDE构建”。所以,我们还是来看一看当今使用的一些构建工具。
Java可用构建工具有Apache Ant、Apache Maven、Gradle和Travis CI。其中,Travis CI是线上构建工具,关联一个Github仓库,在每个commit后对整个仓库进行构建测试。线下的Apache Ant、Apache Maven和Gradle则是各有千秋。配置文件的书写是必不可少的,但是Apache Ant和其它两个有一个很大的不同:Ant的配置文件是在模拟命令行的工作,而Maven和Gradle则将细节隐藏的很好,不需要用户过度关注底层的细节,自动依赖引入,但是也牺牲了一定的自由。
Travis CI是关联GitHub仓库,对每一个commit进行整体构建。Travis CI会启动一个虚拟机(操作系统貌似是Ubuntu 14.04),将代码clone到虚拟机中,进行构建,并返回构建结果。Travis CI到构建脚本可以基于Apache Maven、Gradle和Apache Ant,系统在项目目录中查找这三个工具的配置文件以决定使用哪种工具。
由于软件构造实验的特殊性(src与test平级,依赖的jar包以文件的形式存在),我们选择Apache Ant作为线下构建工具,Travis CI作为线上构建工具。(补充,添加了Gradle的构建)
目录如下:
GuodeMacBook-Air:Lab1-1170300520 guoziyang$ tree -L 2 . ├── lib ------项目所需依赖jar │ ├── hamcrest-core-1.3.jar │ ├── javax.json-1.0.jar │ └── junit.jar ├── src ------项目的Java源码 │ ├── P1 │ ├── P2 │ ├── P3 │ └── P4 └── test ------项目的测试类 ├── P2 ├── P3 └── P4
PS:travis-ci.org只可以构建共有项目,私有项目需要在travis-ci.com中构建。
Apache Ant
Apache Ant是一个Apache基金会的自动构建工具。与其它线下构建工具(Maven和Gradle)不同的是,Ant不仅仅可以构建Java系(Java、Kotlin和Scala)的项目。事实上,它可以构建所有的可以通过命令行构建的项目。
Ant的官方网站是http://ant.apache.org 。
Apache Ant的安装不再赘述,无非是下载、解压、配置系统环境变量,最后成功的标志是在命令行中运行
ant -version,出现类似以下的响应:
GuodeMacBook-Air:~ guoziyang$ ant -version Apache Ant(TM) version 1.10.5 compiled on July 10 2018
Ant识别一个项目的方式是通过一个配置文件build.xml,Ant将与该配置文件同级的所有文件(夹)及其子文件(夹)视作同一项目。
配置文件build.xml是需要我们手动书写的,只需要在项目的根目录下新建build.xml即可。
配置文件示例如下:
<?xml version="1.0" encoding="UTF-8"?> <!-- 指定项目的名称,默认运行的指令,以及项目根目录 --> <project name="SC_Lab1" default="build" basedir="."> <!-- 以下设置一些目录的宏定义 --> <property name="src.dir" value="src"/> <property name="lib.dir" value="lib"/> <property name="build.dir" value="build"/> <property name="build.classes" value="${build.dir}/classes"/> <property name="build.apidocs" value="build/doc"/> <property name="testSrc.dir" value="test"/> <property name="reports.dir" value="build/report"/> <property name="correctreports.dir" value="${reports.dir}/html"/> <!-- 设置依赖目录的宏定义 --> <path id="classpath"> <fileset dir="${lib.dir}"> <include name="*.jar"/> </fileset> </path> <!-- available根据classname是否存在而设置property --> <target name="JUNIT"> <available property="junit.present" classname="junit.framework.TestCase"/> </target> <!-- 编译src下的所有Java文件,depends属性表示在执行该任务前,会事先执行的任务 --> <target name="compile" depends="JUNIT"> <!-- 建立一些输出的目录 --> <mkdir dir="${build.dir}"/> <mkdir dir="${build.classes}"/> <depend srcdir="${src.dir}" destdir="${build.classes}"/> <!-- javac命令编译java文件,srcdir指定java文件位置,destdir指定class文件输出位置,fork表示是否创建新的jvm执行 --> <javac srcdir="${src.dir}" destdir="${build.classes}" fork="true" includeantruntime="on"> <!-- classpath设置依赖路径 --> <classpath> <pathelement path="${build.classes}"/> <pathelement path="${java.class.path}/"/> </classpath> <classpath refid="classpath"/> <!-- include设置源文件为srcdir下的所有java文件 --> <include name="**/*.java"/> <!-- 编译参数,设置字符集编码 --> <compilerarg line="-encoding UTF-8 "/> </javac> </target> <!-- 编译所有的测试用例 --> <target name="testcompile" depends="compile"> <depend srcdir="${testSrc.dir}" destdir="${build.classes}"/> <!-- class文件输出目录与compile中的输出目录一致,可保持包结构不变 --> <javac srcdir="${testSrc.dir}" destdir="${build.classes}" fork="true" includeantruntime="on"> <classpath> <pathelement path="${build.classes}"/> <pathelement path="${java.class.path}/"/> <fileset dir="${lib.dir}"> <include name="*.jar"/> </fileset> </classpath> <compilerarg line="-encoding UTF-8 "/> </javac> </target> <!-- 运行junit测试 --> <target name="runtests" depends="testcompile"> <mkdir dir="${reports.dir}"/> <delete> <fileset dir="${reports.dir}" includes="**/*" /> </delete> <junit printsummary="on" failureProperty="fail"> <!-- 断言所需的参数 --> <jvmarg value="-ea"/> <classpath> <pathelement location="lib/***.jar" /> <pathelement path="${build.classes}"/> <pathelement path="${java.class.path}/"/> </classpath> <classpath refid="classpath"/> <formatter type="xml"/> <batchtest fork="yes" todir="${reports.dir}"> <fileset dir="${testSrc.dir}"> <include name="**/*Test.java"/> </fileset> </batchtest> </junit> <!-- 测试报告输出路径 --> <junitreport todir="${reports.dir}"> <fileset dir="${reports.dir}"> <include name="TEST-*.xml"/> </fileset> <report format="frames" todir="${correctreports.dir}"/> </junitreport> </target> <!-- 编译源文件为jar包 --> <target name="makejar" depends="compile" description="Jar生成"> <delete file="build/lib/main.jar"/> <jar jarfile="build/lib/main.jar" basedir="${build.classes}"> <fileset dir="${build.classes}"> <!-- 编译时排除Test文件 --> <exclude name="**/*Test"/> </fileset> <manifest> <attribute name="Main-Class" value="Main"/> </manifest> </jar> </target> <!-- 以下为运行的target,需要指定main的类名 --> <target name="run_MagicSquare" depends="makejar"> <java classname="P1.MagicSquare" classpath="build/lib/main.jar"> <classpath refid="classpath"/> </java> </target> <target name="run_TurtleSoup" depends="makejar"> <java classname="P2.turtle.TurtleSoup" classpath="build/lib/main.jar" fork="true"> <classpath refid="classpath"/> </java> </target> <target name="run_FriendshipGraph" depends="makejar"> <java classname="P3.FriendshipGraph" classpath="build/lib/main.jar"> <classpath refid="classpath"/> </java> </target> <target name="run_tweet" depends="makejar"> <java classname="P4.twitter.Main" classpath="build/lib/main.jar" fork="true"> <classpath refid="classpath"/> <jvmarg value="-ea"/> </java> </target> <target name="run_all" depends="run_MagicSquare, run_FriendshipGraph, run_tweet"> </target> <target name="build" depends="run_all, runtests"> </target> </project>
通过Ant运行junit测试时,在classpath中除了要有junit.jar以外,还需要hamcrest-core-1.3.jar这个jar包,否则会出现错误。
将以上配置文件放在项目的根目录后,在终端中cd进根目录,运行
ant + target名来执行某个target,如
ant build,如果直接使用
ant,则会执行project标签下的default属性指定的target。
使用以上配置文件的项目,会将junit报告输出在build/report中,报告以xml的形式呈现,在html文件夹中也可以找到网页版报告。
Gradle
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。面向Java应用为主。
简而言之,gradle很简单、很简单、很简单。
gradle的配置文件为build.gradle,也是放置于项目的根目录下。Gradle可以在https://gradle.org 下载。
一个典型的gradle的项目的目录结构如下,如果项目使用该目录结构则不需要特殊配置:
. └── src ├── main ------源码目录 | ├──java | └──resource └── test ------测试目录 ├──java └──resource
最大的问题还是目录结构的不符,需要在配置文件中重新指定各个目录的位置,还有个问题就是依赖的引入了,gradle默认从maven中央仓库获取依赖包,需要配置成从本地文件夹获取,以下就要展现gradle的极简配置了:
apply plugin: 'java' //必选,项目类型 //设置source目录结构 sourceSets { main { java.srcDirs = ['src'] resources.srcDirs = ['src'] } test { java.srcDirs = ['test'] resources.srcDirs = ['test'] } } //依赖文件来源 dependencies { compile fileTree(dir: 'lib', includes: ['*.jar']) }
没错,就这么点儿!在根目录运行
gradle build即可。
GuodeMacBook-Air:Lab2-1170300520 guoziyang$ gradle build Starting a Gradle Daemon (subsequent builds will be faster) BUILD SUCCESSFUL in 15s 5 actionable tasks: 5 executed
测试报告可以在build/reports/tests下查看。
Travis-CI
Travis CI是目前新兴的开源持续集成构建项目,其中CI代表持续集成(continuous integration)。Travis-CI可以与你的Github账号绑定,监控Github中仓库的状态,一旦有新的commit,就会试图构建该项目。
Travis-CI的构建本质上是基于Apache Ant、Apache Maven和Gradle的,取决于你的项目使用的项目管理工具。在一个commit到来时,Travis-CI会自动开启一台虚拟机,并在虚拟机上执行事先设置好的命令来构建。所以,Travis-CI本质上不是一个构建工具,只是一个帮你在线使用其它工具构建的工具(有点拗口)。
使用Travis-CI,首先需要将Github账号与Travis-CI绑定。如果你只需要构建公有项目,那么在https://travis-ci.org ,若要构建私有项目,则需要在https://travis-ci.com 绑定。
绑定完成后,Travis-CI就开始监控你每个项目的每一个commit。Travis-CI通过识别项目根目录下的.travis.yml来构建项目。这又是一个需要自己写的配置文件了……
以下以一个gradle项目为例。gradle构建项目的命令为
gradle build。.travis.yml文件如下:
language: java jdk: oraclejdk11 script: gradle build
很简单。该配置文件指定了项目的语言(java)、jdk的版本(oraclejdk11)以及自动构建的命令(gradle build)。将项目push到github后,即可自动开始构建。
如果你的项目是基于jdk8的,那么可以不用配置jdk属性,然而如果使用的不是jdk8,而你希望使用你的版本来编译,就需要书写jdk属性。
然而,如果你和我一样使用jdk11编译,就可能会遇到编译失败的情况,报错信息大致是gradle无法识别jdk11。根本原因在于,travis-ci默认使用的gradle版本较老,不支持最新的jdk,解决办法如下。
在build.gradle的最后加入:
//用于travis-ci的gradle版本同步任务 task update(type: Wrapper) { gradleVersion = '5.3' }
接着在项目根目录下执行
gradle update,会生成gradlew和gradlew.bat,并修改.travis.yml中的script为
./gradlew即可解决。
使用Travis-CI的一个好处是,可以生成一个小徽章,实时标识了当前项目的构建状态,如
build passing或者
build fail。
然而,Travis-CI的能力远大于此,例如,我的博客就是由Travis-CI自动生成后推送的,不再赘述。
事实上,除了Travis-CI以外,还有一个比较有名的开源在线CI项目——Jenkins。这个项目支持面更加广泛,而且不用花钱(Travis CI构建私有项目时需要会员)……要是我们学校什么时候可以搭建一个就好了,然而学校的垃圾服务器还是算了……(小声BB)
总结
其实没啥好总结的,毕竟我们也不知道老师会用啥构建我们的项目……但是不得不说,gradle简洁的配置还是惊艳到我了,墙裂推荐。
- Gradle自动化构建到Travis-CI持续集成的理解
- Travis CI生命周期以及不同阶段运行失败会导致生么构建结果。
- Gradle基于Apache Ant和Apache Maven概念的项目自动化构建工具本地安装及eclipse 项目集成
- 使用 Gradle 与 Travis CI 进行简单持续集成 II
- Travis CI GradleWrapperMain 错误
- hexo+Travis-ci+github构建自动化博客
- Jenkins+GItLab配置自动化构建CI/CD(Gradle项目的构建、Maven项目的构建、NodeJS项目的构建)
- Travis-ci集成构建系统
- [Latex] Travis-CI与Latex构建开源中文PDF
- 用Gradle 构建你的android程序-依赖管理篇
- Android之项目中如何用好构建神器Gradle?
- Gradle多项目构建并将项目导入到Eclipse
- 使用Jenkins+Git+Gradle自动化构建Android APK包
- gradle 构建apktool.jar
- Java构建工具:Ant vs Maven vs Gradle
- Java 中三大构建工具Ant、Maven和Gradle
- Gradle学习系列之四——增量式构建
- Gradle-使用Gradle构建和测试-2-GradleTasks(一)
- gradle学习(4)-构建java项目
- 灵活强大的构建系统Gradle