在阅读有关编写任务 [1]的教程后,本教程将解释如何获取和设置属性,以及如何使用嵌套文件集和路径。最后,它将解释如何将任务贡献给 Apache Ant。
目标是编写一个任务,该任务在路径中搜索文件并将该文件的路径保存到属性中。
我们可以使用另一个教程中的构建文件并对其进行一些修改。这就是使用属性的优势 - 我们可以重复使用几乎整个脚本。 :-)
<?xml version="1.0" encoding="UTF-8"?> <project name="FindTask" basedir="." default="test"> ... <target name="use.init" description="Taskdef's the Find-Task" depends="jar"> <taskdef name="find" classname="Find" classpath="${ant.project.name}.jar"/> </target> <!-- the other use.* targets are deleted --> ... </project>
构建文件位于存档tutorial-tasks-filesets-properties.zip [2]中的/build.xml.01-propertyaccess(未来版本保存为 *.02...,最终版本保存为 build.xml;源代码也是如此)。
我们的第一步是将属性设置为一个值并打印该属性的值。因此,我们的场景将是
<find property="test" value="test-value"/> <find print="test"/>
好的,它可以用核心任务重写
<property name="test" value="test-value"/> <echo message="${test}"/>
但我必须从已知的基础开始 :-)
那么该怎么做呢?处理三个属性(property、value、print)和一个执行方法。因为这只是一个介绍性示例,所以我不会进行太多检查
import org.apache.tools.ant.BuildException; public class Find extends Task { private String property; private String value; private String print; public void setProperty(String property) { this.property = property; } // setter for value and print public void execute() { if (print != null) { String propValue = getProject().getProperty(print); log(propValue); } else { if (property == null) throw new BuildException("property not set"); if (value == null) throw new BuildException("value not set"); getProject().setNewProperty(property, value); } } }
如另一个教程中所述,属性访问是通过 Project
实例完成的。我们通过公共 getProject()
方法获取此实例,该方法继承自 Task
(更准确地说,继承自 ProjectComponent
)。读取属性是通过 getProperty(propertyname)
完成的(非常简单,不是吗?)。此属性将值作为 String
返回,如果未设置则返回 null
。
设置属性... 并不难,但有多个 setter。您可以使用 setProperty()
方法,该方法将按预期执行。但 Ant 中有一个黄金法则:属性是不可变的。此方法将属性设置为指定的值 - 无论它之前是否有值。因此,我们使用另一种方法。 setNewProperty()
仅在没有同名属性时才设置属性。否则,将记录一条消息。
(顺便说一下,简要解释一下 Ant 的“命名空间” - 不要与 XML 命名空间混淆:<antcall>
为属性名称创建一个新的空间。所有来自调用者的属性都传递给被调用者,但被调用者可以在没有调用者注意的情况下设置自己的属性。)
还有一些其他 setter(但我没有使用它们,所以对此我无话可说,抱歉 :-)
将上面的两行示例放入名为 use.simple
的目标后,我们可以从测试用例中调用它
import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.apache.tools.ant.BuildFileRule; public class FindTest { @Rule public final BuildFileRule buildRule = new BuildFileRule(); @Before public void setUp() { configureProject("build.xml"); } @Test public void testSimple() { buildRule.executeTarget("useSimple"); Assert.assertEquals("test-value", buildRule.getLog()); } }
一切正常。
Ant 提供了一种捆绑文件的通用方法:文件集。因为您正在阅读本教程,我认为您了解它们,我不必花更多时间解释它们在构建文件中的用法。我们的目标是在路径中搜索文件。在此步骤中,路径只是一个文件集(或更准确地说:文件集的集合)。因此,我们的用法将是
<find file="ant.jar" location="location.ant-jar"> <fileset dir="${ant.home}" includes="**/*.jar"/> </find>
我们需要什么?一个具有两个属性(file、location)和嵌套文件集的任务。因为我们已经在上面的示例中解释了属性处理,并且在另一个教程中描述了嵌套元素的处理,所以代码应该非常容易
public class Find extends Task { private String file; private String location; private List<FileSet> filesets = new ArrayList<>(); public void setFile(String file) { this.file = file; } public void setLocation(String location) { this.location = location; } public void addFileset(FileSet fileset) { filesets.add(fileset); } public void execute() { } }
好的 - 该任务不会做太多事情,但我们可以在没有错误的情况下以描述的方式使用它。在下一步中,我们必须实现 execute 方法。在此之前,我们将实现相应的测试用例(TDD - 测试驱动开发)。
在另一个教程中,我们重复使用了构建文件已经编写的目标。现在,我们将通过 Java 代码配置大多数测试用例(有时编写目标比通过 Java 代码编写更容易)。可以测试什么?
也许您会找到更多测试用例。但现在这些就足够了。
对于每个点,我们创建一个 testXX
方法。
public class FindTest { @Rule public final BuildFileRule buildRule = new BuildFileRule(); @Rule public ExpectedException tried = ExpectedException.none(); ... // constructor, setUp as above @Test public void testMissingFile() { tried.expect(BuildException.class); tried.expectMessage("file not set"); Find find = new Find(); find.execute(); } @Test public void testMissingLocation() { tried.expect(BuildException.class); tried.expectMessage("location not set"); Find find = new Find(); find.setFile("ant.jar"); find.execute(); } @Test public void testMissingFileset() { tried.expect(BuildException.class); tried.expectMessage("fileset not set"); Find find = new Find(); find.setFile("ant.jar"); find.setLocation("location.ant-jar"); } @Test public void testFileNotPresent() { buildRule.executeTarget("testFileNotPresent"); String result = buildRule.getProject().getProperty("location.ant-jar"); assertNull("Property set to wrong value.", result); } @Test public void testFilePresent() { buildRule.executeTarget("testFilePresent"); String result = buildRule.getProject().getProperty("location.ant-jar"); assertNotNull("Property not set.", result); assertTrue("Wrong file found.", result.endsWith("ant.jar")); } }
如果我们运行此测试类,所有测试用例(除了 testFileNotPresent
)都会失败。现在,我们可以实现我们的任务,以便这些测试用例通过。
protected void validate() { if (file == null) throw new BuildException("file not set"); if (location == null) throw new BuildException("location not set"); if (filesets.size() < 1) throw new BuildException("fileset not set"); } public void execute() { validate(); // 1 String foundLocation = null; for (FileSet fs : filesets) { // 2 DirectoryScanner ds = fs.getDirectoryScanner(getProject()); // 3 for (String includedFile : ds.getIncludedFiles()) { String filename = includedFile.replace('\\','/'); // 4 filename = filename.substring(filename.lastIndexOf("/") + 1); if (foundLocation == null && file.equals(filename)) { File base = ds.getBasedir(); // 5 File found = new File(base, includedFile); foundLocation = found.getAbsolutePath(); } } } if (foundLocation != null) // 6 getProject().setNewProperty(location, foundLocation); }
在 //1 上,我们检查任务的前提条件。在 validate()
方法中执行此操作是一种常见方法,因为我们将前提条件与实际工作分开。在 //2 上,我们遍历所有嵌套文件集。如果我们不想处理多个文件集,则 addFileset()
方法必须拒绝进一步的调用。我们可以通过其 DirectoryScanner
获取文件集的结果,如 //3 中所示。之后,我们创建文件路径的平台无关字符串表示(//4,当然可以用其他方法完成)。我们必须执行 replace()
,因为我们使用的是简单的字符串比较。Ant 本身是平台无关的,因此可以在使用斜杠 (/
,例如 Linux) 或反斜杠 (\
,例如 Windows) 作为路径分隔符的文件系统上运行。因此,我们必须统一这一点。如果我们找到我们的文件,我们将在 //5 上创建一个绝对路径表示,以便我们可以在不知道 basedir 的情况下使用这些信息。(这在使用多个文件集时非常重要,因为它们可能具有不同的 basedir,并且目录扫描器的返回值相对于其 basedir。)最后,如果我们找到一个文件,我们将文件的路径存储为属性(//6)。
好的,在这个简单的情况下,更容易的做法是将 file 添加为所有文件集的附加 include
元素。但我希望展示如何处理复杂情况而不变得复杂 :-)
测试用例使用 Ant 属性 ant.home
作为参考。此属性由启动 Ant 的 Launcher
类设置。我们可以在构建文件中使用它作为内置属性 [3]。但是,如果我们创建一个新的 Ant 环境,我们必须为自己的环境设置该值。我们使用 fork 模式下的 <junit>
任务。因此,我们必须修改构建文件
<target name="junit" description="Runs the unit tests" depends="jar"> <delete dir="${junit.out.dir.xml}"/> <mkdir dir="${junit.out.dir.xml}"/> <junit printsummary="yes" haltonfailure="no"> <classpath refid="classpath.test"/> <sysproperty key="ant.home" value="${ant.home}"/> <formatter type="xml"/> <batchtest fork="yes" todir="${junit.out.dir.xml}"> <fileset dir="${src.dir}" includes="**/*Test.java"/> </batchtest> </junit> </target>
提供对文件集的支持的任务非常方便。但还有另一种捆绑文件的方法:<path>
。如果所有文件都在一个公共基目录下,则文件集很容易。但如果不是这种情况,您就会遇到问题。另一个缺点是它的速度:如果您在一个巨大的目录结构中只有几个文件,为什么不使用 <filelist>
而不是?<path>
以这种方式组合这些数据类型,即路径包含其他路径、文件集、目录集和文件列表。这就是为什么Ant-Contrib [4] <foreach>
任务被修改为支持路径而不是文件集。所以我们也想要这样。
从文件集更改为路径支持非常容易
将 Java 代码从private List<FileSet> filesets = new ArrayList<>(); public void addFileset(FileSet fileset) { filesets.add(fileset); }更改为
private List<Path> paths = new ArrayList<>(); *1 public void addPath(Path path) { *2 paths.add(path); }并将构建文件从
<find file="ant.jar" location="location.ant-jar"> <fileset dir="${ant.home}" includes="**/*.jar"/> </find>更改为
<find file="ant.jar" location="location.ant-jar"> <path> *3 <fileset dir="${ant.home}" includes="**/*.jar"/> </path> </find>
更改为
在 *1 上,我们只重命名列表。这只是为了更好地阅读源代码。在 *2 上,我们必须提供正确的方法:addName(Type t)
。因此,在此处将文件集替换为路径。最后,我们必须修改 *3 上的构建文件,因为我们的任务不再支持嵌套文件集。因此,我们将文件集包装在路径中。
现在我们修改测试用例。哦,没什么好做的 :-) 重命名 testMissingFileset()
(不是必须的,但最好将其命名为它所做的事情)并在该方法中更新 expected-String(现在期望 path not set 消息)。更复杂的测试用例基于构建脚本。因此,目标 testFileNotPresent 和 testFilePresent 必须以上面描述的方式进行修改。
public void execute() { validate(); String foundLocation = null; for (Path path : paths) { // 1 for (String includedFile : path.list()) { // 2 String filename = includedFile.replace('\\','/'); filename = filename.substring(filename.lastIndexOf("/") + 1); if (foundLocation == null && file.equals(filename)) { foundLocation = includedFile; // 3 } } } if (foundLocation != null) getProject().setNewProperty(location, foundLocation); }
测试已完成。现在我们必须调整任务实现。最简单的修改是在 validate()
方法中,我们将最后一行更改为 if (paths.size()<1) throw new BuildException("path not set");
。在 execute()
方法中,我们还有更多工作要做。... 嗯... 实际上工作量更少,因为 Path
类为我们完成了所有 DirectoryScanner
处理和创建绝对路径的工作。因此,execute 方法变为
当然,我们必须在 //1 上遍历路径。在 //2 和 //3 上,我们看到 Path 类为我们完成了工作:没有 DirectoryScanner(在 2 处)并且没有创建绝对路径(在 3 处)。
到目前为止,一切都很好。但是,文件可能在路径中的多个位置吗?- 当然。
获取所有这些文件会好吗?- 这取决于...
在本节中,我们将扩展该任务以支持返回所有文件的列表。Ant 本身不支持将列表作为属性值。因此,我们必须看看其他任务如何使用列表。使用列表的最著名任务是 Ant-Contrib 的 <foreach>
。所有列表元素都连接在一起,并用可自定义的分隔符(默认值为 ,
)分隔。
<find ... delimiter=""/> ... </find>
因此,我们执行以下操作
如果设置了分隔符,我们将以该分隔符返回所有找到的文件作为列表。
返回该列表
因此,我们添加以下测试用例<target name="test.init"> <mkdir dir="test1/dir11/dir111"/> *1 <mkdir dir="test1/dir11/dir112"/> ... <touch file="test1/dir11/dir111/test"/> <touch file="test1/dir11/dir111/not"/> ... <touch file="test1/dir13/dir131/not2"/> <touch file="test1/dir13/dir132/test"/> <touch file="test1/dir13/dir132/not"/> <touch file="test1/dir13/dir132/not2"/> <mkdir dir="test2"/> <copy todir="test2"> *2 <fileset dir="test1"/> </copy> </target> <target name="testMultipleFiles" depends="use.init,test.init"> *3 <find file="test" location="location.test" delimiter=";"> <path> <fileset dir="test1"/> <fileset dir="test2"/> </path> </find> <delete> *4 <fileset dir="test1"/> <fileset dir="test2"/> </delete> </target>在构建文件中
public void testMultipleFiles() { executeTarget("testMultipleFiles"); String result = getProject().getProperty("location.test"); assertNotNull("Property not set.", result); assertTrue("Only one file found.", result.indexOf(";") > -1); }
在测试类中
现在,我们需要一个目录结构,我们可以在其中找到不同目录中具有相同名称的文件。因为我们不能确定是否有一个,所以我们在 *1 和 *2 上创建一个。当然,我们在 *4 上清理它。可以在我们的测试目标中或在单独的目标中创建它,这对于以后的重复使用会更好(*3)。
private List<String> foundFiles = new ArrayList<>(); ... private String delimiter = null; ... public void setDelimiter(String delim) { delimiter = delim; } ... public void execute() { validate(); // find all files for (Path path : paths) { for (File includedFile : path.list()) { String filename = includedFile.replace('\\','/'); filename = filename.substring(filename.lastIndexOf("/")+1); if (file.equals(filename) && !foundFiles.contains(includedFile)) { // 1 foundFiles.add(includedFile); } } } // create the return value (list/single) String rv = null; if (!foundFiles.isEmpty()) { // 2 if (delimiter == null) { // only the first rv = foundFiles.get(0); } else { // create list StringBuilder list = new StringBuilder(); for (String file : foundFiles) { // 3 list.append(it.next()); if (list.length() > 0) list.append(delimiter); // 4 } rv = list.toString(); } } // create the property if (rv != null) getProject().setNewProperty(location, rv); }
任务实现如下修改
该算法执行以下操作:查找所有文件,根据用户的意愿创建返回值,将值作为属性返回。在 //1 上,我们消除了重复项。//2 确保我们仅在找到一个文件时才创建返回值。在 //3 上,我们遍历所有找到的文件,并且 //4 确保最后一个条目没有尾随分隔符。
如果只有任务开发者能够编写构建文件(而且他只有在接下来的几周内才能做到 :-)),那么这个任务就毫无用处。因此,文档也很重要。你用什么形式来做取决于你的喜好。但在 Ant 内部有一个通用格式,使用它有很多好处:所有任务用户都知道这种格式,如果你决定贡献你的任务,就会要求使用这种格式。所以我们将以这种形式来记录我们的任务。
如果你看一下 Java 任务 [5] 的手册页,你会发现它
作为模板,我们有
<!DOCTYPE html> <html lang="en"> <head> <title>Taskname Task</title> </head> <body> <h2 id="taskname">Taskname</h2> <h3>Description</h3> <p>Describe the task.</p> <h3>Parameters</h3> <table class="attr"> <tr> <th scope="col">Attribute</th> <th scope="col">Description</th> <th scope="col">Required</th> </tr> do this html row for each attribute (including inherited attributes) <tr> <td>classname</td> <td>the Java class to execute.</td> <td>Either jar or classname</td> </tr> </table> <h3>Parameters specified as nested elements</h3> Describe each nested element (including inherited) <h4>your nested element</h4> <p>description</p> <p><em>since Ant 1.6</em>.</p> <h3>Examples</h3> <pre> A code sample; don't forget to escape the < of the tags with < </pre> What should that example do? </body> </html>
这是一个关于我们任务的示例文档页面
<!DOCTYPE html> <html lang="en"> <head> <title>Find Task</title> </head> <body> <h2 id="find">Find</h2> <h3>Description</h3> <p>Searches in a given path for a file and returns the absolute to it as property. If delimiter is set this task returns all found locations.</p> <h3>Parameters</h3> <table class="attr"> <tr> <th scope="col">Attribute</th> <th scope="col">Description</th> <th scope="col">Required</th> </tr> <tr> <td>file</td> <td>The name of the file to search.</td> <td>yes</td> </tr> <tr> <td>location</td> <td>The name of the property where to store the location</td> <td>yes</td> </tr> <tr> <td>delimiter</td> <td>A delimiter to use when returning the list</td> <td>only if the list is required</td> </tr> </table> <h3>Parameters specified as nested elements</h3> <h4>path</h4> <p>The path where to search the file.</p> <h3>Examples</h3> <pre> <find file="ant.jar" location="loc"> <path> <fileset dir="${ant.home}"/> <path> </find></pre> Searches in Ant's home directory for a file <samp>ant.jar</samp> and stores its location in property <code>loc</code> (should be <samp>ANT_HOME/bin/ant.jar</samp>). <pre> <find file="ant.jar" location="loc" delimiter=";"> <path> <fileset dir="C:/"/> <path> </find> <echo>ant.jar found in: ${loc}</echo></pre> Searches in Windows C: drive for all <samp>ant.jar</samp> and stores their locations in property <code>loc</code> delimited with <q>;</q>. (should need a long time :-) After that it prints out the result (e.g. <samp>C:/ant-1.5.4/bin/ant.jar;C:/ant-1.6/bin/ant.jar</samp>). </body> </html>
如果我们决定贡献我们的任务,我们应该做一些事情
有关此方面的更多信息,请参阅 Ant 任务指南 [6]。
现在我们将检查该指南中描述的“提交新任务前的清单”。
此任务不依赖于任何外部库。因此,我们可以将其用作核心任务。此任务只包含一个类。因此,我们可以使用核心任务的标准包:org.apache.tools.ant.taskdefs
。实现位于 src/main 目录中,测试位于 src/testcases 目录中,测试的构建文件位于 src/etc/testcases 目录中。
现在我们将我们的工作集成到 Ant 发行版中。因此,首先我们要更新我们的 Git 树。如果还没有完成,你应该在 GitHub[7] 上克隆 Ant 仓库,然后创建一个本地克隆
git clone https://github.com/your-sig/ant.git
现在我们将构建我们的 Ant 发行版并进行测试。这样我们就可以看到我们的机器上是否有任何测试失败。(我们可以在后面的步骤中忽略这些失败的测试;这里使用的是 Windows 语法——如果需要,请将其转换为 UNIX 语法)
ANTREPO> build // 1 ANTREPO> set ANT_HOME=%CD%\dist // 2 ANTREPO> ant test -Dtest.haltonfailure=false // 3
首先,我们必须构建我们的 Ant 发行版(//1)。在 //2 上,我们将 ANT_HOME
环境变量设置为新创建的发行版所在的目录(%CD%
在 Windows 2000 及更高版本上扩展为当前目录)。在 //3 上,我们让 Ant 执行所有测试(这会强制编译所有测试),而不会在第一次失败时停止。
接下来,我们将我们的工作应用到 Ant 源代码上。因为我们没有修改任何内容,所以这是一个相对简单的步骤。(因为我有一个 Ant 的本地 Git 克隆,并且通常会贡献我的工作,所以我从一开始就在本地副本上工作。优点是:如果你修改了现有的源代码,则不需要此步骤,并且可以节省大量工作 :-))。
package org.apache.tools.ant.taskdefs;
testFileNotPresent、
testFilePresent、
test.init和
testMultipleFiles
use.init的依赖项
configureProject("build.xml");
更改为 configureProject("src/etc/testcases/taskdefs/find.xml");
<a href="Tasks/find.html">Find</a><br>
现在我们的修改已经完成,我们将重新测试它
ANTREPO> build ANTREPO> ant run-single-test // 1 -Dtestcase=org.apache.tools.ant.taskdefs.FindTest // 2 -Dtest.haltonfailure=false
因为我们只想测试我们的新类,所以我们使用单个测试的目标,指定要使用的测试,并配置为不在第一次失败时停止——我们希望看到我们自己的测试的所有失败(//1 + 2)。
然后……哦,所有测试都失败了:Ant 找不到任务或此任务依赖的类。
好的:在前面的步骤中,我们告诉 Ant 使用 Find 类来执行 <find>
任务(请记住 use.init
目标中的 <taskdef>
语句)。但现在我们想将该任务作为核心任务引入。没有人想要 taskdef
javac
、echo
……那么该怎么办?答案是 src/main/.../taskdefs/default.properties。这里完成了任务名称和实现类之间的映射。因此,我们将 find=org.apache.tools.ant.taskdefs.Find
添加为最后一个核心任务(就在 # optional tasks
行之前)。现在我们再试一次
ANTREPO> build // 1 ANTREPO> ant run-single-test -Dtestcase=org.apache.tools.ant.taskdefs.FindTest -Dtest.haltonfailure=false
我们必须重新构建(//1)Ant,因为测试会在 %ANT_HOME%\lib\ant.jar(更准确地说:在类路径上)中查找属性文件。而我们只在源路径中修改了它。因此,我们必须重新构建该 jar。但现在所有测试都通过了,我们将检查我们的类是否破坏了其他一些测试。
ANTREPO> ant test -Dtest.haltonfailure=false
因为有很多测试,所以这一步需要一些时间。因此,在开发过程中使用 run-single-test
,并在最后(可能在开发过程中也会偶尔使用)使用 test
。我们在这里使用 -Dtest.haltonfailure=false,因为可能会有其他测试失败,我们需要查看它们。
此测试运行应该向我们展示两件事:我们的测试将运行,并且失败的测试数量与直接在 git clone
之后(没有我们的修改)相同。
只需从 Ant 源代码树中的其他源代码中复制许可证文本。
Ant 1.10 使用 Java 8 进行开发,但 Ant 1.9 也在积极维护。这意味着 Ant 1.9 中存在的 Ant 代码更新必须能够在 JDK 5 上运行。(对于新的任务,只针对 Ant 1.10 及更高版本进行处理是可以的。)因此,我们必须对其进行测试。你可以从 Oracle [8] 下载旧版本的 JDK。
清理 ANT_HOME
变量,删除 build、bootstrap 和 dist 目录,并将 JAVA_HOME
指向 JDK 5 主目录。然后使用你的提交创建补丁,在 Git 中检出 1.9.x 分支,应用你的补丁并执行 build
,设置 ANT_HOME
并运行 ant test(如上所述)。
我们的测试应该通过。
我们必须确保很多事情。缩进使用 4 个空格,这里和那里有空格……(所有这些都在 Ant 任务指南 [6] 中描述,其中包括 Sun 代码风格 [9])。因为有很多事情,所以我们很乐意有一个工具来执行检查。有一个工具:checkstyle。Checkstyle 可在 Sourceforge [10] 上获得,Ant 提供了 check.xml 构建文件,它可以为我们完成这项工作。
下载它并将 checkstyle-*-all.jar 放入你的 %USERPROFILE%\.ant\lib 目录中。存储在那里的所有 jar 都可供 Ant 使用,因此你无需将其添加到你的 %ANT_HOME%\lib 目录中(此功能自 Ant 1.6 起可用)。
因此,我们将使用以下命令运行测试
ANTREPO> ant -f check.xml checkstyle htmlreport
我更喜欢 HTML 报告,因为有很多消息,我们可以更快地浏览。打开 ANTREPO/build/reports/checkstyle/html/index.html 并导航到 Find.java。现在我们看到有一些错误:缺少空格、未使用的导入、缺少 javadoc。因此,我们必须这样做。
提示:从文件的底部开始,这样报告中的行号将保持最新,你将更容易找到下一个错误位置,而无需重新执行 checkstyle。
根据消息清理代码后,我们删除 reports 目录并再次运行 checkstyle。现在我们的任务没有列出。这很好 :-)
最后,我们发布该存档。如 Ant 任务指南 [7] 中所述,我们可以在开发者邮件列表中宣布它,创建一个 BugZilla 条目并打开一个 GitHub 拉取请求。对于两者,我们都需要一些信息
主题 | 简短描述 | 用于在路径中查找文件的任务 |
---|---|---|
正文 | 有关路径的更多详细信息 | 此新任务在嵌套的 <path/> 中查找文件出现的位置,并将所有位置存储为属性。有关详细信息,请参阅附带的手册。 |
拉取请求引用 | GitHub 拉取请求 URL | https://github.com/apache/ant/pull/0 |
发送包含此信息的电子邮件非常容易,我认为我不需要描述它。BugZilla 稍微复杂一些。但优点是条目不会被遗忘(每周都会生成一次报告)。因此,我将描述该过程。
首先,您需要拥有一个 BugZilla 帐户。因此,请打开 BugZilla 主页 [11] 并按照链接 创建新的 Bugzilla 帐户 [12] 中的步骤操作,如果您还没有帐户的话。
现在新的任务已在错误数据库中注册。