Return-Path: Mailing-List: contact ant-dev-help@jakarta.apache.org; run by ezmlm Delivered-To: mailing list ant-dev@jakarta.apache.org Received: (qmail 74933 invoked from network); 30 Aug 2000 14:16:03 -0000 Received: from lukla.sun.com (192.18.98.31) by locus.apache.org with SMTP; 30 Aug 2000 14:16:03 -0000 Received: from gw.netbeans.com ([129.156.76.1]) by lukla.Sun.COM (8.9.3+Sun/8.9.3) with ESMTP id IAA10716 for ; Wed, 30 Aug 2000 08:16:01 -0600 (MDT) Received: from homesrv.netbeans.com (homesrv.netbeans.com [212.24.138.170]) by gw.netbeans.com (8.9.3/8.9.3) with ESMTP id QAA13246 for ; Wed, 30 Aug 2000 16:16:00 +0200 Received: from netbeans.com (IDENT:jglick@karlovo-nb.netbeans.com [212.24.138.185]) by homesrv.netbeans.com (8.9.3/8.9.3) with ESMTP id QAA25327 for ; Wed, 30 Aug 2000 16:16:00 +0200 Sender: Jesse.Glick@netbeans.com Message-ID: <39AD174E.7659E763@netbeans.com> Date: Wed, 30 Aug 2000 16:16:46 +0200 From: Jesse Glick Organization: Sun Microsystems / NetBeans X-Mailer: Mozilla 4.75 [en] (X11; U; Linux 2.2.12-20 i686) X-Accept-Language: en MIME-Version: 1.0 To: Ant List Subject: [PATCH] Assorted fixes and enhancements to and Content-Type: multipart/mixed; boundary="------------4F1056F97045F806F783315F" X-Spam-Rating: locus.apache.org 1.6.2 0/1000/N This is a multi-part message in MIME format. --------------4F1056F97045F806F783315F Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit A couple of weeks ago I sent out a patch for and tasks which had several enhancements and bugfixes, but did not get any firm response and it was not applied. There was some disagreement as to handling of empty archives; so I have created a new patch, which I think resolves this disagreement by grandfathering the current behavior but making it easy to switch; and included several more improvements. Attached is a fresh patch against current CVS sources; noticeable changes from CVS version in no particular order are: 1. Behavior of Zip and Jar when no files match is more carefully regulated. (With current CVS sources, it will silently skip making the archive--not intentionally but due to an improperly swallowed exception.) There is now an optional attribute "whenempty" which is used to control what happens when you try to make an archive with no files. "create" causes it to be created as you asked, though a warning is printed. "skip" causes it to skip trying to make the archive (and a warning is printed). "fail" causes the build to halt with an exception. The default for Zip is "skip" to match current behavior (though personally I would prefer "fail" or "create" as defaults; it would be a one-line change to modify the default behavior). The default for Jar is "create" (since there is always the manifest, so the archive is not exactly empty). In the case of empty ZIP files, java.util.zip.* cannot handle this, so an empty ZIP archive (22 bytes, but zero file entries) is created when "create" is given. 2. Error-handling of both tasks is made more robust; some exceptions were being swallowed before, or some streams might not have been closed reliably, etc. 3. Entries for containing directories are added to the archive whenever files are added, since some tools might fail if these were not present, and for consistency with existing ZIP and JAR creation tools. 4. The default manifest for JARs says they were created by Ant and gives the Ant version number used. 5. JARs are recreated if their manifest is specified and is changed on disk (and of course if any contained files are changed or added too, as for ZIPs). 6. Documentation update for both tasks covering user-visible changes. 7. Both and tasks may take embedded filesets. Thus, for compatibility with current scripts and for simplicity of syntax, there is still an implicit fileset used when you specify basedir (which is now optional if there are embedded filesets). But you may add or elements inside the or and these will also be included in the archive. If multiple filesets are employed, it works more or less like the JAR tool's -C option: each fileset has its own basedir, and any files acquired from that fileset are stored with a relative archive path according to that basedir. This makes it possible to efficiently and in one task include files from multiple disparate directories, while controlling the archive path (critical for e.g. resource paths/packages in JARs). 8. For Jar task, if you have a file META-INF/MANIFEST.MF present in one of your filesets, this was always ignored in deference to the (automatic or supplied) manifest. Now a warning is printed if this happens. Only tested on Linux with Sun JDK 1.3; I tried to not use any > 1.1 APIs but someone should check of course. Including ZIP of informal test set; tries to make ZIPs and JARs in various ways with different options. Just run the build.xml file, target "all" to create; "touchreal" to change timestamp of a content file, then try "all" again; "touchmani" for a manifest file; "clean" to delete archives; three other targets mentioned in it which are expected to fail. I think that's it. Let me know if anything is missing. Cheers, -Jesse -- Jesse Glick NetBeans, Open APIs tel (+4202) 3300-9161 Sun Micro x49161 Praha CR --------------4F1056F97045F806F783315F Content-Type: text/plain; charset=us-ascii; name="zip-jar-patch.txt" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="zip-jar-patch.txt" Index: build.xml =================================================================== RCS file: /home/cvspublic/jakarta-ant/build.xml,v retrieving revision 1.62 diff -d -u -r1.62 build.xml --- build.xml 2000/08/21 15:05:52 1.62 +++ build.xml 2000/08/30 13:41:10 @@ -90,7 +90,6 @@ - @@ -102,6 +101,7 @@ forceoverwrite="true" filtering="on"> + Index: docs/index.html =================================================================== RCS file: /home/cvspublic/jakarta-ant/docs/index.html,v retrieving revision 1.82 diff -d -u -r1.82 index.html --- docs/index.html 2000/08/21 14:41:11 1.82 +++ docs/index.html 2000/08/30 13:41:12 @@ -2022,6 +2022,15 @@ <include>, <exclude>, <patternset> and <patternsetref> elements.

