Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 68 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
68
Dung lượng
3,44 MB
Nội dung
LISTENERS AND LOGGERS 511 } for (Enumeration e = fileProperties.keys(); e.hasMoreElements();) { String key = (String) e.nextElement(); String value = fileProperties.getProperty(key); properties.put(key, project.replaceProperties(value)); } boolean success = (event.getException() == null); String prefix = success ? "success" : "failure"; try { boolean notify = Project.toBoolean(getValue(properties, prefix + ".notify", "on")); if (!notify) { return; } String mailhost = getValue(properties, "mailhost", "localhost"); String from = getValue(properties, "from", null); String toList = getValue(properties, prefix + ".to", null); String subject = getValue(properties, prefix + ".subject", (success) ? "Build Success" : "Build Failure"); sendMail(mailhost, from, toList, subject, buffer.toString()); } catch (Exception e) { System.out.println("MailLogger failed to send e-mail!"); e.printStackTrace(System.err); } } protected void log(String message) { buffer.append(message).append(StringUtils.LINE_SEP); } private String getValue(Hashtable properties, String name, String defaultValue) throws Exception { String propertyName = "MailLogger." + name; String value = (String) properties.get(propertyName); if (value == null) { value = defaultValue; } if (value == null) { Should we send email? Substitutes Ant properties Overrides DefaultLogger.log, capture messages Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 512 CHAPTER 20 EXTENDING ANT FURTHER throw new Exception("Missing required parameter: " + propertyName); } return value; } private void sendMail (String mailhost, String from, String toList, String subject, String message) throws IOException { MailMessage mailMessage = new MailMessage(mailhost); mailMessage.from(from); StringTokenizer t = new StringTokenizer(toList, ", ", false); while (t.hasMoreTokens()) { mailMessage.to(t.nextToken()); } mailMessage.setSubject(subject); PrintStream ps = mailMessage.getPrintStream(); ps.println(message); mailMessage.sendAndClose(); } } It is important to note that the buildFinished method initially delegates to DefaultLogger’s implementation of buildFinished. This is necessary so that the final output is generated to the console or log file before sending the email. There are several configurable parameters that are needed to create a robust email logger: from email address, to email address(es), and subject. Beyond those parameters, we will allow the ability to enable/disable failure and success messages separately, have different email address lists for failure and success emails, as well as have different subjects based on the success or failure of a build. Parameters are configurable through a properties file and through Ant properties, with Ant properties taking precedence and overriding those specified in the properties file. This order of precedence allows common settings to be used across multiple projects, but also allows settings to be controlled on a per-project or per-user basis. We can use the special project property MailLogger.properties.file (Ant calls this one of its magic properties) to define the location of the configuration file, then load it and overlay the project properties. The success status of a build is based on whether the BuildEvent contains an exception or not. Uses Ant’s built-in mailer Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com LISTENERS AND LOGGERS 513 20.2.4 Using the MailLogger To use the MailLogger, which, again, is already part of Ant since version 1.5, we must provide the necessary configuration parameters. We recommend using an external properties file. This allows multiple projects to share the settings, which can be overrid- den on a per-project basis simply by overriding the properties using <property> or any other property setting technique. Our maillogger.properties file contains: MailLogger.from = erik@example.org MailLogger.failure.to = erik@example.org MailLogger.mailhost = localhost MailLogger.success.to = erik@example.org MailLogger.success.subject = ${ant.project.name} - Build success MailLogger.failure.subject = FAILURE - ${ant.project.name} MailLogger.success.notify = off Notice how we use Ant properties within this configuration file. We use the built-in ${ant.project.name} property to insert the project name into the subject of the emails sent, allowing us to easily identify which project is being reported at a quick glance. Our example build file to demonstrate the MailLogger is: <project name="MailLogger example" default="test"> <target name="test"> <echo message="hello out there"/> </target> <target name="fail"><fail/></target> </project> From the command line, we specify the configuration file and invoke the fail target: > ant -f buildmail.xml -logger org.apache.tools.ant.listener.MailLogger -DMailLogger.properties.file=maillogger.properties fail Buildfile: buildmail.xml fail: BUILD FAILED C:\AntBook\Sections\Extending\listeners\buildmail.xml:7: No message Total time: 1 second Because we have MailLogger.success.notify set to off, we only receive build failure emails. Setting ANT_ARGS with the appropriate -logger and -DMail- Logger.properties.file settings allows us to invoke Ant simply as ant -f buildmail.xml fail. See appendix A for details on using ANT_ARGS. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 514 CHAPTER 20 EXTENDING ANT FURTHER 20.3 DEVELOPING A CUSTOM MAPPER Several Ant tasks support the <mapper> datatype, allowing file names to be mapped to one or more corresponding files. Section 3.10 discusses the built-in mappers in detail. In almost all cases, the provided mappers are sufficient, but you may find a need to write a custom one. In fact, we found such a need during the writing of this book, and we will use it as an example. We wanted to speed up our builds that incorporated unit tests, but the <junit> task simply reruns all tests each time it is encountered. By using <uptodate> to compare timestamps on the unit test results with the actual Java source files, we are able to bypass testing if they have already been run. The problem encountered was that the Java source files are in a directory structure based on package names, while the unit test results are written to a flat directory structure with the dotted package name used in the XML file name. Section 4.8 provides more details on this short-circuiting technique. We developed the package mapper to solve this problem (which is now part of Ant, as of version 1.5), shown in listing 20.4. public class PackageNameMapper extends GlobPatternMapper { /** * Returns the part of the given string that matches the * in the * "from" pattern replacing file separators with dots * *@param name Source filename *@return Replaced variable part */ protected String extractVariablePart(String name) { String var = name.substring(prefixLength, name.length() - postfixLength); return var.replace(File.separatorChar, '.'); } } A custom mapper must implement the org.apache.tools.ant.util.FileName- Mapper interface, which glob mapper class does. We subclass the GlobPattern- Mapper to inherit the asterisk (*) pattern-matching capability. By overriding its extractVariablePart method, all that was needed was to replace file separators with dots. The FileNameMapper interface’s primary method has this signature: String[] mapFileName(String sourceFileName) In our case, the GlobPatternMapper implements this, but you may wish to implement FileNameMapper directly, providing an array of files that translate from the source file name. To use a custom mapper in a build file, simply specify a class- name and optionally a classpath, classpathref, or a nested <classpath> element to the <mapper>: Listing 20.4 The package mapper implementation Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CREATING CUSTOM SELECTORS 515 <uptodate property="is.uptodate"> <srcfiles dir="${some.dir}"/> <mapper classname="org.example.antbook.MyCustomMapper" classpathref="mapper.classpath" from="*Test.java" to="${test.data.dir}/TEST-*Test.xml"/> </uptodate> Because our example mapper is now part of the Ant distribution, you can simply refer to it by name: <uptodate property="is.uptodate"> <srcfiles dir="${some.dir}"/> <mapper type="package" from="*Test.java" to="${test.data.dir}/TEST-*Test.xml"/> </uptodate> 20.4 CREATING CUSTOM SELECTORS One of Ant’s strengths is its ability to represent domain-specific needs at a high level, such as that provided by filesets, which represent a collection of files rooted from a base directory. Patternsets provide the ability to include or exclude files based on file name patterns, and the built-in selectors provide even more selection capability, such as selecting only files that contain a certain string or were modified after a certain date. Section 3.6 covers the built-in selectors in more detail. Our goal here is to write a custom selector that implements something new: selecting files that are read-only. Our ReadOnlySelector code is quite short and sweet, as shown in listing 20.5. package org.example.antbook; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.selectors.BaseExtendSelector; import java.io.File; public class ReadOnlySelector extends BaseExtendSelector { public boolean isSelected(File basedir, String filename, File file) throws BuildException { return (!file.canWrite()); } } Because Ant’s documentation already provides extensive coverage of writing custom selectors, we will not cover it in detail here. The main things to do are extending BaseExtendSelector and implementing the isSelected method. Custom selectors can also take parameters using nested <param> tags. For example, we could have written our selector to be a generic file attribute selector and allow a nested <param name="attribute" value="readonly"/> (or value="writable" ) . Again, the Ant documentation covers this in detail, so we refer you there. Listing 20.5 ReadOnlySelector includes only files that are not writable Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 516 CHAPTER 20 EXTENDING ANT FURTHER 20.4.1 Using a custom selector in a build The build file in listing 20.6 compiles and tests our custom selector. <project name="selectors" default="main"> <property name="build.dir" location="build"/> <property name="temp.dir" location="${build.dir}/temp"/> <property name="src.dir" location="src"/> <property name="data.dir" location="data"/> <target name="init"> <mkdir dir="${build.dir}"/> <condition property="is.windows"> <os family="windows"/> </condition> </target> <target name="clean"> <delete dir="${build.dir}"/> </target> <target name="compile" depends="init"> <javac srcdir="${src.dir}" destdir="${build.dir}"/> </target> <target name="setup-test-init"> <delete dir="${temp.dir}"/> <mkdir dir="${temp.dir}"/> <delete dir="${data.dir}"/> <mkdir dir="${data.dir}"/> <echo file="${data.dir}/writable.dat">writable</echo> <echo file="${data.dir}/nonwritable.dat">nonwritable</echo> </target> <target name="setup-test-windows" if="is.windows"> <exec executable="cmd.exe"> <arg line="/c attrib +R"/> <arg file="${data.dir}/nonwritable.dat"/> </exec> <exec executable="cmd.exe"> <arg line="/c attrib -R"/> <arg file="${data.dir}/writable.dat"/> </exec> </target> <target name="setup-test" depends="setup-test-init,setup-test-windows"> <chmod file="${data.dir}/nonwritable.dat" perm="u-r"/> <chmod file="${data.dir}/writable.dat" perm="u+r"/> </target> Listing 20.6 Using a custom selector, and demonstrating cross-platform testing of file attribute settings Sets a flag for Windows platforms Creates two test files Sets the attributes on Windows platforms Sets the attributes on non-Windows platforms Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com IMPLEMENTING A CUSTOM FILTER 517 <target name="test" depends="compile,setup-test"> <selector id="selector"> <custom classname="org.example.antbook.ReadOnlySelector" classpath="${build.dir}"/> </selector> <copy todir="${temp.dir}"> <fileset dir="${data.dir}"> <selector refid="selector"/> </fileset> </copy> <available file="${temp.dir}/writable.dat" property="test.failed"/> <fail if="test.failed"> Failed! Writable file copied! </fail> <echo>Test passed</echo> </target> <target name="main" depends="test"/> </project> This build file is overly elaborate to demonstrate how we were able to test our custom read-only file selector. It creates two files and sets one as writable, and one as nonwrit- able using attrib on Windows platforms through <exec>. The <chmod> task is executed on all platforms, but does nothing on Windows platforms because the chmod tool is not natively supported. We then construct a fileset which encompasses both files, but uses our custom selector to only pick read-only files. An <avail- able> check, followed by a conditional <fail> ensures that we have not copied the writable file. Using the <not> selector container, the logic could be reversed to copy only writ- able files instead. This eliminates the need for us to write two selectors or parameterize this selector to be more generic. 20.5 IMPLEMENTING A CUSTOM FILTER In section 3.9, we covered the FilterChain and its nested FilterReaders, which can be used in a few of Ant’s tasks. You are not limited to just the built-in FilterReaders, and can write your own if you have a need that is not fulfilled by the handful of built-in ones. The problem we will solve with an example FilterReader is the use of a proper- ties file to customize an XML file for deployment. First, our templated XML data file: <root> <description>${description}</description> </root> Defines reusable selector Uses custom defined selector to copy read-only file Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 518 CHAPTER 20 EXTENDING ANT FURTHER We are going to use an <expandproperties> FilterReader in a <copy> to replace ${description}. We are going to read the description property from a properties file, which might contain characters that are illegal in an XML file. Our server.prop- erties file contains: description=<some description> If literally “<some description>” was substituted into ${description} in the XML file, the resultant file would be invalid. Angle brackets are special characters in XML files, and must be escaped in most cases (see appendix B for more on special characters in XML). The <loadproperties> task is similar to <property>, except that it allows for a nested <filterchain>. There is no built-in FilterReader to do the proper escaping, so we will write one, and use it in this manner: <loadproperties srcfile="${data.dir}/server.properties"> <filterchain> <filterreader classname="org.example.antbook.EscapeFilter" classpath="${build.dir}" /> </filterchain> </loadproperties> <echo>description=${description}</echo> <copy tofile="${build.dir}/server.xml" file="${data.dir}/template.xml" overwrite="true"> <filterchain> <expandproperties/> </filterchain> </copy> <xmlvalidate file="${build.dir}/server.xml" lenient="true"/> This build file piece is in a target that depends on the compilation target, so that EscapeFilter can be used in the same build file in which it is compiled. The out- put produced is: [echo] description=<some description> [copy] Copying 1 file to C:\AntBook\Sections\Extending\filters\build [xmlvalidate] 1 file(s) have been successfully validated. The description property loaded is different than the value from the properties file. The angle brackets have been replaced with their corresponding XML entity refer- ences. Had we omitted the <filterchain> within <loadproperties>, the XML validation would have failed. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com IMPLEMENTING A CUSTOM FILTER 519 20.5.1 Coding a custom filter reader Listing 20.7 shows our relatively straightforward EscapeFilter implementation. package org.example.antbook; import org.apache.tools.ant.filters.BaseFilterReader; import org.apache.tools.ant.filters.ChainableReader; import java.io.Reader; import java.io.IOException; public class EscapeFilter extends BaseFilterReader implements ChainableReader { private String queuedData = null; public EscapeFilter(final Reader in) { super(in); } public Reader chain(Reader rdr) { EscapeFilter newFilter = new EscapeFilter(rdr); newFilter.setProject(getProject()); return newFilter; } public int read() throws IOException { if (queuedData != null && queuedData.length() == 0) { queuedData = null; } int ch = -1; if (queuedData != null) { ch = queuedData.charAt(0); queuedData = queuedData.substring(1); if (queuedData.length() == 0) { queuedData = null; } } else { ch = in.read(); if (ch == -1) { return ch; } queuedData = getEscapeString(ch); if (queuedData != null) { return read(); } } return ch; } private String getEscapeString(int ch) { String output = null; Listing 20.7 EscapeFilter—a custom filter reader implementation Allows ourselves to chain Pulls one character at a time from the queue End of data Starts reading from the queue Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 520 CHAPTER 20 EXTENDING ANT FURTHER switch (ch) { case '<' : output = "<"; break; case '>' : output = ">"; break; case '"' : output = """; break; case '\'' : output = "'"; break; } if (output != null) { return output; } if (ch < 32 || ch > 127) { return "&#x" + Integer.toHexString(ch) + ";"; } return null; } } FilterReaders use the standard java.io.Reader, which is implicitly available as the in member variable from the parent class BaseFilterReader. If we had wanted our class to be configurable through the build file, we would have had to extend from BaseParamFilterReader instead. The chain method comes from the Chain- ableReader interface, and allows our FilterReader to be linked to a successive Fil- terReader, passing the modified stream through to it. The read method can be a bit complicated, and care must be taken to return -1 when in.read() returns it, otherwise an infinite loop can occur as we experienced in our first iteration of EscapeFilter. The read method is initially called from the Ant framework, but we also call it internally when escaped strings are queued. Each call of read returns only a single character (as an int), so buffering is necessary when text is added, as is the case in EscapeFilter. We found that writing a FilterReader was a bit trickier than other Ant customiza- tions, but was well worth the effort. Had we not had custom filter reader capability in this situation, we probably would have opted to change our business process by mandating that data be already encoded for XML inclusion within the properties file. However, we may want to use that same data outside of XML for other purposes and the situation would have gotten more complex. Luckily, filter readers saved the day by allowing us to have the data cleanly in the properties file, and escape the characters when needed. 20.6 SUMMARY This chapter has covered several odds and ends with respect to Ant extensibility. While these techniques are not normally needed in the majority of builds, they are each quite powerful and handy when the situations arise for their use. Scripting using any of the Bean Scripting Framework supported languages allows ad-hoc task writing within an Ant build file, without the need to write, compile, and Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... launching Ant through the wrapper scripts, such as the provided ant. bat, ant. sh, runant.pl, or runant.py 4 Run without a CLASSPATH There is no need to manually set your system CLASSPATH environment variable When running through the Ant wrapper scripts, the libraries in ANT_ HOME/lib are automatically placed into the system CLASSPATH before invoking Ant 5 Place commonly used Ant library dependencies in Ant s... export JAVA_ HOME= (wherever the JDK is installed) export ANT_ HOME= (wherever Ant is installed) export PATH=$PATH: $ANT_ HOME/bin: $JAVA_ HOME/bin The environment settings for tcsh have a different syntax but the same behavior, and go into the equivalent file: cshrc or tcshrc setenv JAVA_ HOME= (wherever the JDK is installed) setenv ANT_ HOME= (wherever Ant is installed) setenv PATH=$PATH\: $ANT_ HOME/bin\: $JAVA_ HOME/bin... important if you are planning to have an automated build process later on; whatever account the automated build runs under it needs to have a copy of Ant [Apps]$ pwd /home/slo /Java/ Apps [Apps]$ ls jakarta -ant- 1.5-bin.tar.gz [Apps]$ ls jakarta -ant- 1.5-bin.tar.gz [Apps]$ tar xzf jakarta -ant- 1.5-bin.tar.gz [Apps]$ ls jakarta -ant- 1.5 jakarta -ant- 1.5-bin.tar [Apps]$ cd jakarta -ant- 1.5/bin [bin]$ /ant -version... system properties and memory configuration During the development of this book, the index we built was over 20MB in size and crashed the Ant JVM We solved this by setting ANT_ OPTS to increase the Java initial heap size On Windows this is SET ANT_ OPTS=-Xmx500M A.5.2 ANT_ ARGS In a similar fashion to ANT_ OPTS, the ANT_ ARGS environment variable is passed to Ant s main process as command-line arguments, in addition... either Java is not installed or the path is wrong Fix: Install the JDK; set up JAVA_ HOME to point to the install location INSTALLATION TROUBLESHOOTING 527 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Problem: JDK not installed/configured Ant needs to find the JDK so that it can use classes in tools.jar, such as the Java compiler Without this, some Ant tasks will fail with. .. environment variable JAVA_ HOME is used to find the JDK—if it is not set, Ant will warn you on startup with an error message: Warning: JAVA_ HOME environment variable is not set This may just be a warning, but it is a warning that some tasks will not work properly More insidiously, if JAVA_ HOME is wrong, Ant will not notice until some tasks fail, usually and Test 1: Run javac from the command... the underlying problem Problem: Calling Ant generates a Java usage message If the Java invocation string that the Ant launcher scripts is somehow corrupt, then the java program will not be able to parse it, so it will print a message beginning Usage: java [-options] class [args ] This is usually caused by one of the environment variables, JAVA_ HOME, ANT_ HOME, ANT_ OPTS, or CLASSPATH being invalid Test:... for tasks such as 538 A PP E ND I X C IDE INTEGRATION Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com The final nice feature of jEdit for Ant- based development is that although it ships with a built-in version of Ant, you can select any other installation of Ant on the command line through a dialog box, which is ideal when you are extending or editing Ant itself Unfortunately,... Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com A.5 INSTALLATION CONFIGURATION There are two useful environment variables that the Ant wrapper scripts use when invoking Ant: ANT_ OPTS and ANT_ ARGS Neither of these is typically set by users, but each can provide value for certain situations A.5.1 ANT_ OPTS The ANT_ OPTS environment variable provides options to the JVM executing Ant, ... an older version of Ant Second, the installation may be incomplete; dependent libraries or even dependent batch files, such as lcp.bat, which ant. bat uses, may be missing Test: One trick is to have a build file that contains a target with the string to see the Ant home directory Another is to search for all copies of ant. bat, ant. jar or just plain ant in the file system, . installed) setenv ANT_ HOME= (wherever Ant is installed) setenv PATH=$PATH: $ANT_ HOME/bin: $JAVA_ HOME/bin There is a place where Ant options (such as ANT_ OPTS) can be set in Unix, the .antrc file in. external integration with Ant, and custom-writing them is easy. Ant comes with several listeners and loggers already, which are detailed in Ant s documentation. Familiarize yourself with these before embarking. IMPORTANT Installing the Java SDK on a path without spaces in it, such as c: java jdk, instead of a path such as c:Program Files Java is highly recommended, as sometimes spaces confuse Ant and