|
24 | 24 | </target> |
25 | 25 |
|
26 | 26 | <target name="package" depends="init"> |
27 | | - <zip destfile="target/pljava-${suffix}.zip"> |
28 | | - <concat resourcename="pljava/sharedir/extension/pljava.control"> |
29 | | - <fileset dir="target/classes" includes="pljava.control"/> |
30 | | - <filterchain> |
31 | | - <fixcrlf eol="crlf" eof="remove"/> |
32 | | - </filterchain> |
33 | | - </concat> |
34 | | - <concat resourcename= |
35 | | - "pljava/sharedir/pljava/pljava--${project.version}.sql"> |
36 | | - <fileset dir="target/classes" includes="pljava.sql"/> |
37 | | - <filterchain> |
38 | | - <fixcrlf eol="crlf" eof="remove"/> |
39 | | - </filterchain> |
40 | | - </concat> |
41 | | - <concat resourcename= |
42 | | - "pljava/sharedir/pljava/pljava--unpackaged--${project.version}.sql"> |
43 | | - <fileset dir="target/classes" |
44 | | - includes="pljava--unpackaged.sql"/> |
45 | | - <filterchain> |
46 | | - <fixcrlf eol="crlf" eof="remove"/> |
47 | | - </filterchain> |
48 | | - </concat> |
49 | | - <zipfileset dir="target/classes" prefix="pljava/sharedir/pljava/" |
50 | | - includes="*.sql" excludes="pljava.sql pljava--unpackaged.sql"/> |
51 | | - <zipfileset dir="../pljava/target" includes="pljava*.jar" |
52 | | - prefix="pljava/sharedir/pljava/"/> |
53 | | - <zipfileset dir="../pljava-examples/target" |
54 | | - includes="pljava-examples*.jar" |
55 | | - prefix="pljava/sharedir/pljava"/> |
56 | | - <zipfileset prefix="pljava/pkglibdir/" filemode="755" dir= |
| 27 | + |
| 28 | + <!-- |
| 29 | + Create a new ant task <jarxbuild> for building a jar that self- |
| 30 | + extracts by including org/gjt/cuspy/JarX.class. <jarxbuild> takes |
| 31 | + one attribute, destfile= (which should be absolute, consider |
| 32 | + specifying ${basedir} at the front). To specify what goes IN the |
| 33 | + jar, supply nested zipfileset elements; that class was chosen |
| 34 | + because it already supports prefix=, fullpath=, and filemode= |
| 35 | + attributes to control the stored path and permission info, as well |
| 36 | + as all the scanning/inclusion/exclusion rules you could want. But by |
| 37 | + itself, it isn't enough to tell JarX which files are binary or text, |
| 38 | + or the encodings of the text files. That is conveyed by which alias |
| 39 | + is used for each zipfileset. That is, you don't literally use |
| 40 | + nested <zipfileset> elements, but instead <binary>, <ascii>, or |
| 41 | + <utf8> elements, which all are zipfilesets behind the scenes and |
| 42 | + have all the same attributes and elements. Any files supplied in |
| 43 | + a <binary> element will be archived and unarchived unchanged. |
| 44 | + Files supplied in an <ascii> or <utf8> element will be treated as |
| 45 | + text and have their line endings converted to local conventions on |
| 46 | + extraction; they won't be transcoded, but will be stored in and |
| 47 | + extracted from the jar in the specified encoding (and verified to |
| 48 | + be well-formed in it). That obviously isn't a fully-general way to |
| 49 | + specify the text handling, but it gets the job done, and if you have |
| 50 | + another case to handle, just add another zipfileset <element> with |
| 51 | + another name, and edit the javascript below to know what to do |
| 52 | + with it. |
| 53 | +
|
| 54 | + To emphasize, files given in an <ascii> or <utf8> element are |
| 55 | + extracted in exactly that encoding on any target platform regardless |
| 56 | + of the platform's default encoding. That's appropriate for an |
| 57 | + extension .control file (which must be <ascii> because PostgreSQL |
| 58 | + assumes it), and any extension .sql scripts we supply (which must |
| 59 | + be <utf8> because the .control file says so). It's possible that |
| 60 | + for some other text files we might include (general docs or |
| 61 | + what not), it would be nicer to let them be extracted into the |
| 62 | + platform's default encoding (achieved by specifying a fixed value |
| 63 | + such as UTF-8 for _JarX_CharsetInArchive while entirely omitting |
| 64 | + _JarX_CharsetWhenUnpacked). But at the moment no such files are |
| 65 | + being included, so I haven't provided another element name to give |
| 66 | + that behavior. |
| 67 | +
|
| 68 | + There has to be a <binary> element supplying JarX.class itself. |
| 69 | +
|
| 70 | + The <jarxbuild> task also accepts nested text, which will be treated |
| 71 | + as a path-resolver script (the language is assumed to be |
| 72 | + application/javascript; I'm lazy). It will be stuffed into the |
| 73 | + manifest in a form that survives manifest line-wrapping, and JarX |
| 74 | + will use it at extraction time to rewrite pathnames. In this case, |
| 75 | + that script (see further below) will use pg_config or system |
| 76 | + properties to determine the extraction paths. |
| 77 | + --> |
| 78 | + |
| 79 | + <scriptdef name='jarxbuild' language='javascript'> |
| 80 | + <attribute name='destfile'/> |
| 81 | + <element type='zipfileset' name='binary'/> |
| 82 | + <element type='zipfileset' name='ascii'/> |
| 83 | + <element type='zipfileset' name='utf8'/> |
| 84 | + <classpath> |
| 85 | + <pathelement path='${classpath}'/> |
| 86 | + <pathelement location='target/classes'/> <!-- JarX is there --> |
| 87 | + </classpath> |
| 88 | +<![CDATA[ |
| 89 | +var manifest = new java.util.jar.Manifest(); |
| 90 | +var sections = manifest.getEntries(); |
| 91 | +var mainsect = manifest.getMainAttributes(); |
| 92 | +mainsect.putValue('Manifest-Version', '1.0'); |
| 93 | +mainsect.putValue('Created-By', project.getProperty('project.name') + ' ' + |
| 94 | + project.getProperty('project.version')); |
| 95 | +mainsect.putValue('Main-Class', 'org.gjt.cuspy.JarX'); |
| 96 | +
|
| 97 | +/* Declare default permissions for entries that don't have their own. |
| 98 | + */ |
| 99 | +mainsect.putValue('_JarX_Permissions', 'read=all,write=none,execute=none'); |
| 100 | +
|
| 101 | +/* Munge a name (obtained directly from a resource in a fileset) according to |
| 102 | + the prefix or fullpath given for the fileset itself. |
| 103 | + */ |
| 104 | +function munge(rsrc, fileset) { |
| 105 | + var fullpath = fileset.getFullpath(project); |
| 106 | + var prefix = fileset.getPrefix(project); |
| 107 | + var n = rsrc.getName(); |
| 108 | + if ( !(null === fullpath || '' == fullpath) ) |
| 109 | + n = fullpath; |
| 110 | + else if ( !(null === prefix) ) |
| 111 | + n = prefix + n; |
| 112 | + return n; |
| 113 | +} |
| 114 | +
|
| 115 | +/* Given one fileset, compute a set of manifest per-entry attributes from the |
| 116 | + specified charset (caller supplied according to the alias name that was used |
| 117 | + for the fileset) and file mode specified on the fileset, if any, then enter |
| 118 | + a manifest section with those attributes for every resource name in the |
| 119 | + fileset. Caller supplies charset === null for a fileset to be treated as |
| 120 | + binary. |
| 121 | + */ |
| 122 | +function mksections(charset, fileset) { |
| 123 | + var atts = new java.util.jar.Attributes(); |
| 124 | + if ( !(null === charset) ) { |
| 125 | + atts.putValue('Content-Type', 'text/plain'); |
| 126 | + atts.putValue('_JarX_CharsetInArchive', charset); |
| 127 | + atts.putValue('_JarX_CharsetWhenUnpacked', charset); |
| 128 | + } |
| 129 | + if ( fileset.hasFileModeBeenSet() ) { |
| 130 | + var m = fileset.getFileMode(project) - 32768; /* don't ask me why */ |
| 131 | + if ( 0444 == m ) |
| 132 | + atts.putValue('_JarX_Permissions', |
| 133 | + 'read=all,write=none,execute=none'); |
| 134 | + else if ( 0555 == m ) |
| 135 | + atts.putValue('_JarX_Permissions', |
| 136 | + 'read=all,write=none,execute=all'); |
| 137 | + else |
| 138 | + self.fail('File mode '+m+' not covered in script.'); |
| 139 | + } |
| 140 | + var it = fileset.iterator(); |
| 141 | + while ( it.hasNext() ) { |
| 142 | + var rsrc = it.next(); |
| 143 | + sections.put(munge(rsrc, fileset), atts); |
| 144 | + } |
| 145 | +} |
| 146 | +
|
| 147 | +/* Given a fileset and JarOutputStream, for each resource look up the manifest |
| 148 | + per-entry section earlier created, pass that to builder.classify() to set |
| 149 | + how shovel() will handle the contents, then make the next jar entry and |
| 150 | + shovel the resource contents in. |
| 151 | + */ |
| 152 | +function storeset(fileset, jos) { |
| 153 | + var it = fileset.iterator(); |
| 154 | + while ( it.hasNext() ) { |
| 155 | + var rsrc = it.next(); |
| 156 | + var n = munge(rsrc, fileset); |
| 157 | + var atts = sections.get(n); |
| 158 | + builder.classify(atts, true); |
| 159 | + var je = new java.util.jar.JarEntry(n); |
| 160 | + jos.putNextEntry(je); |
| 161 | + var instream = rsrc.getInputStream(); |
| 162 | + print(n+' '); |
| 163 | + builder.shovel(instream, jos); |
| 164 | + jos.closeEntry(); |
| 165 | + } |
| 166 | +} |
| 167 | +
|
| 168 | +/* If there is text nested in the <jarxbuild> element, it is a path-resolver |
| 169 | + script. Munge it to the manifest-attribute-linewrap-resistant style JarX |
| 170 | + expects (first \-escape every \ and " then turn every line ending to the |
| 171 | + three characters "/" then add " at start and end), and place into the main |
| 172 | + manifest section as _JarX_PathResolver, with the language tag |
| 173 | + application/javascript (hardcoded here, you weren't thinking of using |
| 174 | + a different script language anyway). |
| 175 | + */ |
| 176 | +var pathresolver = self.text.trim() + ''; /* ensure js string not java string */ |
| 177 | +if ( !(null === pathresolver || '' == pathresolver) ) { |
| 178 | + pathresolver = pathresolver.replace(/["\\]/g, "\\$&"); |
| 179 | + pathresolver = pathresolver.replace(/\r\n?|\n/g, '"/"'); |
| 180 | + pathresolver = '"' + pathresolver + '"'; |
| 181 | + mainsect.putValue('_JarX_PathResolver', |
| 182 | + 'application/javascript' + pathresolver); |
| 183 | +} |
| 184 | +
|
| 185 | +/* For each element name that can be used to supply a zipfileset, get all the |
| 186 | + zipfilesets supplied under that name and pass them to mksections with the |
| 187 | + appropriate charset parameter according to the element name. |
| 188 | + */ |
| 189 | +var binary = elements.get("binary"); |
| 190 | +for ( i = 0; i < binary.size(); ++i ) { |
| 191 | + var fset = binary.get(i); |
| 192 | + mksections(null, fset); |
| 193 | +} |
| 194 | +var ascii = elements.get("ascii"); |
| 195 | +for ( i = 0; i < ascii.size(); ++i ) { |
| 196 | + var fset = ascii.get(i); |
| 197 | + mksections('US-ASCII', fset); |
| 198 | +} |
| 199 | +var utf = elements.get("utf8"); |
| 200 | +for ( i = 0; i < utf.size(); ++i ) { |
| 201 | + var fset = utf.get(i); |
| 202 | + mksections('UTF-8', fset); |
| 203 | +} |
| 204 | +
|
| 205 | +/* Set up the output archive... |
| 206 | + */ |
| 207 | +var builder = new org.gjt.cuspy.JarX.Build(); |
| 208 | +builder.setDefaults(mainsect); |
| 209 | +var destf = attributes.get('destfile'); |
| 210 | +var fos = new java.io.FileOutputStream(destf); |
| 211 | +var jos = new java.util.jar.JarOutputStream(fos, manifest); |
| 212 | +
|
| 213 | +/* Go through the supplied filesets once again, passing them to storeset... |
| 214 | + */ |
| 215 | +for ( i = 0; i < binary.size(); ++i ) { |
| 216 | + var fset = binary.get(i); |
| 217 | + storeset(fset, jos); |
| 218 | +} |
| 219 | +for ( i = 0; i < ascii.size(); ++i ) { |
| 220 | + var fset = ascii.get(i); |
| 221 | + storeset(fset, jos); |
| 222 | +} |
| 223 | +for ( i = 0; i < utf.size(); ++i ) { |
| 224 | + var fset = utf.get(i); |
| 225 | + storeset(fset, jos); |
| 226 | +} |
| 227 | +
|
| 228 | +/* And finalize the output. |
| 229 | + */ |
| 230 | +jos.close(); |
| 231 | +]]> |
| 232 | + </scriptdef> |
| 233 | + |
| 234 | + <!-- Now build the jar using the <jarxbuild> task just created. --> |
| 235 | + |
| 236 | + <jarxbuild destfile="${basedir}/target/pljava-${suffix}.jar"> |
| 237 | + <binary dir="target/classes" includes="**/JarX.class"/> |
| 238 | + <binary prefix="pljava/pkglibdir/" filemode="555" dir= |
57 | 239 | "../pljava-so/target/nar/pljava-so-${project.version}-${naraol}-plugin/lib/${naraol}/plugin" |
58 | 240 | includes="**/*pljava-so*"/> |
59 | | - </zip> |
| 241 | + <binary dir="../pljava/target" includes="pljava*.jar" |
| 242 | + prefix="pljava/sharedir/pljava/"/> |
| 243 | + <binary dir="../pljava-examples/target" |
| 244 | + includes="pljava-examples*.jar" |
| 245 | + prefix="pljava/sharedir/pljava/"/> |
| 246 | + <ascii dir="target/classes" includes="pljava.control" |
| 247 | + prefix="pljava/sharedir/extension/"/> |
| 248 | + <utf8 dir="target/classes" includes="pljava.sql" |
| 249 | + fullpath="pljava/sharedir/pljava/pljava--${project.version}.sql" |
| 250 | + /> |
| 251 | + <utf8 dir="target/classes" includes="pljava--unpackaged.sql" |
| 252 | + fullpath= |
| 253 | + "pljava/sharedir/pljava/pljava--unpackaged--${project.version}.sql" |
| 254 | + /> |
| 255 | + <utf8 dir="target/classes" prefix="pljava/sharedir/pljava/" |
| 256 | + includes="*.sql" excludes="pljava.sql pljava--unpackaged.sql"/> |
| 257 | +<!-- If editing the script below, expand tabs to spaces (at 4 columns, and only |
| 258 | + for the lines of the script) before saving. Line-wrapped into the manifest, |
| 259 | + it looks horrible with tabs. |
| 260 | +--> |
| 261 | +<![CDATA[ |
| 262 | +/* |
| 263 | + At extraction time, JarX invokes this for each entry, with these bindings: |
| 264 | + properties - the Java system properties object |
| 265 | + storedPath - the full path of the archive member as stored |
| 266 | + in the archive |
| 267 | + platformPath - the name after changing only file.separator |
| 268 | + for the platform (if different from /) |
| 269 | + computedPath - initially the same as platformPath |
| 270 | + If the script updates computedPath, that is where the member |
| 271 | + will be extracted. |
| 272 | +*/ |
| 273 | +var re = new RegExp('^pljava/([^/]+dir)(?![^/])'); |
| 274 | +var found = re.exec(storedPath); |
| 275 | +if (found) { |
| 276 | + var prefix = found[0]; |
| 277 | + var key = found[1]; |
| 278 | + var propkey = 'pgconfig.' + key; |
| 279 | + var replacement = properties.getProperty(propkey); |
| 280 | + if ( null === replacement ) { |
| 281 | + var pgc = properties.getProperty('pgconfig'); |
| 282 | + if ( null === pgc ) |
| 283 | + pgc = 'pg_config'; |
| 284 | + var pb = new java.lang.ProcessBuilder(pgc, '--'+key); |
| 285 | + pb.redirectErrorStream(true); |
| 286 | + var proc = pb.start(); |
| 287 | + var instream = proc.getInputStream(); |
| 288 | + var scanner = |
| 289 | + new java.util.Scanner(instream).useDelimiter('\\A'); |
| 290 | + var output = scanner.next(); |
| 291 | + var status = proc.waitFor(); |
| 292 | + if ( 0 != status ) { |
| 293 | + java.lang.System.err.println( |
| 294 | + 'ERROR: pg_config status is '+status+':\n'+output); |
| 295 | + java.lang.System.exit(1); |
| 296 | + } |
| 297 | + replacement = output.trim(); |
| 298 | + properties.setProperty(propkey, replacement); |
| 299 | + } |
| 300 | + var fsep = properties.getProperty('file.separator'); |
| 301 | + var plen = fsep.length() - 1; /* original separator had length 1 */ |
| 302 | + plen += prefix.length; |
| 303 | + computedPath = replacement + computedPath.slice(plen); |
| 304 | +} |
| 305 | +]]> |
| 306 | + </jarxbuild> |
60 | 307 | </target> |
61 | 308 |
|
62 | 309 | </project> |
0 commit comments