-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.xml
More file actions
645 lines (599 loc) · 35.1 KB
/
Copy pathbuild.xml
File metadata and controls
645 lines (599 loc) · 35.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="stepss" default="default" basedir="." xmlns:unless="ant:unless" xmlns:if="ant:if">
<description>Builds, tests, and runs the project ramses.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar-with-manifest: JAR building (if you are using a manifest)
-do-jar-without-manifest: JAR building (if you are not using a manifest)
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="stepss-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
<!-- Why this merge exists at all, since every jar named below is already in
dist/lib and already on the manifest Class-Path built by NetBeans'
copylibs, which makes the merge look redundant: the jpackage install
path reads that Class-Path fine and never needs this. What does not is
dist/stepss.jar taken on its own with no dist/lib/ beside it, which is
exactly how the release workflow ships it (a single file attached to
the GitHub release, see .github/workflows/release.yml) and exactly how
README's Quick Start tells someone to run it: `java -jar dist/stepss.jar`.
Isolated like that, a class from a jar that is only "beside" and not
"inside" throws NoClassDefFoundError the moment it is touched. Every
jar a feature needs at runtime has to be listed here too, not only in
javac.classpath, or adding a library silently reintroduces this gap for
the standalone-jar path while the installer keeps working, which is
what makes it easy to miss.
FlatLaf is a multi-release jar: its Java 9+ implementations of two
helper classes live under META-INF/versions/9/. The excludes pattern is
one level deep, so those survive the merge, but they are only ever
loaded from a jar whose manifest says Multi-Release, which is why that
attribute is in manifest.mf. It goes there rather than in a manifest
element here because this task runs with update="true" over a jar that
already has Main-Class and Class-Path in it, and supplying a manifest to
an updating jar task is how you lose them.
The natives under com/formdev/flatlaf/natives/ are not excluded either:
they are what FlatLaf uses for window decorations on Windows and macOS,
and it degrades quietly if they are absent, which is the kind of quiet
that costs an afternoon to diagnose on someone else's machine.
batik-all, xmlgraphics-commons and xml-apis-ext join the merge for the
same reason as the three above: SvgImage (my.stepss.diagram) is reached
from the standalone jar exactly as easily as from the installer, and
Ant's META-INF/* exclude matches direct children only, so
META-INF/services/org.apache.batik.script.InterpreterFactory under
META-INF/services/ survives, which keeps Batik's own service lookup
working the same way it does when the jars are separate. -->
<target name="-post-jar">
<jar jarfile="dist/stepss.jar" update="true">
<zipfileset src="${dist.jar}" excludes="META-INF/*" />
<zipfileset src="dist/lib/commons-exec-1.3.jar" excludes="META-INF/*" />
<zipfileset src="dist/lib/commons-io-2.11.0.jar" excludes="META-INF/*" />
<zipfileset src="dist/lib/flatlaf-3.7.2.jar" excludes="META-INF/*" />
<zipfileset src="dist/lib/batik-all-1.19.jar" excludes="META-INF/*" />
<zipfileset src="dist/lib/xmlgraphics-commons-2.11.jar" excludes="META-INF/*" />
<zipfileset src="dist/lib/xml-apis-ext-1.3.04.jar" excludes="META-INF/*" />
</jar>
<!-- Beside the jar as well as inside it. jpackage is given an explicit
main-class argument, so its launchers start the class on a classpath
and never read the jar manifest: SplashScreen-Image reaches
`java -jar` and nothing else. The bundles get -splash: pointed at
these copies, which the input directory argument (dist) carries into
the application directory. -->
<copy todir="dist">
<fileset dir="src/my/stepss">
<include name="splash-460.png"/>
<include name="splash-460@2x.png"/>
</fileset>
</copy>
</target>
<!-- Runs after javac, once my.stepss.platform.Toolchain and friends are
compiled, and after stage-payloads (a -pre-compile dependency, so it
has already run by the time -post-compile does) has populated
src/my/stepss/payload/. Catches the case versions.properties' own
header used to invite: bumping a component's version there fetches
and stages the new asset under its new name and leaves the build
green, but Toolchain.java's hardcoded, version-suffixed resource
names still point at the old one, so every launch would fail with
"Missing bundled resource" once the jar shipped. See
PayloadManifestCheck's class comment for the full rationale. -->
<target name="-post-compile"
description="Fail the build if Toolchain.java's payload manifest and the staged payload directory disagree">
<java classname="my.stepss.platform.PayloadManifestCheck"
classpath="${build.classes.dir}"
fork="true" failonerror="true">
<arg value="src/my/stepss/payload"/>
<arg value="${examples.descriptor}"/>
</java>
</target>
<property file="versions.properties"/>
<property name="bundle.dir" location="bundle"/>
<!-- Empty means jpackage's platform default installer. CI also asks for
the app-image type, which is a directory that needs no installer. -->
<property name="bundle.type" value=""/>
<property name="payload.cache" location="payload-cache"/>
<!-- Names the bundled examples, the files each one is made of, and what the
dialog says about them. Read by ExamplesPack here and by the interface at
run time, so the packer and the dialog cannot disagree about what an
example contains. -->
<property name="examples.descriptor"
location="src/my/stepss/examples/examples.properties"/>
<macrodef name="fetch-asset">
<attribute name="repo"/>
<attribute name="tag"/>
<attribute name="asset"/>
<attribute name="sha256"/>
<sequential>
<mkdir dir="${payload.cache}"/>
<!-- Skip the network call entirely when the asset is already sitting in
the cache: `gh release download` still resolves the release against
api.github.com before deciding whether the "skip existing" flag
applies, so calling it unconditionally would mean every build needs
network and `gh` auth, not just the first one. The digest check
below still runs on both paths, so a corrupt or tampered cached file
fails the build exactly as it would after a fresh download. -->
<available file="${payload.cache}/@{asset}" property="@{asset}.cached"/>
<!-- The cache is keyed on filename only, and not every asset's filename
carries its version: helios' Linux and Windows assets, for example,
are named identically release to release. A cache hit can therefore
be a stale file left over from a previous version pin, sitting under
the name the current pin expects. Verify a cached file before
trusting it: a match means it's genuinely reusable (stop here, no
network); a mismatch means the file is stale, not corrupt or
tampered, so discard it and fall through to the fresh download below
instead of failing the build on what would otherwise look like a bad
release. verifyproperty (rather than the property-and-<equals> form
used for the final check below) is the natural fit for this
test-then-branch. -->
<checksum file="${payload.cache}/@{asset}" algorithm="SHA-256"
property="@{sha256}" verifyproperty="@{asset}.cache.ok"
if:set="@{asset}.cached"/>
<condition property="@{asset}.stale">
<and>
<isset property="@{asset}.cached"/>
<not><istrue value="${@{asset}.cache.ok}"/></not>
</and>
</condition>
<delete file="${payload.cache}/@{asset}" quiet="true" if:set="@{asset}.stale"/>
<!-- Re-check availability post-discard: the exec below (and the final
verification further down) gate on this, so a stale cache hit that
just got deleted behaves exactly like a cold cache miss from here
on. -->
<available file="${payload.cache}/@{asset}" property="@{asset}.still-cached"/>
<!-- SPS-L/@{repo} release assets live in a private repo: the plain
https://github.com/OWNER/REPO/releases/download/... URL only serves
browser sessions and returns 404 to anonymous or bearer-token requests
(verified). The GitHub REST API's asset-redirect endpoint accepts
token auth, but reaching it needs a JSON lookup of the numeric asset
id that stock Ant cannot do. `gh release download` performs that
lookup and the authenticated download in one step, reusing the
caller's existing `gh auth login` session. Requires: gh CLI installed
and authenticated with access to the SPS-L org. -->
<exec executable="gh" failonerror="true" failifexecutionfails="true"
unless:set="@{asset}.still-cached">
<arg value="release"/>
<arg value="download"/>
<arg value="@{tag}"/>
<arg value="--repo"/>
<arg value="@{repo}"/>
<arg value="--pattern"/>
<arg value="@{asset}"/>
<arg value="--dir"/>
<arg value="${payload.cache}"/>
<arg value="--skip-existing"/>
</exec>
<!-- Only re-hash when a download just happened above: a cache hit that
already matched the pinned digest is known-good from the check
above and doesn't need a second pass. -->
<checksum file="${payload.cache}/@{asset}" algorithm="SHA-256"
property="@{asset}.actual" unless:set="@{asset}.still-cached"/>
<fail message="Digest mismatch for @{asset}: expected @{sha256}, got ${@{asset}.actual}">
<condition>
<and>
<isset property="@{asset}.actual"/>
<not><isset property="@{asset}.stale"/></not>
<not><equals arg1="${@{asset}.actual}" arg2="@{sha256}"/></not>
</and>
</condition>
</fail>
<!-- Same check, but reached only after a stale cache entry was already
discarded and re-downloaded once: a mismatch here is not the stale
cache biting us a second time, it means the release itself changed
or the pin is wrong, so say that plainly instead of leaving it to
read like the plain case above. -->
<fail message="Digest mismatch for @{asset} even after discarding a stale cached copy and re-downloading fresh: expected @{sha256}, got ${@{asset}.actual}. This is not a stale-cache symptom -- the pinned digest or the released asset itself needs checking.">
<condition>
<and>
<isset property="@{asset}.stale"/>
<not><equals arg1="${@{asset}.actual}" arg2="@{sha256}"/></not>
</and>
</condition>
</fail>
<echo message="verified @{asset}"/>
</sequential>
</macrodef>
<target name="fetch-payloads" description="Download and verify pinned release payloads">
<fetch-asset repo="${ramses.repo}" tag="${ramses.tag}"
asset="${ramses.windows.asset}" sha256="${ramses.windows.sha256}"/>
<fetch-asset repo="${ramses.repo}" tag="${ramses.tag}"
asset="${ramses.linux.asset}" sha256="${ramses.linux.sha256}"/>
<fetch-asset repo="${ramses.repo}" tag="${ramses.tag}"
asset="${ramses.macos.asset}" sha256="${ramses.macos.sha256}"/>
<fetch-asset repo="${helios.repo}" tag="${helios.tag}"
asset="${helios.windows.asset}" sha256="${helios.windows.sha256}"/>
<fetch-asset repo="${helios.repo}" tag="${helios.tag}"
asset="${helios.linux.asset}" sha256="${helios.linux.sha256}"/>
<fetch-asset repo="${helios.repo}" tag="${helios.tag}"
asset="${helios.macos.asset}" sha256="${helios.macos.sha256}"/>
<fetch-asset repo="${dyngraph.repo}" tag="${dyngraph.tag}"
asset="${dyngraph.windows.asset}" sha256="${dyngraph.windows.sha256}"/>
<fetch-asset repo="${dyngraph.repo}" tag="${dyngraph.tag}"
asset="${dyngraph.linux.asset}" sha256="${dyngraph.linux.sha256}"/>
<fetch-asset repo="${dyngraph.repo}" tag="${dyngraph.tag}"
asset="${dyngraph.macos.asset}" sha256="${dyngraph.macos.sha256}"/>
<fetch-asset repo="${codegen.repo}" tag="${codegen.tag}"
asset="${codegen.windows.asset}" sha256="${codegen.windows.sha256}"/>
<fetch-asset repo="${codegen.repo}" tag="${codegen.tag}"
asset="${codegen.linux.asset}" sha256="${codegen.linux.sha256}"/>
<fetch-asset repo="${codegen.repo}" tag="${codegen.tag}"
asset="${codegen.macos.asset}" sha256="${codegen.macos.sha256}"/>
</target>
<target name="fetch-uramses"
description="Download the pinned stepss-uramses source archive (public, no gh needed)">
<mkdir dir="${payload.cache}"/>
<available file="${payload.cache}/stepss-uramses-${uramses.version}.zip"
property="uramses.cached"/>
<get src="${uramses.source.url}"
dest="${payload.cache}/stepss-uramses-${uramses.version}.zip"
verbose="true" unless:set="uramses.cached"/>
</target>
<!-- Like fetch-uramses: the example repositories are public, so this is a
plain HTTPS GET with no gh and no STEPSS_TOKEN. The cached name carries
the version, so a bump downloads afresh rather than reusing the previous
release's archive under the same name. No digest check here on purpose -
ExamplesPack verifies by content manifest, and the archive's own bytes are
not stable enough to check (see versions.properties). -->
<macrodef name="fetch-example">
<attribute name="id"/>
<attribute name="version"/>
<attribute name="url"/>
<sequential>
<mkdir dir="${payload.cache}"/>
<available file="${payload.cache}/example-@{id}-@{version}.zip"
property="example.@{id}.cached"/>
<get src="@{url}" dest="${payload.cache}/example-@{id}-@{version}.zip"
verbose="true" unless:set="example.@{id}.cached"/>
</sequential>
</macrodef>
<!-- Filters one example archive down to the files its descriptor entry names,
verifies the content manifest against the pin, and writes the payload the
jar ships. Fails the build, naming the file, if the pinned release stopped
carrying something the descriptor names: that turns a bad upstream release
into a red build instead of a menu entry that opens onto missing slots. -->
<macrodef name="pack-example">
<attribute name="id"/>
<attribute name="version"/>
<attribute name="sha256"/>
<sequential>
<java classname="my.stepss.examples.ExamplesPack"
classpath="${build.classes.dir}" fork="true" failonerror="true">
<arg value="${examples.descriptor}"/>
<arg value="@{id}"/>
<arg value="${payload.cache}/example-@{id}-@{version}.zip"/>
<arg value="src/my/stepss/payload/example-@{id}.zip"/>
<arg value="@{sha256}"/>
</java>
</sequential>
</macrodef>
<target name="fetch-examples"
description="Download the pinned example test system archives (public, no gh needed)">
<fetch-example id="kundur" version="${kundur.version}" url="${kundur.source.url}"/>
<fetch-example id="nordic" version="${nordic.version}" url="${nordic.source.url}"/>
<fetch-example id="five-bus" version="${five-bus.version}" url="${five-bus.source.url}"/>
<fetch-example id="six-bus" version="${six-bus.version}" url="${six-bus.source.url}"/>
</target>
<!-- `init` is build-impl.xml's property-loading chain, and this target needs
it for ${build.classes.dir}, ${javac.source} and ${javac.target}. Reached
through -pre-compile it would already have run, but this target carries a
`description`, so `ant -p` advertises it and `ant stage-payloads` is a
reasonable thing to type. Without init that run deletes the staged
payloads, copies the component ones back, and then dies on `invalid
target release: ${javac.target}` before UramsesKitPack repacks the kit -
leaving the tree short one payload with an error naming none of that. -->
<target name="stage-payloads" depends="init,fetch-payloads,fetch-uramses,fetch-examples"
description="Copy verified payloads into the source tree for packaging">
<!-- Nothing else prunes this directory, so a stale payload left over from
a previous version pin (e.g. after bumping versions.properties) would
otherwise survive indefinitely and ship in the jar alongside the
current one. Clearing it here means it always reflects exactly the
pinned set in payload-cache/, never a superset. This only touches the
staged copies under src/my/stepss/payload/ - payload-cache/ itself
(the download cache) is left untouched. -->
<delete dir="src/my/stepss/payload" quiet="true"/>
<mkdir dir="src/my/stepss/payload"/>
<!-- Named one by one from versions.properties rather than matched by glob.
payload-cache/ deliberately keeps superseded downloads so an offline
build still works after a version bump, and a glob like "dyngraph-linux-*"
cannot tell the pinned asset from the one it replaced: bumping dyngraph
1.1.0 to 1.2.0 staged both, and the jar shipped a dead copy of the old
one. The manifest check does not catch it either, since it only asserts
that every resource Toolchain.java names is present, not that nothing
else is. Adding a component means adding its lines here. -->
<copy todir="src/my/stepss/payload" flatten="true">
<fileset dir="${payload.cache}">
<include name="${ramses.windows.asset}"/>
<include name="${ramses.linux.asset}"/>
<include name="${ramses.macos.asset}"/>
<include name="${helios.windows.asset}"/>
<include name="${helios.linux.asset}"/>
<include name="${helios.macos.asset}"/>
<include name="${dyngraph.windows.asset}"/>
<include name="${dyngraph.linux.asset}"/>
<include name="${dyngraph.macos.asset}"/>
<include name="${codegen.windows.asset}"/>
<include name="${codegen.linux.asset}"/>
<include name="${codegen.macos.asset}"/>
</fileset>
</copy>
<mkdir dir="${build.classes.dir}"/>
<javac srcdir="src" destdir="${build.classes.dir}" includeantruntime="false"
source="${javac.source}" target="${javac.target}"
includes="my/stepss/platform/UramsesKitPack.java,my/stepss/examples/ExamplesPack.java"/>
<java classname="my.stepss.platform.UramsesKitPack"
classpath="${build.classes.dir}" fork="true" failonerror="true">
<arg value="${payload.cache}/stepss-uramses-${uramses.version}.zip"/>
<arg value="src/my/stepss/payload/uramses-kit-v${uramses.version}.zip"/>
<arg value="${uramses.manifest.sha256}"/>
</java>
<!-- Unlike the component payloads above, these are not copied from the cache
by name: the packer reads the downloaded archive and writes the filtered
payload straight into the staged directory, so there is no cached file
under the shipped name that a stale copy could be mistaken for. -->
<pack-example id="kundur" version="${kundur.version}"
sha256="${kundur.manifest.sha256}"/>
<pack-example id="nordic" version="${nordic.version}"
sha256="${nordic.manifest.sha256}"/>
<pack-example id="five-bus" version="${five-bus.version}"
sha256="${five-bus.manifest.sha256}"/>
<pack-example id="six-bus" version="${six-bus.version}"
sha256="${six-bus.manifest.sha256}"/>
</target>
<target name="-pre-compile" depends="stage-payloads"/>
<!-- Native bundles: the jar wrapped in a launcher, an icon and a runtime, so
STEPSS installs and starts like an application instead of being run from
a terminal. jpackage builds for the platform it runs on and no other, so
the three installers come from the three CI runners, not from here.
It packages dist/, which is the fat jar plus dist/lib/, rather than
re-resolving dependencies: whatever `ant jar` produced and the harnesses
passed against is exactly what ships, and there is no second definition
of the classpath to drift.
A trimmed runtime is deliberately not used. jlink needs the module graph
of everything reachable, and the toolchain this jar extracts and runs is
native executables invoked through Commons Exec, so what a module scan
finds and what the application actually needs are not the same question.
A full runtime costs about 40MB on top of a 34MB jar and removes the
class of failure where a bundle starts and then cannot find a class. -->
<target name="bundle" depends="jar"
description="Build a native installer for the platform this runs on">
<mkdir dir="${bundle.dir}"/>
<delete dir="${bundle.dir}/work" quiet="true"/>
<mkdir dir="${bundle.dir}/work"/>
<loadfile property="stepss.version" srcFile="src/my/stepss/version.txt">
<filterchain><striplinebreaks/></filterchain>
</loadfile>
<!-- One icon format per platform. The mac test comes first because macOS
answers to the unix family too. stepss.icns is not in the repository:
a real icns container needs iconutil, which only exists on macOS, so
the mac CI job builds one from the PNG just before this runs. When it
is absent the icon argument is dropped rather than passed a file
jpackage would reject, and the bundle gets the default icon. -->
<condition property="bundle.icon.file" value="packaging/stepss.icns">
<os family="mac"/>
</condition>
<condition property="bundle.icon.file" value="packaging/stepss.ico">
<os family="windows"/>
</condition>
<property name="bundle.icon.file" value="packaging/stepss.png"/>
<condition property="bundle.icon.arg" value="--icon ${bundle.icon.file}">
<available file="${bundle.icon.file}"/>
</condition>
<property name="bundle.icon.arg" value=""/>
<!-- Menu entries and shortcuts, so an installed application is reachable
from where the desktop keeps applications rather than only from the
directory it was unpacked into.
None of these apply to the app-image type, which is a plain directory
and not an installer: jpackage rejects every windows and linux option
below with "not valid with type [APP_IMAGE]" and fails the build. This
condition is first because ant properties are immutable, so the first
one that matches wins over the per-platform ones underneath. -->
<condition property="bundle.platform.args" value="">
<contains string="${bundle.type}" substring="app-image"/>
</condition>
<condition property="bundle.platform.args" value="">
<os family="mac"/>
</condition>
<!-- The upgrade uuid is the MSI UpgradeCode: the GUID by which Windows
Installer recognises two packages as the same product, one superseding
the other. Left unset, jpackage generates a fresh random one on every
build, and it is the only random GUID in the file - every other one it
writes is name-derived and reproducible. So each release was a distinct
product that installed BESIDE its predecessor rather than replacing it,
and nothing looked wrong from the outside: ProductCode is also supposed
to change per version, so the two differing GUIDs looked alike. Anyone
who took v3.74.9 and then v3.74.10 has both, twice over in Add or Remove
Programs and twice over on disk at ~200MB each.
THIS VALUE MUST NEVER CHANGE. Editing it is equivalent to renaming the
product: every installation already out there stops being recognised as
STEPSS, and the next release installs beside it exactly as before. -->
<condition property="bundle.platform.args"
value="--win-shortcut --win-menu --win-dir-chooser --win-menu-group STEPSS --win-upgrade-uuid 1a830fb8-fd91-4606-b544-df3f47211ce2">
<os family="windows"/>
</condition>
<!-- What the toolchain links against, which jpackage cannot work out for
itself. It derives Depends: by asking which packages own the shared
libraries the APP IMAGE needs - the launcher, the bundled JRE and the
jar - and the engines are none of those: they travel as .tar.gz
resources inside stepss.jar and are extracted at run time. So the
generated list is a dependency list for Swing, and `apt install stepss`
used to succeed, open a window, and fail on the first simulation.
"objdump -p" on the extracted ramses is the source of the first three:
libopenblas.so.0 libgfortran.so.5 libgomp.so.1
libm.so.6 libc.so.6 libgcc_s.so.1
Only RAMSES is affected; helios, dyngraph and CODEGEN are all statically
linked. libc6 and libgcc-s1 are already detected from the JRE, but
libgcc-s1 is named here anyway rather than relied on, because that
detection is about the runtime and this line is about the engine. -->
<property name="bundle.linux.deps"
value="libgfortran5, libgomp1, libopenblas0, libgcc-s1"/>
<condition property="bundle.platform.args"
value="--linux-shortcut --linux-menu-group Science --linux-package-name stepss --linux-app-category science --linux-package-deps '${bundle.linux.deps}'">
<os family="unix"/>
</condition>
<property name="bundle.platform.args" value=""/>
<!-- Linux only. Six of jpackage's own templates, each its original with a
few lines changed, so that everything else stays generated and a JDK
that reworks one of them conflicts in one place:
STEPSS.desktop adds StartupWMClass. Java derives WM_CLASS from the main
class, so the window announces itself as
my-stepss-StepssUI while the menu entry is
stepss-STEPSS.desktop; with nothing to connect them the
shell shows a generic cog in the dock beside a window
whose own title bar has the right icon.
control sets Maintainer, and adds Recommends:, which no
command-line option can express. jpackage builds
Maintainer: out of the vendor option plus the
linux-deb-maintainer one, giving "...Cyprus University
of Technology <Unknown>": an address that is not
one, and a vendor string that is a sentence. The vendor
is shared with the Windows and macOS bundles, where the
full lab name is the right thing to show, so the short
form cannot come from there. Recommends: is the Fortran
toolchain that CODEGEN needs to compile custom models
and nothing else does - installed by default,
declinable, which is exactly what
getting-started/installation.mdx describes.
copyright replaces a stub that read "Copyright (C) 2026 / License:
Unknown" with a stanza per bundled component. An
archive hands this package to people who never saw a
release page, and RAMSES is proprietary and
non-commercial-only.
postinst add /usr/bin/stepss and the /usr/share/doc/stepss/
postrm copyright link, and remove them again. Neither is
owned by dpkg, because jpackage can only place files
under the install directory.
postinst stop xdg-desktop-menu from being fatal. It exits 3 with
prerm "No writable system menu directory found" wherever
there is no desktop, and both scripts run under
"set -e", so installing on a server, in a container or
under WSL failed outright and left dpkg holding a
half-configured package - and removing it then failed
the same way. A container is the only place that
reproduces this; a workstation never will, which is
what tools/deb-harness.sh is for.
Everything under packaging/linux is passed as one directory, so a file
added there is picked up by name with no change here. -->
<condition property="bundle.resource.arg" value="--resource-dir packaging/linux">
<and>
<os family="unix"/>
<not><os family="mac"/></not>
</and>
</condition>
<property name="bundle.resource.arg" value=""/>
<!-- macOS Developer ID signing. Absent when mac.signing.keychain is unset,
so a local build on a developer's Mac still produces an unsigned bundle
rather than failing for want of a certificate: only the release CI has
the identity, and it passes both properties together.
The identity is passed with its "Developer ID Application: " prefix
already on it. jpackage accepts either form, and its help text asks for
the team name alone, but the no-prefix path builds the search key by
concatenating its own constant, which has no trailing space, onto the
name. Whether the result still matches what `security` prints was not
something the option probe could test, having no certificate to sign
with; passing the full string takes that path out of play. See
docs/superpowers/notes/2026-09-22-jpackage-mac-options.md.
No mac-entitlements option. jpackage's own default entitlements already
carry com.apple.security.cs.disable-library-validation, which is what
lets the app load Homebrew's gfortran, alongside the JVM's allow-jit
and allow-unsigned-executable-memory. A custom file replaces all of
them rather than adding to them, so the release workflow asserts the
key is present on the built app instead of dictating the whole list.
The package identifier is written out rather than left to default to
the main class name, for the reason the win-upgrade-uuid comment above
gives: an identifier the operating system uses to recognise one
installation as the successor of another must not move because
somebody renamed a package. jpackage defaults it to the main class,
so my.stepss.StepssUI is what the derivation produced up to and
including v3.81.2 and what every installation out there carries. It
is written out here as exactly that value, unchanged, so that pinning
it costs nothing: a pin that differs from the field is not a pin but
a rename, performed on the release that introduces it.
THIS VALUE MUST NOT CHANGE, and in particular must not be shortened
to follow the my.stepss package. macOS keys Launch Services and the
privacy consents a user has granted on the bundle identifier, so a
new value is a different application rather than a successor: the
user is asked for every permission again and the association with
the installation they already have is lost, silently and on every
Mac at once. A package rename is not a reason to touch it; that is
the whole point of it being here. -->
<condition property="bundle.sign.args"
value="--mac-sign --mac-signing-key-user-name '${mac.signing.identity}' --mac-signing-keychain '${mac.signing.keychain}' --mac-package-identifier my.stepss.StepssUI">
<and>
<os family="mac"/>
<isset property="mac.signing.keychain"/>
</and>
</condition>
<property name="bundle.sign.args" value=""/>
<exec executable="jpackage" failonerror="true">
<arg line="--name STEPSS"/>
<arg line="--app-version ${stepss.version}"/>
<arg line="--vendor 'Sustainable Power Systems Laboratory, Cyprus University of Technology'"/>
<arg line="--description 'Static and Transient Electric Power Systems Simulation'"/>
<arg line="--input dist"/>
<arg line="--main-jar stepss.jar"/>
<arg line="--main-class my.stepss.StepssUI"/>
<!-- $APPDIR is jpackage's own token, substituted identically on Windows,
macOS and Linux, so this one line is the splash on all three. The @2x
file beside it is picked up by the JDK's naming convention with no
further argument. -->
<arg line="--java-options -splash:$APPDIR/splash-460.png"/>
<arg line="${bundle.icon.arg}"/>
<arg line="--dest ${bundle.dir}"/>
<arg line="--temp ${bundle.dir}/work"/>
<arg line="${bundle.platform.args}"/>
<arg line="${bundle.sign.args}"/>
<arg line="${bundle.resource.arg}"/>
<arg line="${bundle.type}"/>
</exec>
<echo>Bundles written to ${bundle.dir}</echo>
</target>
<target name="-post-clean" description="Remove staged payload copies so ant clean leaves no residue">
<delete dir="src/my/stepss/payload" quiet="true"/>
</target>
</project>