您的位置:首页 > 运维架构 > Apache

从Apache Ant到Gradle再到Travis CI——构建!构建!构建!

2020-06-08 05:35 405 查看

概述

本文原载于我的博客,地址: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简洁的配置还是惊艳到我了,墙裂推荐。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: