Janis Erdmanis janiserdmanis.org
This work is supported by the European Union through the Next Generation Internet initiative (NGI0 Entrust), via the NLnet Julia-AppBundler project.
A few years ago, I wanted to build a prototype for an e-voting system peacefounder.org
That led me to a deployment challenge:
How do you deploy user-facing GUI applications written in Julia?
And that's how AppBundler started.
curl -L https://trust.me/install.sh | sh
module CmdApp import AppEnv function (@main)(ARGS::Vector{String}) AppEnv.init() # initialize runtime environment # application logic filename = only(ARGS) println("$filename: $(word_count(filename)) words") return 0 end export main end
If your Julia application exposes @main and can be launched with julia --project=. -m MyApp, it is ready to bundle:
@main
julia --project=. -m MyApp
appbundler build ./examples/CmdApp --build-dir=build --selfsign
AppBundler isn't limited to one application structure
Microsoft tooling
StagingDir → MakeAppx → SignTool → MyApp.msix
AppBundler
StagingDir → msixpack → osslsigncode → MyApp.msix
AppBundler assembles this StagingDir/ before creating the .msix.
StagingDir/
AppxManifest.xml
resources.pri
Assets/
Msix.AppInstaller.Data/MSIXAppInstallerData.xml
MyApp.exe
AppBundler changes MyApp.exe to the Windows GUI subsystem after compilation. For GUI applications, this currently leaves a zombie console process.
Sandboxing is configured through AppxManifest.xml. The application also requires the UCRT runtime.
Apple tooling
MyApp.app → codesign → hdiutil → codesign → notarytool → stapler
MyApp.app → rcodesign → xoriso → dmg → rcodesign -notarize
dmg
AppBundler assembles the MyApp.app bundle before creating the .dmg:
MyApp.app
.dmg
Contents/MacOS/MyApp
Contents/Libraries/main
Contents/Info.plist
Contents/Resources/icon.icns
../DS_Store
The launcher must be a native binary for code signing. Sandboxing is configured through Entitlements.plist and incorporated into the signed launcher.
Entitlements.plist
Snapcraft
snapcraft.yaml → snapcraft → MyApp.snap
StagingDir → mksquashfs → MyApp.snap
AppBundler assembles the StagingDir/ before creating the .snap:
.snap
snap/snap.yaml
bin/MyApp
meta/icon.png
meta/gui/MyApp.desktop
meta/hooks/configure
Compiled code doesn't know it's been moved.
@__DIR__
pkgdir(@__MODULE__)/assets
pkgdir
Fix: ship an asset index; AppEnv.init() repopulates Base.pkgorigins
AppEnv.init()
Base.pkgorigins
Sysimage and JuliaC bundles don't need the sources — only the assets. We can select only the assets we need in LocalPreferences.toml:
LocalPreferences.toml
[AppEnv] assets = ["LICENSE"] [QMLApp] assets = ["src/App.qml"] [AppBundler] juliaimg_selective_assets = true # Other AppBundler options
Install GitHub action script:
AppBundler.install_github_workflow()
and bunlde installers with macos-latest-x86_64, macos-latest-aarch64, ubuntu-latest-x86_64, ubuntu-24.04-arm-aarch64, windows-latest runners.
macos-latest-x86_64
macos-latest-aarch64
ubuntu-latest-x86_64
ubuntu-24.04-arm-aarch64
windows-latest
Until then:
The install.sh script can bootstrap trust of the bundle signer. The app then installs through the normal platform packaging path.
install.sh
Quaternions.jl
ds_store
HTML comment recognizes as a presenter note per pages.
You may place multiple comments in a single page.