Skip to content

Commit 34773e8

Browse files
committed
Package as a self-extracting jar.
Build the jar to include JarX.class along with a short bit of JavaScript to replace pljava/foodir/ path prefixes with the output from pg_config --foodir or the value given with -Dpgconfig.foodir= The exact pg_config to execute can be specified with -Dpgconfig=
1 parent c3748d4 commit 34773e8

File tree

2 files changed

+292
-31
lines changed

2 files changed

+292
-31
lines changed

pljava-packaging/build.xml

Lines changed: 278 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,286 @@
2424
</target>
2525

2626
<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=
57239
"../pljava-so/target/nar/pljava-so-${project.version}-${naraol}-plugin/lib/${naraol}/plugin"
58240
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>
60307
</target>
61308

62309
</project>

pljava-packaging/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@
9090
</executions>
9191
</plugin>
9292

93+
<plugin>
94+
<groupId>org.apache.maven.plugins</groupId>
95+
<artifactId>maven-compiler-plugin</artifactId>
96+
<executions>
97+
<execution>
98+
<id>compile the jar extractor</id>
99+
<phase>compile</phase>
100+
<goals>
101+
<goal>compile</goal>
102+
</goals>
103+
</execution>
104+
</executions>
105+
</plugin>
106+
93107
<plugin>
94108
<groupId>org.apache.maven.plugins</groupId>
95109
<artifactId>maven-antrun-plugin</artifactId>

0 commit comments

Comments
 (0)