The Aussom platform installer is a feature of apac that packages your Aussom
application into a native installer for Linux (.deb, .rpm), Windows
(.msi, .exe), and macOS (.pkg, .dmg). The produced installer carries
its own JVM, the Aussom CLI runtime, and your .aus source files. End users
double-click the installer and get a native app; they do not need Java or
Aussom installed first.
This guide starts with the everyday flow (apac -ii to scaffold,
apac -ib to build) and progresses to the per-platform details you reach
for once the basic build works.
installer: block in
package.yaml, alongside the rest of your apac project config| Concept | Meaning |
|---|---|
apac -ii |
"Installer init" - scaffolds the installer: block in package.yaml plus a package-files/ directory tree. Run once. |
apac -ib |
"Installer build" - reads the config, stages everything jpackage needs, runs jpackage, and drops the produced installer in target/installer/<plat>/. |
installer: block |
A new top-level key inside package.yaml. Holds the app name, version, platform sections, and everything jpackage needs to know. |
package-files/ |
A directory next to package.yaml. Holds icons, license text, optional Debian maintainer scripts, native libraries, and per-platform jpackage overrides. |
| Universal fields | Fields inside installer: that apply to every platform (appName, vendor, mainScript, etc.). |
| Per-platform section | linux:, windows:, or macos: - holds settings that vary by OS (installer types, signing, shortcuts, etc.). |
| jpackage | The JDK tool that actually produces the native installer. apac wraps it and stages the inputs. |
| Fat vs core jar | Two flavors of the Aussom CLI jar. The fat jar (~90 MB) includes JavaFX; the core jar (~20 MB) does not. apac picks one per build based on bundle.fx. |
| GTK4 native cluster | The set of native libraries GTK4 needs at runtime (libgtk-4, libglib-2.0, libcairo, libpango-1.0, libharfbuzz, and their transitive deps). On macOS and Windows, apac copies this cluster out of the Aussom CLI install when bundle.gtk4: true. On Linux it relies on the system-installed GTK4. |
| Bundled runtime | The trimmed JDK image that ships inside your installer. apac reuses the runtime apac itself is running on, so the same JDK version end users get matches what built the app. |
These two commands cover the happy path:
apac -ii "MyApp" # scaffold installer config + package-files/ tree
apac -ib # validate, stage, and run jpackage
apac -ii takes one required argument: the user-facing application name
(quoted if it has spaces). It writes the installer: block into the
project's package.yaml, generating a minimal package.yaml first if one
does not exist. It also creates the package-files/ directory next to it.
apac -ib reads the block, validates the inputs, stages everything jpackage
needs under target/staging/<plat>/, and runs jpackage once per requested
installer type. Output lands in target/installer/<plat>/.
By default apac -ib builds for the current OS only. Pass -t <type> to
filter the type list (for example -t deb on Linux).
After apac -ii "MyApp" in an empty directory:
package.yaml # extended with the installer: block
package-files/
README.md # in-tree docs about what each subdir is for
license.txt # copy of LICENSE.txt if present, else placeholder
icons/
.gitkeep # drop app.png / app.ico / app.icns here
deb/
postinst # bash skeleton w/ /usr/bin/<slug> symlink, executable bit set
prerm # bash skeleton that removes the symlink, executable bit set
msi/wix-overrides/
.gitkeep # drop WiX overrides here
pkg/
postinstall # bash skeleton w/ /usr/local/bin/<slug> symlink, executable bit set
dmg/
.gitkeep # drop macOS .dmg overrides here
lib/
.gitkeep # drop platform-specific native libs here
extra-licenses/
.gitkeep # one <jarname>.txt per bundle.extraJars entry
The empty .gitkeep files mark directories that ship empty by default.
For deb/ and dmg/, apac only wires the directory into jpackage via
--resource-dir when it has actionable content (a file other than
.gitkeep or README.md); an empty dir is silently skipped so
jpackage uses its built-in defaults. For msi/wix-overrides/ and
pkg/ the behavior is different — apac always stages a default WiX
template (MSI / EXE) or postinstall script (PKG / DMG) at build
time so a freshly-scaffolded project gets the PATH integration and
the /usr/local/bin/<slug> symlink without any extra setup. Anything
the user drops in those directories overlays on top.
This walkthrough starts from an empty directory and ends with a working
.deb you can install on Debian or Ubuntu.
Create a project directory and scaffold the installer config:
mkdir hello-world
cd hello-world
apac -ii "MyApp"
This generates package.yaml (with sensible defaults) and the
package-files/ directory. The scaffolded mainScript defaults to
myapp.aus — the slug-form of "MyApp" (lowercased, with
non-alphanumeric runs collapsed to single hyphens) — so the entry
script, the deb package name, and the installed launcher path all
share one name. For an appName with spaces, like "Hello JavaFX",
the slug would be hello-javafx and the scaffolded mainScript
would be hello-javafx.aus.
Create that entry script:
cat > myapp.aus <<'EOF'
class Hello {
public Hello() { }
public main() {
c.println("hello from MyApp");
}
}
EOF
Build:
apac -ib
apac prints progress through each phase, then jpackage takes over and
produces the .deb under target/installer/linux/. Install it with
sudo dpkg -i target/installer/linux/myapp_1.0.0_amd64.deb, then run
myapp from any shell to see hello from MyApp. The /usr/bin/myapp
entry comes from the scaffolded package-files/deb/postinst.
The scaffold leaves the icon path commented out in package.yaml. To turn
it on, drop an icon into package-files/icons/ and uncomment the matching
line in the platform section.
For Linux:
linux:
# icon: package-files/icons/app.png # uncomment + drop a 1024x1024 PNG here
types:
- deb
After uncommenting and providing package-files/icons/app.png, re-run
apac -ib. The produced .deb now installs with the icon registered in
the desktop menu.
Use app.png for Linux, app.ico for Windows (multi-resolution), and
app.icns for macOS. Tools like ImageMagick and png2icns convert
between them.
By default the produced installer ships the slim core Aussom CLI jar
without the JavaFX ecosystem. If your app uses include fx.*; anywhere,
turn on FX in each platform section that needs it:
linux:
types:
- deb
bundle:
fx: true # use the fat Aussom CLI jar with JavaFX shaded in
The bundle gets roughly 70 MB larger. On macOS, apac does NOT add
-XstartOnFirstThread for FX-only apps — JavaFX Glass spawns its
own Cocoa main-loop thread and the FX window fails to appear when
the flag is set. (GTK4 needs the opposite — see Example 4.)
If your app uses include gtk.gtk; (or any other generated GTK4
binding), the Aussom CLI's binding code is already inside the CLI jar -
but the GTK4 native libraries it calls into need to be present on the
end user's machine. Linux end users almost always have GTK4 installed
through the distro package manager; macOS and Windows users typically
do not.
Turn on bundle.gtk4 for the platforms where you want apac to ship
the GTK4 native cluster inside the installer:
linux:
types:
- deb
bundle:
gtk4: true # accepted but no-op; deb depends on libgtk-4-1
windows:
types:
- msi
bundle:
gtk4: true # apac copies the GTK4 DLL cluster into the bundle
macos:
types:
- pkg
bundle:
gtk4: true # apac copies the GTK4 dylib cluster into the bundle
On macOS and Windows builds, apac reads a per-platform manifest shipped
inside the Aussom CLI install at
$APPDIR/packaging/gtk4-libs-macos.txt (or gtk4-libs-windows.txt),
matches each glob in the manifest against the files in the CLI's
lib/ directory, and copies the matches into your installer's lib/.
The macOS bundle gains roughly 100 MB; the Windows bundle gains
roughly 80 MB.
On Linux, bundle.gtk4: true is accepted but no-op — no libs are
copied. End users are expected to have GTK4 already installed via
their distro's package manager (libgtk-4-1 on Debian / Ubuntu,
gtk4 on Fedora / Arch); the system loader resolves the imports at
runtime. apac does not add this as a declared package dependency to
the produced .deb or .rpm — if you want that, list it in
linux.raw (e.g. ["--linux-package-deps", "libgtk-4-1"]).
You do not need to manage the per-lib list yourself. When the Aussom
CLI ships a new GTK4 dependency, the manifest is updated in the same
release, and your next apac -ib picks the new file up automatically.
apac -ib defaults to every type listed under the current platform. Pass
-t to narrow to one:
apac -ib -t deb # only the .deb, even if rpm is also listed
apac -ib -t app-image # plain runtime image, no installer
app-image is the fastest type to iterate on - no installer wrapping, no
host packaging tools needed (no dpkg-deb, no WiX, no pkgbuild). The
output is a directory you can launch directly.
The scaffold drops a postinst and prerm into package-files/deb/
that come pre-wired to symlink the bundled launcher into /usr/bin/,
so the app shows up on $PATH for every shell as soon as the .deb
installs:
#!/bin/sh
# postinst - Debian package hook for this app.
# Runs as root. Edit as needed.
set -e
ln -s /opt/hello-javafx/bin/hello-javafx /usr/bin/hello-javafx
exit 0
#!/bin/sh
# prerm - Debian package hook for this app.
# Runs as root. Edit as needed.
set -e
rm /usr/bin/hello-javafx
exit 0
The path is derived from your appName: APAC lowercases it and folds
non-alphanumeric runs to hyphens, matching the install path jpackage
produces (/opt/<slug>/). For appName: "Hello JavaFX" the slug is
hello-javafx; for appName: "hellojavafx" it stays hellojavafx.
To layer extra install-time steps on top of the symlink, edit the
scaffolded postinst in place and add your commands above exit 0.
apac wires the whole package-files/deb/ directory to jpackage via
--resource-dir. jpackage looks up these filenames inside that directory:
postinst, prerm, preinst, postrm, control, copyright. Any file
you supply replaces the matching jpackage default. Important: the
scaffolded postinst IS already a replacement for jpackage's default,
so anything jpackage's default would have run (notably
xdg-desktop-menu install for .desktop registration) does not happen
unless you add it back. See the
jpackage override-resources docs
for the list of behaviors jpackage's bundled defaults provide.
These keys live at the top of the installer: block and apply to every
platform.
| Field | Required | Description |
|---|---|---|
enabled |
no | Set to false to disable the installer entirely without removing the block. Defaults to true. |
appName |
yes | User-facing application name. Used for the launcher name and the installer title. Quote it if it has spaces. Does not inherit from the package's name: field. |
version |
no | App version. Defaults to the top-level package version: when omitted. |
vendor |
yes | Company or author name shown in the OS package metadata. |
copyright |
yes | One-line copyright notice. |
description |
yes | Short product description shown by the OS package tools. |
mainScript |
yes | Bare filename of the entry .aus (no path). apac stages the directory holding this file as the app's root. The scaffolded default is <appName-slug>.aus (e.g. appName: "Hello JavaFX" → hello-javafx.aus). |
appArgs |
no | List of CLI arguments appended after the mainScript path when the launcher runs. |
javaOptions |
no | List of JVM arguments to pass to the bundled runtime. Per-platform javaOptions lists are appended to this one at build time. |
mainScript looks up the entry script in two places, in this order:
src/main/aus/<package-name>/<mainScript> (Maven-style layout), then
<cwd>/<mainScript> (flat layout). The first match wins. The script's
parent directory becomes the staged app/ root, so any sibling .aus
files travel with it.
Each platform section holds the fields that vary by OS. linux:,
windows:, and macos: are independent - declare only the ones you want
to build for.
These fields appear under every platform section.
| Field | Description |
|---|---|
types |
List of installer types to build for this platform. At least one is required. See per-platform tables below for valid values. |
icon |
Path to the icon file. Optional; jpackage uses a generic icon when absent. The scaffold leaves this commented out with a hint at the conventional location. |
shortcut |
Boolean. Linux: write a .desktop entry. Windows: write desktop + start-menu shortcuts. Ignored on macOS (apps install to /Applications). |
console |
Windows only. When true, apac passes --win-console to jpackage so the launcher attaches a console window and the app's stdout/stderr are visible. Scaffolded default true for CLI apps; flip to false for pure-GUI Windows apps. Ignored on Linux and macOS. |
fileAssociations |
List of {extension, description, mimeType} entries. apac synthesizes one --file-associations properties file per entry. |
launchers |
Additional native launchers next to the main one. Each entry needs a name and mainScript; optional appArgs, icon, description, javaOptions. |
javaOptions |
JVM options appended to the universal javaOptions list for this platform only. |
bundle.fx |
Bundle the JavaFX ecosystem. Defaults to false (smaller bundle). |
bundle.gtk4 |
macOS and Windows: copy the GTK4 native lib cluster out of the Aussom CLI install into the produced installer. Accepted on Linux but no-op (Linux uses system GTK4). Defaults to false. See Example 4. |
bundle.extraJars |
List of paths to third-party JARs to drop into the installer's libs/ directory. See Bundling Third-Party Jars. |
bundle.extraResources |
List of directories to copy verbatim into the staging tree. Useful for embedded data, templates, or config trees. |
signing.enabled |
Turn on code signing. macOS only. Setting it true on Windows currently fails validation; see Code Signing. |
signing.identity |
Platform-specific signing identity (Apple Developer ID on macOS). |
raw |
List of literal extra jpackage CLI args appended to that platform's invocation. Escape hatch for anything apac does not surface by name. |
Valid types: deb, rpm, app-image.
| Field | Description |
|---|---|
| (inherits all shared fields above) |
Host tools required: dpkg-deb and fakeroot for .deb; rpmbuild for
.rpm. Both ship in the standard distro repositories. app-image
requires no extra host tools.
Drop maintainer scripts and templates into package-files/deb/:
postinst, prerm, preinst, postrm, control, copyright. Each
filename is literal and case-sensitive. See Example 6 for the override
contract.
Valid types: msi, exe, app-image.
| Field | Description |
|---|---|
console |
Scaffolded to true because Aussom apps are predominantly CLI tools. With console: true apac passes --win-console to jpackage and the launcher attaches a visible terminal — every c.println / c.err line appears in that window. Flip to false for pure-GUI apps that should run windowed with no console attached. |
signing.identity |
Currently rejected at validation time (jpackage 23 does not expose a Windows code-signing flag). Sign the produced .msi or .exe externally with signtool.exe. |
Out of the box, the produced MSI / EXE installer also adds the install
directory to the per-user PATH environment variable. Once installed,
the user can invoke the app from any cmd / PowerShell by typing the
appName (the launcher exe is <AppName>.exe under
C:\Program Files\<AppName>\). This mirrors the Linux deb's
/usr/bin/<slug> symlink and the macOS pkg's /usr/local/bin/<slug>
symlink.
apac wires this by staging a default main.wxs template into a
generated resource directory when package-files/msi/wix-overrides/
holds only marker files. The template mirrors jpackage's bundled
default and adds a single <Component> with a WiX <Environment>
entry that appends [INSTALLDIR] to the per-user PATH (HKCU,
removed on uninstall). The component's GUID is derived
deterministically from the appName via a Type-3 UUID so MSI upgrades
match the prior install's component identity.
The moment you drop ANY non-marker file into
package-files/msi/wix-overrides/, apac switches over to passing
that directory to jpackage and stops staging its default. Drop a
fresh main.wxs to fully replace the WiX project; you own PATH
wiring (and everything else) from that point.
Host tools required: WiX 3.x. WiX 4 and 5 are not compatible with the
JDK 23 jpackage and apac's host-tool check rejects them with a pointer to
the WiX 3.14 download. Make sure WiX 3.14's bin/ directory is first on
PATH (the executables are candle.exe and light.exe).
Valid types: pkg, dmg, app-image.
| Field | Description |
|---|---|
signing.identity |
Apple Developer ID identity (the certificate's "common name"). Passed to jpackage as --mac-signing-key-user-name when signing.enabled: true. |
Host tools required: pkgbuild and hdiutil. Both ship with Xcode
command-line tools.
apac auto-injects -XstartOnFirstThread into the JVM args when
macos.bundle.gtk4: true, NEVER for FX-only apps. The two macOS
UI stacks disagree about thread-0 ownership:
gtk_window_present aborts with 'NSWindow should only be instantiated on the main thread!'.For hybrid FX + GTK4 builds (uncommon) apac still adds the flag
because GTK's failure is a hard crash while FX's is a soft
no-window. If you also list -XstartOnFirstThread in
macos.javaOptions, the dedup check (whitespace-insensitive) makes
sure only one copy reaches the produced launcher.
apac also sets two macOS-specific jpackage flags for every macOS
build so each produced .app has a distinct LaunchServices /
NSApplication identity:
| Flag | apac default | Why |
|---|---|---|
--mac-package-identifier |
the appName slug (e.g. hello-javafx) |
Becomes CFBundleIdentifier. Without an explicit value, jpackage falls back to --main-class, which is ALWAYS com.lehman.aussom.Main for apac-built apps. Every apac-produced .app on the same machine would then share that identifier, and macOS would treat them all as the same registered app — JavaFX windows fail to appear (Glass calls NSApplication, gets the wrong bundle state back, nothing shows). |
--mac-package-name |
the case-preserving sanitized appName, truncated to 15 chars | Becomes CFBundleName (the menu-bar title). jpackage rejects names of 16 chars or longer with this flag, so apac always truncates. |
To override either, list the flag and value in macos.raw:
installer:
macos:
raw:
- "--mac-package-identifier"
- "com.acme.myapp"
apac detects the user-supplied value in raw and skips its own
auto-add for that flag so jpackage doesn't see a duplicate.
Notarization is out of scope. If you need it, run xcrun notarytool
yourself against the produced .pkg.
Drop installer assets (background image, custom welcome text) into
package-files/pkg/ for .pkg builds or package-files/dmg/ for .dmg
builds.
A working postinstall script ships with every produced .pkg
out of the box. It runs as root after macOS Installer has placed
the app bundle at /Applications/<AppName>.app and creates a
symlink so the app shows up on $PATH for every shell. For an
appName of "Hello JavaFX", the rendered body is:
#!/bin/sh
# postinstall - macOS pkg post-install hook for Hello-JavaFX.
# Runs as root after macOS Installer has placed the app bundle
# at /Applications/Hello-JavaFX.app. Edit by dropping your own
# postinstall into package-files/pkg/ (your copy wins on
# collision with the apac default).
set -e
APP="/Applications/Hello-JavaFX.app"
# PATH symlink. ln -sf overwrites any prior symlink so a
# re-install or upgrade refreshes the link without erroring.
ln -sf "$APP/Contents/MacOS/Hello-JavaFX" /usr/local/bin/hello-javafx
exit 0
APP uses the case-preserving sanitized appName (matches the .app
bundle and launcher binary jpackage produces); the /usr/local/bin/
link uses the lowercase slug for a shell-friendly command name,
matching the Linux deb's /usr/bin/<slug>.
apac -ii also drops a copy of this script at
package-files/pkg/postinstall so the default is visible and
editable in your project. apac stages the default at build time
regardless — so older projects that were scaffolded before the
postinstall existed still get the symlink without re-running
apac -ii. To customize, edit the file in package-files/pkg/ (or
drop a fresh postinstall there); your copy wins on collision with
the apac default. Any other files you drop alongside (background
image, license RTF, etc.) ride along into jpackage's resource
directory unchanged.
Each subdirectory under package-files/ has a specific contract.
| Subdir | What goes in it |
|---|---|
icons/ |
App icons. Reference them via the per-platform icon: field. apac does not pick a default; if icon: is unset, jpackage uses its generic icon. |
deb/ |
Debian maintainer scripts (postinst, prerm, preinst, postrm) and control templates (control, copyright). Filenames are literal. |
msi/wix-overrides/ |
WiX template overrides for the Windows MSI build. |
pkg/ |
macOS .pkg installer assets, plus the scaffolded postinstall script (literal filename — pkgbuild runs it as root during install). |
dmg/ |
macOS .dmg installer assets. |
lib/ |
Platform-specific native libraries (.so, .dll, .dylib) the app loads at runtime. |
extra-licenses/ |
One <jarname>.txt per entry in bundle.extraJars. apac concatenates these into THIRD_PARTY_LICENSES.txt. |
For deb/ and dmg/, apac wires the directory to jpackage via
--resource-dir only when it contains a file other than .gitkeep
or README.md; an empty directory is silently skipped and jpackage
uses its built-in defaults. For msi/wix-overrides/ and pkg/, apac
always passes a --resource-dir — when the directory is empty, apac
stages its own defaults (the WiX template that adds INSTALLDIR to the
per-user PATH on Windows, and the postinstall that symlinks the
launcher into /usr/local/bin/<slug> on macOS); user files overlay
on top.
Native libraries (.so on Linux, .dll on Windows, .dylib on macOS)
are not a schema concern. apac has no opinion on which libraries your app
needs - that depends on the host platform, architecture, and runtime
version, none of which apac can read from package.yaml.
Convention: drop the libraries for the current build target into
package-files/lib/ and apac copies the contents verbatim into the
installer's lib/ directory (skipping the .gitkeep marker). The
installed app's launcher arranges for that directory to be on the
system loader's search path so the libs resolve at runtime.
A typical multi-platform release pipeline has a per-platform CI step that
stages the right libraries into package-files/lib/ before invoking
apac -ib. If you need different libraries on Linux vs Windows, your
pipeline manages that swap - apac does not.
GTK4 is the one exception. Because the GTK4 native cluster is large
(hundreds of files), version-coupled to the Aussom CLI's own bindings,
and identical from app to app, apac ships it as a managed bundle: set
bundle.gtk4: true and apac copies the right files out of the Aussom
CLI install for you on macOS and Windows. You do not need to drop any
GTK4 libs into package-files/lib/ yourself, and the two paths
compose - any libs you put in package-files/lib/ ship in addition to
the GTK4 cluster apac copies.
To ship a third-party JAR (a JDBC driver, an ORM, anything not already in
the Aussom CLI), list its path under bundle.extraJars:
linux:
types:
- deb
bundle:
extraJars:
- libs/sqlite-jdbc-3.45.0.0.jar
- libs/postgresql-42.7.3.jar
For each entry, drop a matching <jarname>.txt into
package-files/extra-licenses/ containing the JAR's license text. apac
concatenates these into a THIRD_PARTY_LICENSES.txt file that ships
inside the installer.
The build fails if any extraJars entry is missing its license file. The
goal is to make license-compliance impossible to skip silently.
apac copies each JAR into the installer's libs/ directory. jpackage's
recursive input scan picks up every .jar it finds under the staged
input tree and adds it to the launcher's startup classpath, so the
extra jars are immediately available to include-resolved code with no
extra setup on your side.
Use bundle.extraResources to ship arbitrary directory trees alongside
the app:
linux:
types:
- deb
bundle:
extraResources:
- data
- templates
Each listed directory gets copied verbatim into the staging tree under
its original name. The launched app sees it at $APPDIR/data/,
$APPDIR/templates/, etc.
| Platform | Status |
|---|---|
| macOS | Supported. Set macos.signing.enabled: true and macos.signing.identity to your Apple Developer ID. apac passes them to jpackage. |
| Windows | Not yet supported. jpackage 23 does not expose a Windows code-signing flag. Setting windows.signing.enabled: true fails validation up front; sign the produced .msi or .exe externally with signtool.exe. |
| Linux | N/A. .deb and .rpm do not use code signatures the way macOS and Windows installers do; package signing is a registry-side concern. |
apac -ib validates the config and the host before running jpackage. It
exits with these codes:
| Code | Meaning |
|---|---|
| 0 | All requested installers built successfully. |
| 1 | One or more jpackage invocations failed. The last failing invocation's output appears just above the error line. |
| 2 | Config error: a field is missing, a referenced file does not exist, the mainScript is not a bare filename, etc. The error message names the field and the expected value. |
| 3 | Host tool missing or wrong version (for example, WiX 4 on Windows). The error message names the tool and how to install the right one. |
Common config errors and what they mean:
installer.mainScript 'main.aus' not found - apac looked at
src/main/aus/<pkg>/main.aus and <cwd>/main.aus; neither exists.
Create the file or fix the value.installer.<plat>.icon '...' not found - the icon file is missing.
Drop the file at the listed path or comment the icon: line out.extra jar '...' is missing its license file - drop the matching
<jarname>.txt into package-files/extra-licenses/.installer.mainScript must be a bare filename - the value contains
/ or \\. apac stages the script's parent directory as the app
root; subdir paths would silently change what gets staged. Move
the file or refactor.Pass --dry-run to validate, stage, and print the jpackage command line
without actually invoking jpackage:
apac -ib --dry-run
The printed command is shell-quoted, so you can copy-paste it to debug
jpackage output directly. The staging tree under target/staging/<plat>/
is still produced and is safe to inspect.
This is the fastest way to confirm a config change before paying for a full installer build.
apac does not cross-compile. To produce a .deb, .msi, and .pkg
from the same source tree, you need to run apac -ib on each OS in
turn. The typical pattern is a CI matrix with one job per OS, each
checking out the source, staging the right platform-specific native
libraries into package-files/lib/, and running apac -ib.
The staging tree under target/staging/<plat>/ is per-platform isolated,
so simultaneous builds on a shared filesystem do not collide.