AppBundler.jl offers recipes for building Julia GUI applications in modern desktop application installer formats. It uses Snap for Linux, MSIX for Windows, and DMG for MacOS as targets. It bundles full Julia within the app, which, together with artifact caching stored in scratch space, allows the bundling to be done quickly, significantly shortening the feedback loop.
The build product of
AppBundler.jl is a bundle that can be conveniently finalised with a shell script on the corresponding host system without an internet connection. This allows me to avoid maintaining multiple Julia installations for different hosts and reduces failures due to a misconfigured system state. It is ideal for a Virtualbox setup where the bundle together with bundling script is sent over SSH after which the finalised installer is retrieved.
The configuration options for each installer bundle vary greatly and are virtually limitless; thus, creating a single bundling configuration file for all systems is impractical. To resolve this, the AppBundler recipie system comes into the picture. AppBundler provides default configuration files which substitute a few set variables specified at the
Project.toml in a dedicated
[bundle] section. This shall cover plenty of use cases. In case the application needs more control, like interfacing web camera, speaker, host network server, etc., the user can place a custom
Entitlements.plist in the application
meta folder, overloading the defaults. Additional files can be provided easily for the bundle by placing them in a corresponding folder hierarchy. For instance, this can be useful for providing custom-sized icon sizes. To see how that works, explore AppBundler.jl/examples and PeaceFounderClient where you can check out the releases page to see what one can expect.
All recipes define a
USER_DATA environment variable where apps can store their data. On Linux and Windows those are designated application locations which get removed with the uninstallation of the app, whereas on MacOS, apps use
~/.cache/myapp folders unless one manages to get an app running from a sandbox in which case the
$HOME/Library/Application Support/Local folder will be used.
Thought has also been put into improving the precompilation experience to reduce start-up time for the first run. For MacOS, precompilation can be done before bundling in the
/Applications folder by running
MyApp.app/Contents/MacOS/precompile. For Linux, precompilation is hooked into the snap
configure hook executed after installation. For Windows, a splash screen is shown during the first run, providing user feedback that something is happening. Hopefully, the cache relocability fix in Julia 1.11 will allow us to precompile the Windows bundle as well.
AppBundler expects an application folder which contains
Manifest.toml. The application entry point is the
main.jl, which allows starting the application with
julia --project=. main.jl from the project directory. A
Project.toml contains many adjustable template variables under a
[bundle] section. The configuration of the
bundle sits in a
meta folder from where files take precedence over the AppBunlder
recepies folder. Thus, it is straightforward to copy a template from the recipes folder, modify and place it in the
meta folder if the default configuration does not suffice. See the
examples folder to see ways to configure.
A bundle can be created with
AppBundler.bundle_app function as follows:
import Pkg.BinaryPlatforms: Linux, MacOS, Windows
bundle_app(MacOS(:x86_64), "MyApp", "build/MyApp-x64.app")
The first argument is a platform for which the bundle is being made; in this case, MacOS;
MyApp is the location for the project, and
build/MyApp-x64.app is the location where the bundles will be stored. For Linux, the extension
.snap and Windows
.zip for destination determines whether the output is compressed, which can be overridden by
compress=false in the keyword arguments.
The resulting bundles can be easily tested on the resulting platforms. The testing phase works without any postprocessing as follows:
- MacOS: Double-click on the resulting app bundle to open the application.
- Linux: the snap can be installed from a command line
snap install --classic --dangerous app.snap
- Windows: the bundle can be tried from the application directory with the PowerShell command
Add-AppPackage -register AppxManifest.xml
Note that you will face difficulties when using
AppBundler from Windows for Linux, macOS and other UNIX operating systems, as Windows does not have a concept of execution bit. By hand, you could set
chmod +x to every executable on the resulting platform. Technically, this can be resolved by directly bundling files into a tar archive and processing the incoming archives without extraction, but this will not happen. Thus, it is better to use WSL when bundling for other platforms on Windows.
After the bundle is created, it needs to be finalised on the host system, where precompilation and, usually, bundle signing must be performed.
- Precompilation can be done
myapp.app/Contents/MacOS/precompile, which will generate a compilation cache in
myapp.app/Contents/Frameworks/compiledfolder. As a hack, precompilation can be done from the
/Applications/MyAppfolder so that users would not need to wait for precompilation when starting the app (untested).
- Before an application can be signed, we need to create an executable which can store signatures in metadata. Rename the bash launcher script as
gccand move the executable to the launcher location
MacOS/myapp. The executable will start `MacOS/main; thus, little can go wrong there.
- Code signing can be performed with
Resources/Entitlements.plistfile contains entitlements which should be used when signing the final bundle.
- For the creation of a dmg bundle, the
dmgbuildis recommended, which can be installed conveniently with
pip install dmgbuild. For convenience,
Resources/dmg_settings.pyis provided, which allows to
dmgbuild -s "MyApp/Contents/Resources/dmg_settings.py" -D app="MyApp" "MyApp Installer" "MyApp.dmg"
The signing certificate can be obtained from Apple by subscribing to its developer program. Alternatively, for development purposes, you can generate a self-signing certificate. Follow offciial instructions of Apple, set Certificate Type to *Code Signing` and fill out the rest of the parameters as instructed on youtube.
- If the application is compressed into the snap, use
- Run precompilation with
myapp/bin/precompile,which will create
- Squash the folder back into a snap with the command
mksquashfs myapp myapp.snap -noappend -comp xz
For snap, it is also worth mentioning the
snap try myapp command, which allows one to install an application without squashing. There is also
snap run --shell myapp, which is a valuable command for entering into the snap confinement shell.
For Windows, one has to install
editbin installed with WindowsSDK. Installation of Windows SDK fails. Thus, one needs to install Visual Studio Code, adding a few more gigabytes to download and install. For those who run Windows from Parallels, don't run the
Add-Package -register AppxManifest.xml from a network drive, but copy the files to the home directory instead, as otherwise, Julia crashes. Also, running an executable from installation location
C:\ProgramFiles\WindowsApps\<app folder> with admin privileges will run the application within the containerised environment specified with
Generation of Self-Signing Certificate
It must be signed to test and install the
.msix bundle on the Windows S platform. We can do that with self-signing, where the certificate is added to the system's store. An alternative is to exit from
Windows S and enable side loading under developer tools, which is only available after buying the Windows license. The procedure here should be easily adaptable with a codesigning certificate from a trusted provider.
A self-signing certificate can be made from a PowerShell with the following command:
New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=YourName" -KeyAlgorithm RSA -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My" -FriendlyName "YourCertificateName"
The critical part is
CN=YourName, which must match the
Publisher entry in
AppxManifest.xml for the package to be correctly signed. The output generates a thumbprint for the certificate, which you place in the following command, which will create your private key signed with the certificate:
Export-PfxCertificate -cert "Cert:\CurrentUser\My\[Thumbprint]" -FilePath JanisErdmanis.pfx -Password (ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText)
which generates a pfx certificate and adds password protection.
Bundling Procedure for MSIX
After installation of Visual Studio Code, find the relevant tools (best to do that with Windows Finder) and either add them to a path or make an alias like:
New-Alias -Name makeappx -Value "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\makeappx.exe"
New-Alias -Name signtool -Value "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
New-Alias -Name editbin -Value "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\bin\Hostx64\x64\editbin.exe"
- If the bundle is compressed, unzip it.
- Run the precompilation script
myapp\precompile.ps1which will generate
julia.exeto be Windows application
editbin /SUBSYSTEM:WINDOWS myapp\julia\bin\julia.exeso the console is not shown when the app is run.
- Make a bundle
makeappx pack /d myapp /p myapp.msix
- Sign the bundle
signtool sign /fd SHA256 /a /f JanisErdmanis.pfx /p "PASSWORD" myapp.msix
When self-signed, the resulting bundle can not immediately be installed as the certificate is not binding to the trusted anchors of the system. This can be resolved by installing the certificate to the system from the MSIX package itself, which is described in https://www.advancedinstaller.com/install-test-certificate-from-msix.html