+

You can also use nested file sets for more flexibility, and specify +multiple ones to merge together different trees of files into one JAR. +See the Zip task for more details and examples.

+

If the manifest is omitted, a simple one will be supplied by Ant. +You should not include META-INF/MANIFEST.MF in your set of files. +

The whenempty parameter controls what happens when no files match. +If create (the default), the JAR is created anyway with only a manifest. +If skip, the JAR is not created and a warning is issued. +If fail, the JAR is not created and the build is halted with an error.

Parameters

@@ -2037,7 +2046,7 @@ - + @@ -2079,6 +2088,11 @@ + + + + +
basedir the directory from which to jar the files.YesNo
compressthe manifest file to use. No
whenemptyBehavior to use if no files match.No

Examples

  <jar jarfile="${dist}/lib/app.jar" basedir="${build}/classes" />
@@ -2100,6 +2114,21 @@ called app.jar in the ${dist}/lib directory. Only files under the directory mypackage/test are used, and files with the name Test.class are excluded.

+
  <jar jarfile="${dist}/lib/app.jar">
+    <fileset dir="${build}/classes"
+             excludes="**/Test.class"
+    />
+    <fileset dir="${src}/resources"/>
+  </jar>
+

jars all files in the ${build}/classes directory and also +in the ${src}/resources directory together in a file +called app.jar in the ${dist}/lib directory. +Files with the name Test.class are excluded. +If there are files such as ${build}/classes/mypackage/MyClass.class +and ${src}/resources/mypackage/image.gif, they will appear +in the same directory in the JAR (and thus be considered in the same package +by Java).

+

Java

Description

@@ -3655,6 +3684,19 @@ <include>, <exclude>, <patternset> and <patternsetref> elements.

+

Or, you may place within it nested file sets, or references to file sets. +In this case basedir is optional; the implicit file set is only used +if basedir is set. You may use any mixture of the implicit file set +(with basedir set, and optional attributes like includes +and optional subelements like <include>); explicit nested +<fileset> elements; and nested <filesetref> +elements; so long as at least one fileset total is specified. The ZIP file will +only reflect the relative paths of files within each fileset.

+

The whenempty parameter controls what happens when no files match. +If skip (the default), the ZIP is not created and a warning is issued. +If fail, the ZIP is not created and the build is halted with an error. +If create, an empty ZIP file (explicitly zero entries) is created, +which should be recognized as such by compliant ZIP manipulation tools.

Parameters

@@ -3670,7 +3712,7 @@ - + @@ -3707,6 +3749,11 @@ ("yes"/"no"). Default excludes are used when omitted. + + + + +
basedir the directory from which to zip the files.YesNo
compress No
whenemptyBehavior when no files match.No

