摘要
对于计算机学院的同学(或其它院的同学),完成各种涉及程序设计的实验、作业、大作业、课程论文等可能已经是习以为常的事了。为每项作业建立目录、打包等工作虽然简单,但本着尽量将重复性劳动自动化以降低错误率和节省时间的原则,本文介绍了一种方法,通过充分利用构建工具 Apache Ant,实现作业目录的自动化管理。
基本作业目录结构
作业目录树的根目录名在本文中被称作${aid}
,其中 aid 是 assignment ID 的缩写,本人在使用此管理工具时常令根目录名为数字,表示第几次实验或作业。
通常一个作业目录树中包含以下几项文件或文件夹:
- 报告文件夹(包含需要提交的实验报告、课程论文等)
- 代码文件夹(包含作业中涉及的源代码,例如把整个 Eclipse 工程目录放在这里供老师参考)
- 图片文件夹(包含程序截图、硬件连接图例、照片等)
对应于本实现中的目录名为:
- report
- project
- pic
除此以外,可能还会包含参考资料文件夹等,不过个人觉得以上三个文件夹是通常而言必不可少的基本目录结构。
目录管理需要实现的功能
作为一个实用的作业目录管理工具,需要实现以下功能:
- 创建基本目录结构(创建如上所述的基本目录结构,避免一个个手动建文件夹)
- 发布用于交作业的压缩包(包含必要的运行前检查,如报告是否存在;以及自动按正确命名格式生成压缩包)
- 清除发布与备份(将已存在的压缩包转为备份格式,并清除所有中间文件)
- 导出作业目录(将作业目录整个复制到可移动存储介质上,例如用于放到学校机房电脑上完成作业)
- 导入作业目录(将作业目录从可移动存储介质上导入或更新到本地,例如用于在机房完成作业后迁移或本地磁盘的过程)
对应于本实现中的功能名为:
- make
- dist
- clean-dist
- export
- import
本实现中所需要的外部属性
为增强目录管理功能的通用性,本实现中将基本功能放到根构建文件中,而把需要定制的部分放到其它构建文件,并在那里调用根构建文件。根构建文件需要一些外部属性(property)以实现其可定制性。
aid
:根目录名,必需的外部属性report.name
:报告名(包含扩展名),管理工具需要知道报告名以在发布前检查报告文件夹中是否存在以此命名的文件;在做发布、清除发布 操作时需要用到dist.arc.name
:交作业压缩包名,需要以 .zip 为扩展名,因为本实现使用 zip 格式进行压缩;在做发布、清除发布 操作时需要用到ext.dir
:可移动存储介质路径,在做导入、导出 操作时需要用到
这些外部属性可以在构建文件中调用根构建文件前通过 property
任务指定,也可在命令行中通过 -D
选项指定。例如:
ant -Daid=2 make
创建了一个根目录名为“2”的作业目录(如果重名文件夹不存在),此处指定外部属性 aid
值为“2”。
根构建文件使用方法
<ant antfile="${root-buildfile}" target="${target-to-invoke}" />
调用了所处路径为 ${root-buildfile}
的根构建文件中名为 ${target-to-invoke}
目标。
运行效果
假设根构建文件的全路径被 root-buildfile
属性指定,假设所有被管理的作业文件夹所处的目录(被称作“作业库”)中除它们自己外有且只有可定制的构建文件 build.xml
。在此处以根目录名为“3”的作业文件夹为例。
作业库初始状态
创建基本目录
ant -Daid=3 make
这是作业库的样子。
这是作业目录“3”内部的样子。
完成作业
模拟了插入截图和写完报告的过程,源代码部分未模拟。
这是pic
目录中的内容。
这是report
目录中的内容。
发布
ant -Daid=3 dist
这是发布后作业目录“3”内部的样子,其中dist
目录是临时文件夹,包含压缩包中的内容,如果不删除,那么修改后再次发布时用时会更短一些。压缩包的名字需要在定制的构建文件中指定。
这是压缩包内部的样子,因为没有往project
(代码文件夹)中放东西,所以没有将其包含在压缩包内。如果在发布时没有往report
(报告文件夹)中放入名为“Lab3 Report.docx”的报告(此名称需要在定制的构建文件中指定),那么会报错“report not found”。
这是压缩包中pic
文件夹内部的样子。
清除发布
ant -Daid=3 clean-dist
这是作业目录“3”内部的样子,dist
文件夹被删除了,而且压缩包被加了bak
后缀转成了备份文件的形式,该文件会在下一次发布前删除。
导入 和导出 功能就不展示了,没什么直观效果。
附录
附录中记录了构建文件代码,使用的是 Apache Ant 1.9.7。
根构建文件
<project name="assignment-mgmt">
<!-- External property: name="aid", type="int" required="true" -->
<!-- External property: name="report.name" type="str" required="true for target dist" -->
<!-- External property: name="dist.arc.name" type="str" required="true for target dist" -->
<!-- External property: name="ext.dir" type="location" required="true for target export, import" -->
<description>
Simplify the assignment file management.
The directory template for each assignment directory is such:
root directory
| report [DIRECTORY]
| project [DIRECTORY]
| pic [DIRECTORY]
| dist [DIRECTORY]
| ${dist.arc.name} [FILE]
where
report - directory for reports
project - directory for source codes
pic - directory for snapshots, photos, etc
dist - temporary directory for holding submission files;
appear only after target "dist"
${dist.arc.name} - an archive file for submission
The "dist" folder as well as the final archive contain the
following contents:
* project directory unless it's empty
* pic directory unless it's empty
* the report file specified in external properties under the report
directory
External properties required:
aid - assignment_id, required by all targets
report.name - report name, required by target "dist"
dist.arc.name - the name of zip archive, required by target
"dist"
ext.dir - external directory, esp portable device, to temporarily
accommodate the assignment directory, required by
target "import" and "export"
</description>
<!-- main directory names -->
<property name="report.dir.name" value="report" />
<property name="proj.dir.name" value="project" />
<property name="pic.dir.name" value="pic" />
<!-- directory locations -->
<property name="dist.dir" location="${aid}/dist" /><!-- temporary -->
<property name="report.dir" location="${aid}/${report.dir.name}" />
<property name="proj.dir" location="${aid}/${proj.dir.name}" />
<property name="pic.dir" location="${aid}/${pic.dir.name}" />
<!--
Public targets
-->
<target name="make" depends="-extvar-check" description="make "${aid}" assignment directories">
<mkdir dir="${aid}" />
<mkdir dir="${report.dir}" />
<mkdir dir="${proj.dir}" />
<mkdir dir="${pic.dir}" />
</target>
<target name="dist" depends="-extvar-check-dist,-check-report-availability" description="distribute the assignment under directory "${aid}" before removing the backup version of previous build if any">
<delete file="${aid}/${dist.arc.name}.bak" failonerror="false" />
<mkdir dir="${dist.dir}" />
<copy todir="${dist.dir}" includeemptydirs="false">
<fileset dir="${report.dir}">
<include name="${report.name}" />
</fileset>
<fileset dir="${aid}">
<include name="${pic.dir.name}/**" />
<include name="${proj.dir.name}/**" />
</fileset>
</copy>
<zip destfile="${aid}/${dist.arc.name}" basedir="${dist.dir}" update="true" />
</target>
<target name="clean-dist" depends="-extvar-check-dist" description="clean distribution of assignment under directory "${aid}" but leave a backup version of original distributed archive intact">
<delete dir="${dist.dir}" />
<move file="${aid}/${dist.arc.name}" tofile="${aid}/${dist.arc.name}.bak" />
</target>
<target name="export" depends="-extvar-check-export" description="export assignment directory "${aid}" to directory specified by property "ext.dir"">
<mkdir dir="${ext.dir}/${aid}" />
<copy todir="${ext.dir}/${aid}">
<fileset dir="${aid}" />
</copy>
</target>
<target name="import" depends="-extvar-check-import" description="import/update assignment directory "${aid}" from directory "${ext.dir}/${aid}" specified by property "ext.dir"">
<copy todir="${aid}">
<fileset dir="${ext.dir}/${aid}" />
</copy>
</target>
<!--
Auxilliary targets
-->
<!-- ensure all basic external properties necessary are available, and fail the build otherwise -->
<target name="-extvar-check">
<!-- condition on external property "aid" -->
<condition property="aid.available">
<isset property="aid" />
</condition>
<fail unless="aid.available">external property "aid" required yet not found</fail>
</target>
<!-- ensure all external properties required by target "dist" are available, and fail the build otherwise -->
<target name="-extvar-check-dist" depends="-extvar-check">
<!-- condition on external property "report.name" -->
<condition property="report.name.available">
<isset property="report.name" />
</condition>
<fail unless="report.name.available">external property "report.name" required yet not found</fail>
<!-- condition on external property "dist.arc.name" -->
<condition property="dist.arc.name.available">
<isset property="dist.arc.name" />
</condition>
<fail unless="dist.arc.name.available">external property "dist.arc.name" required yet not found</fail>
</target>
<!-- ensure report file is available before distribution -->
<target name="-check-report-availability">
<!-- condition on the availability of the report file -->
<available property="report.available" file="${report.dir}/${report.name}" type="file" />
<fail unless="report.available">report "${report.name}" not found under "${report.dir}"</fail>
</target>
<!-- ensure all external properties required by target "import" and "export" are available, and fail the build otherwise -->
<target name="-extvar-check-import-export" depends="-extvar-check">
<!-- condition on external property "ext.dir" -->
<condition property="ext.dir.available">
<isset property="ext.dir" />
</condition>
<fail unless="ext.dir.available">external property "ext.dir" required yet not found</fail>
</target>
<!-- ensure all external properties required by target "import" are available and valid, and fail the build otherwise -->
<target name="-extvar-check-import" depends="-extvar-check-import-export">
<!-- condition on the existence of import source directory -->
<available property="import.srcdir.available" file="${ext.dir}/${aid}" type="dir" />
<fail unless="import.srcdir.available">the directory "${ext.dir}/${aid}" from which to import the assignment cannot be found</fail>
</target>
<!-- ensure all external properties required by target "export" are available and valid, and fail the build otherwise -->
<target name="-extvar-check-export" depends="-extvar-check-import-export">
<!-- condition on the existence of export target directory -->
<available property="export.destdir.available" file="${ext.dir}" type="dir" />
<fail unless="export.destdir.available">the directory "${ext.dir}" to which to export the assignment cannot be found</fail>
</target>
</project>
定制的构建文件示例
对应于正文中的运行效果范例。
<project name="cloud-computing-assignment-mgmt" basedir=".">
<description>
Simplify the assignment file management.
The directory template for each assignment directory is such:
root directory
| report [DIRECTORY]
| project [DIRECTORY]
| pic [DIRECTORY]
| dist [DIRECTORY]
| ${dist.arc.name} [FILE]
where
report - directory for reports
project - directory for source codes
pic - directory for snapshots, photos, etc
dist - temporary directory for holding submission files;
appear only after target "dist"
${dist.arc.name} - an archive file for submission
The "dist" folder as well as the final archive contain the
following contents:
* project directory unless it's empty
* pic directory unless it's empty
* the report file specified in external properties under the report
directory
External properties required:
aid - assignment_id, required by ALL targets
ext.dir - external directory, esp portable device, to temporarily
accommodate the assignment directory, required by
target "import" and "export"
</description>
<property name="root_antfile" location="../../../build.xml" />
<!-- 此处指定了压缩包名和报告名 -->
<property name="dist.arc.name" value="Lab${aid}.zip" />
<property name="report.name" value="Lab${aid} Report.docx" />
<!-- 如果在调用了根构建文件的相应任务后还有其它定制任务需要完成,直接加在<ant>调用之后即可 -->
<target name="make" description="make "${aid}" assignment directories">
<ant antfile="${root_antfile}" target="make" />
<!-- 这里写其它定制操作(虽然本例中不需要),下同 -->
</target>
<target name="dist" description="distribute the assignment under directory "${aid}" before removing the backup version of previous build if any">
<ant antfile="${root_antfile}" target="dist" />
</target>
<target name="clean-dist" description="clean distribution of assignment under directory "${aid}" but leave a backup version of original distributed archive intact">
<ant antfile="${root_antfile}" target="clean-dist" />
</target>
<target name="export" description="export assignment directory "${aid}" to directory specified by property "ext.dir"">
<ant antfile="${root_antfile}" target="export" />
</target>
<target name="import" description="import/update assignment directory "${aid}" from directory "${ext.dir}/${aid}" specified by property "ext.dir"">
<ant antfile="${root_antfile}" target="import" />
</target>
</project>