Examples

  <zip zipfile="${dist}/manual.zip"
@@ -3729,6 +3776,14 @@
 

zips all files in the htdocs/manual directory in a file called manual.zip in the ${dist} directory. Only html files under the directory api are zipped, and files with the name todo.html are excluded.

+
  <zip zipfile="${dist}/manual.zip">
+    <fileset dir="htdocs/manual"/>
+    <fileset dir="." includes="ChangeLog.txt"/>
+  </zip>
+

zips all files in the htdocs/manual directory in a file called manual.zip +in the ${dist} directory, and also adds the file ChangeLog.txt in the +current directory. ChangeLog.txt will be added to the top of the ZIP file, just as if +it had been located at htdocs/manual/ChangeLog.txt.


Optional tasks

Index: src/main/org/apache/tools/ant/defaultManifest.mf =================================================================== RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/defaultManifest.mf,v retrieving revision 1.1 diff -d -u -r1.1 defaultManifest.mf --- src/main/org/apache/tools/ant/defaultManifest.mf 2000/01/13 10:41:40 1.1 +++ src/main/org/apache/tools/ant/defaultManifest.mf 2000/08/30 13:41:32 @@ -1 +1,3 @@ Manifest-Version: 1.0 +Created-By: Ant @VERSION@ + Index: src/main/org/apache/tools/ant/taskdefs/Jar.java =================================================================== RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Jar.java,v retrieving revision 1.5 diff -d -u -r1.5 Jar.java --- src/main/org/apache/tools/ant/taskdefs/Jar.java 2000/06/27 11:12:11 1.5 +++ src/main/org/apache/tools/ant/taskdefs/Jar.java 2000/08/30 13:41:33 @@ -86,38 +86,67 @@ super.zipDir(new File(manifest.getParent()), zOut, "META-INF/"); super.zipFile(manifest, zOut, "META-INF/MANIFEST.MF"); } else { - /* - * We don't store directories at all and this one will cause a lot - * of problems with STORED Zip-Mode. - * - * That's why i've removed it -- Stefan Bodewig - */ - // ZipEntry ze = new ZipEntry("META-INF/"); - // zOut.putNextEntry(ze); String s = "/org/apache/tools/ant/defaultManifest.mf"; InputStream in = this.getClass().getResourceAsStream(s); if ( in == null ) throw new BuildException ( "Could not find: " + s ); + super.zipDir(null, zOut, "META-INF/"); zipFile(in, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis()); } } + protected boolean isUpToDate(FileScanner[] scanners, File zipFile) + { + File[] files = grabFiles(scanners); + if (emptyBehavior == null) emptyBehavior = "create"; + if (files.length == 0) { + if (emptyBehavior.equals("skip")) { + log("Warning: skipping JAR archive " + zipFile + + " because no files were included.", Project.MSG_WARN); + return true; + } else if (emptyBehavior.equals("fail")) { + throw new BuildException("Cannot create JAR archive " + zipFile + + ": no files were included.", location); + } else { + // create + if (!zipFile.exists() || + (manifest != null && + manifest.lastModified() > zipFile.lastModified())) + log("Note: creating empty JAR archive " + zipFile, Project.MSG_INFO); + // and continue below... + } + } + if (!zipFile.exists()) return false; + if (manifest != null && manifest.lastModified() > zipFile.lastModified()) + return false; + for (int i=0; i zipFile.lastModified()) { + return false; + } + } + return true; + } + protected void zipDir(File dir, ZipOutputStream zOut, String vPath) throws IOException { // First add directory to zip entry - if(!vPath.equals("META-INF/")) { + if(!vPath.equalsIgnoreCase("META-INF/")) { // we already added a META-INF super.zipDir(dir, zOut, vPath); } + // no warning if not, it is harmless in and of itself } protected void zipFile(File file, ZipOutputStream zOut, String vPath) throws IOException { // We already added a META-INF/MANIFEST.MF - if (!vPath.equals("META-INF/MANIFEST.MF")) { + if (!vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) { super.zipFile(file, zOut, vPath); + } else { + log("Warning: selected JAR files include a META-INF/MANIFEST.MF which will be ignored " + + "(please use manifest attribute to jar task)", Project.MSG_WARN); } } } Index: src/main/org/apache/tools/ant/taskdefs/Zip.java =================================================================== RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java,v retrieving revision 1.10 diff -d -u -r1.10 Zip.java --- src/main/org/apache/tools/ant/taskdefs/Zip.java 2000/08/03 09:13:18 1.10 +++ src/main/org/apache/tools/ant/taskdefs/Zip.java 2000/08/30 13:41:33 @@ -55,9 +55,11 @@ package org.apache.tools.ant.taskdefs; import org.apache.tools.ant.*; +import org.apache.tools.ant.types.*; import java.io.*; import java.util.Enumeration; +import java.util.Hashtable; import java.util.StringTokenizer; import java.util.Vector; import java.util.zip.*; @@ -75,6 +77,10 @@ private File baseDir; private boolean doCompress = true; protected String archiveType = "zip"; + // For directories: + private static long emptyCrc = new CRC32 ().getValue (); + protected String emptyBehavior = null; + private Vector filesets = new Vector (); /** * This is the name/location of where to @@ -99,73 +105,113 @@ doCompress = Project.toBoolean(compress); } - public void execute() throws BuildException { - if (baseDir == null) { - throw new BuildException("basedir attribute must be set!"); - } - if (!baseDir.exists()) { - throw new BuildException("basedir does not exist!"); - } + /** + * Adds a set of files (nested fileset attribute). + */ + public void addFileset(FileSet set) { + filesets.addElement(set); + } - DirectoryScanner ds = super.getDirectoryScanner(baseDir); + /** + * Adds a reference to a set of files (nested filesetref element). + */ + public void addFilesetref(Reference ref) { + filesets.addElement(ref); + } - String[] files = ds.getIncludedFiles(); - String[] dirs = ds.getIncludedDirectories(); + /** + * Sets behavior of the task when no files match. + * Possible values are: fail (throw an exception + * and halt the build); skip (do not create + * any archive, but issue a warning); create + * (make an archive with no entries). + * Default for zip tasks is skip; + * for jar tasks, create. + */ + public void setWhenempty(String we) throws BuildException { + we = we.toLowerCase(); + // XXX could instead be using EnumeratedAttribute, but this works + if (!"fail".equals(we) && !"skip".equals(we) && !"create".equals(we)) + throw new BuildException("Unrecognized whenempty attribute: " + we); + emptyBehavior = we; + } + + public void execute() throws BuildException { + if (baseDir == null && filesets.size() == 0) + throw new BuildException("basedir attribute must be set, or at least one fileset must be given!"); + + Vector dss = new Vector (); + if (baseDir != null) + dss.addElement(getDirectoryScanner(baseDir)); + for (int i=0; i - zipFile.lastModified()) - upToDate = false; - if (upToDate) return; + // can also handle empty archives + if (isUpToDate(scanners, zipFile)) return; log("Building "+ archiveType +": "+ zipFile.getAbsolutePath()); - ZipOutputStream zOut = null; - try { - zOut = new ZipOutputStream(new FileOutputStream(zipFile)); - if (doCompress) { - zOut.setMethod(ZipOutputStream.DEFLATED); - } else { - zOut.setMethod(ZipOutputStream.STORED); - } - initZipOutputStream(zOut); + try { + ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream(zipFile)); + try { + if (doCompress) { + zOut.setMethod(ZipOutputStream.DEFLATED); + } else { + zOut.setMethod(ZipOutputStream.STORED); + } + initZipOutputStream(zOut); - for (int i = 0; i < dirs.length; i++) { - File f = new File(baseDir,dirs[i]); - String name = dirs[i].replace(File.separatorChar,'/')+"/"; - zipDir(f, zOut, name); - } + // XXX ideally would also enter includedDirectories to the archive + Hashtable parentDirs = new Hashtable(); - for (int i = 0; i < files.length; i++) { - File f = new File(baseDir,files[i]); - String name = files[i].replace(File.separatorChar,'/'); - zipFile(f, zOut, name); - } - } catch (IOException ioe) { - String msg = "Problem creating " + archiveType + " " + ioe.getMessage(); + for (int j = 0; j < scanners.length; j++) { + String[] files = scanners[j].getIncludedFiles(); + File thisBaseDir = scanners[j].getBasedir(); + for (int i = 0; i < files.length; i++) { + File f = new File(thisBaseDir,files[i]); + String name = files[i].replace(File.separatorChar,'/'); + // Look for & create parent dirs as needed. + int slashPos = -1; + while ((slashPos = name.indexOf((int)'/', slashPos + 1)) != -1) { + String dir = name.substring(0, slashPos); + if (!parentDirs.contains(dir)) { + parentDirs.put(dir, dir); + zipDir(new File(thisBaseDir, dir.replace('/', File.separatorChar)), + zOut, dir + '/'); + } + } + zipFile(f, zOut, name); + } + } + } finally { + zOut.close (); + } + } catch (IOException ioe) { + String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); // delete a bogus ZIP file - if (zOut != null) { - try { - zOut.close(); - zOut = null; - } catch (IOException e) {} - if (!zipFile.delete()) { - msg = zipFile + " is probably corrupt but I could not delete it"; - } - } + if (!zipFile.delete()) { + msg += " (and the archive is probably corrupt but I could not delete it)"; + } throw new BuildException(msg, ioe, location); - } finally { - if (zOut != null) { - try { - // close up - zOut.close(); - } - catch (IOException e) {} - } } } @@ -174,9 +220,87 @@ { } + /** + * Check whether the archive is up-to-date; and handle behavior for empty archives. + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); false if + * archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate(FileScanner[] scanners, File zipFile) throws BuildException + { + if (emptyBehavior == null) emptyBehavior = "skip"; + File[] files = grabFiles(scanners); + if (files.length == 0) { + if (emptyBehavior.equals("skip")) { + log("Warning: skipping ZIP archive " + zipFile + + " because no files were included.", Project.MSG_WARN); + return true; + } else if (emptyBehavior.equals("fail")) { + throw new BuildException("Cannot create ZIP archive " + zipFile + + ": no files were included.", location); + } else { + // Create. + if (zipFile.exists()) return true; + // In this case using java.util.zip will not work + // because it does not permit a zero-entry archive. + // Must create it manually. + log("Note: creating empty ZIP archive " + zipFile, Project.MSG_INFO); + try { + OutputStream os = new FileOutputStream(zipFile); + try { + // Cf. PKZIP specification. + byte[] empty = new byte[22]; + empty[0] = 80; // P + empty[1] = 75; // K + empty[2] = 5; + empty[3] = 6; + // remainder zeros + os.write(empty); + } finally { + os.close(); + } + } catch (IOException ioe) { + throw new BuildException("Could not create empty ZIP archive", ioe, location); + } + return true; + } + } else { + // Probably unnecessary but just for clarity: + if (!zipFile.exists()) return false; + for (int i=0; i zipFile.lastModified()) { + return false; + } + } + return true; + } + } + + protected static File[] grabFiles(FileScanner[] scanners) { + Vector files = new Vector (); + for (int i = 0; i < scanners.length; i++) { + File thisBaseDir = scanners[i].getBasedir(); + String[] ifiles = scanners[i].getIncludedFiles(); + for (int j = 0; j < ifiles.length; j++) + files.add(new File(thisBaseDir, ifiles[j])); + } + File[] toret = new File[files.size()]; + files.copyInto(toret); + return toret; + } + protected void zipDir(File dir, ZipOutputStream zOut, String vPath) throws IOException { + ZipEntry ze = new ZipEntry (vPath); + if (dir != null) ze.setTime (dir.lastModified ()); + ze.setSize (0); + ze.setMethod (ZipEntry.STORED); + // This is faintly ridiculous: + ze.setCrc (emptyCrc); + zOut.putNextEntry (ze); } protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath, --------------4F1056F97045F806F783315F Content-Type: application/x-zip-compressed; name="zip-jar-test.zip" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="zip-jar-test.zip" UEsDBAoAAAAAAIRMHikAAAAAAAAAAAAAAAAUABUAemlwamFyL2VtcHR5Ly5leGlzdHNVVAkA A2e5rDlnuaw5VXgEAPQB9AFQSwMECgAAAAAAvGIeKQAAAAAAAAAAAAAAABUAFQB6aXBqYXIv c29tZXRoaW5nL3JlYWxVVAkAA0PgrDlS4Kw5VXgEAPQB9AFQSwMECgAAAAAAXU4eKQAAAAAA AAAAAAAAACoAFQB6aXBqYXIvc29tZXRoaW5nL3N1YmRpcjEvc3ViZGlyMi9zdWJkaXIzL3lV VAkAA+G8rDlS4Kw5VXgEAPQB9AFQSwMECgAAAAAAWU4eKQAAAAAAAAAAAAAAABoAFQB6aXBq YXIvc29tZXRoaW5nL3N1YmRpcjEveFVUCQAD2bysOVLgrDlVeAQA9AH0AVBLAwQUAAAACABp TR4pSSQE+D4AAACHAAAAFwAVAHppcGphci9zb21ldGhpbmcvYmlnZ2VyVVQJAAMWu6w5bAyt OVV4BAD0AfQBC87PTVUoSa0oUchILUrV43LMS1EoBonl5hdhlwCLleQrJOfnFhSlFhcjyYP1 FKWmlOalJOaV4FOJsAIiBgBQSwMEFAAAAAgAo2IeKWiYdZlRAgAAlA4AABAAFQB6aXBqYXIv YnVpbGQueG1sVVQJAAMR4Kw5bAytOVV4BAD0AfQBtVfBkqMgEL3PV7Aep2KM5LYV518YbBJn 0aSE1Dr79QutTKVMGmKVOQwqr+n3Gh8d53Dpz18gLetEC1VmwdiM1aDEVdsqE1pn7FMYqJu+ yrbZxxtjByv6I4QFGFHDBbraVNm/5rLb+MHgKP1Ybr5Ev8PB4Cj9iLMcB5zlOLv3w+BX5cpM l7xtBqjxoQcVJv2ti/XXrEBZv/KcmdP5qmumRKN/M69AbZinHC9csTy/L8HHYWUOcPd+nWr0 tBm7rXu82QNoL/Y7YzBIfa3Blfz+XmxhaIw1k45iTP6Yx9BEZmT6e4IOSarM/FmTWtHU6o7a 7+B61JKmlnfUsgdhYTXykuQuZ6/WnFuwp6Y7prJ6R4Ws7t5b69Yw7nEd9XhkaCIzMr3EMHho aGp1R72eYbBF0NTyjnpNw/jORHKXs1e7xDCczMrHrK3oGuUeq8zfbVu1WkGc9hCnPPRCNbSt OGWrF6qhncZpp71Oz56Us0/bZIkfB5JomDewwfbCkz3RbN0vM9luHYYNd8RdhIeMW/1Iu+dx wc8Qjt8HMdox4ily1tRuZ78R/Ql24dPrnIg/m+MR+qDUS5zSJdKDNrCwPv99Q5bmwIdVuXnm /m5rWbyrUeIRX8i91raEzz7KwCiNOCxLdsqliAux56s8uZaggxScYKOSn+oKjEicHVyJh+xB qiA+kUNqEF1YX4MGC2z+IVuQsEngKoHLOF4mluMLi6mL4yqByzhexmGegBPqeEIdT6jbx+Eh Doe+Gwu46ZB0WDjx0UTpoHA6Z24+FNN/oh9v/wFQSwMECgAAAAAAxWIeKZEvFH0xAAAAMQAA AA4AFQB6aXBqYXIvbWFuaS5tZlVUCQADUeCsOWwMrTlVeAQA9AH0AU1hbmlmZXN0LVZlcnNp b246IDEuMApYLUNvbW1lbnQ6IFNvbWV0aGluZyBoZXJlCgpQSwMECgAAAAAAEVkeKQAAAAAA AAAAAAAAABQAFQB6aXBqYXIvZXh0cmFtYW5pL2Zvb1VUCQADEc+sOZHcrDlVeAQA9AH0AVBL AwQKAAAAAAAhWR4pNGNaejUAAAA1AAAAJQAVAHppcGphci9leHRyYW1hbmkvTUVUQS1JTkYv TUFOSUZFU1QuTUZVVAkAAy3PrDlsDK05VXgEAPQB9AFNYW5pZmVzdC1WZXJzaW9uOiAxLjAK WC1Db21tZW50OiBzaG91bGQgbm90IGJlIHVzZWQKClBLAwQKAAAAAAAHYR4pAAAAAAAAAAAA AAAAGAAVAHppcGphci9zb21ldGhpbmdlbHNlL3doeVVUCQADDd2sOUXgrDlVeAQA9AH0AVBL AwQKAAAAAAAaYR4pAAAAAAAAAAAAAAAAKwAVAHppcGphci9zb21ldGhpbmdlbHNlL3N1YmRp cjEvc3ViZGlyMmEvd2hlcmVVVAkAAzTdrDlF4Kw5VXgEAPQB9AFQSwMECgAAAAAAF2EeKQAA AAAAAAAAAAAAACAAFQB6aXBqYXIvc29tZXRoaW5nZWxzZS9zdWJkaXIxL2hvd1VUCQADLd2s OUXgrDlVeAQA9AH0AVBLAQIWAwoAAAAAAIRMHikAAAAAAAAAAAAAAAAUAA0AAAAAAAAAAAC0 gQAAAAB6aXBqYXIvZW1wdHkvLmV4aXN0c1VUBQADZ7msOVV4AABQSwECFgMKAAAAAAC8Yh4p AAAAAAAAAAAAAAAAFQANAAAAAAAAAAAAtIFHAAAAemlwamFyL3NvbWV0aGluZy9yZWFsVVQF AAND4Kw5VXgAAFBLAQIWAwoAAAAAAF1OHikAAAAAAAAAAAAAAAAqAA0AAAAAAAAAAAC0gY8A AAB6aXBqYXIvc29tZXRoaW5nL3N1YmRpcjEvc3ViZGlyMi9zdWJkaXIzL3lVVAUAA+G8rDlV eAAAUEsBAhYDCgAAAAAAWU4eKQAAAAAAAAAAAAAAABoADQAAAAAAAAAAALSB7AAAAHppcGph ci9zb21ldGhpbmcvc3ViZGlyMS94VVQFAAPZvKw5VXgAAFBLAQIWAxQAAAAIAGlNHilJJAT4 PgAAAIcAAAAXAA0AAAAAAAEAAAC0gTkBAAB6aXBqYXIvc29tZXRoaW5nL2JpZ2dlclVUBQAD FrusOVV4AABQSwECFgMUAAAACACjYh4paJh1mVECAACUDgAAEAANAAAAAAABAAAAtIHBAQAA emlwamFyL2J1aWxkLnhtbFVUBQADEeCsOVV4AABQSwECFgMKAAAAAADFYh4pkS8UfTEAAAAx AAAADgANAAAAAAABAAAAtIFVBAAAemlwamFyL21hbmkubWZVVAUAA1HgrDlVeAAAUEsBAhYD CgAAAAAAEVkeKQAAAAAAAAAAAAAAABQADQAAAAAAAAAAALSBxwQAAHppcGphci9leHRyYW1h bmkvZm9vVVQFAAMRz6w5VXgAAFBLAQIWAwoAAAAAACFZHik0Y1p6NQAAADUAAAAlAA0AAAAA AAEAAAC0gQ4FAAB6aXBqYXIvZXh0cmFtYW5pL01FVEEtSU5GL01BTklGRVNULk1GVVQFAAMt z6w5VXgAAFBLAQIWAwoAAAAAAAdhHikAAAAAAAAAAAAAAAAYAA0AAAAAAAAAAAC0gZsFAAB6 aXBqYXIvc29tZXRoaW5nZWxzZS93aHlVVAUAAw3drDlVeAAAUEsBAhYDCgAAAAAAGmEeKQAA AAAAAAAAAAAAACsADQAAAAAAAAAAALSB5gUAAHppcGphci9zb21ldGhpbmdlbHNlL3N1YmRp cjEvc3ViZGlyMmEvd2hlcmVVVAUAAzTdrDlVeAAAUEsBAhYDCgAAAAAAF2EeKQAAAAAAAAAA AAAAACAADQAAAAAAAAAAALSBRAYAAHppcGphci9zb21ldGhpbmdlbHNlL3N1YmRpcjEvaG93 VVQFAAMt3aw5VXgAAFBLBQYAAAAADAAMAAIEAACXBgAAAAA= --------------4F1056F97045F806F783315F--