Shipping Sigil Applications
This guide explains how to package Sigil applications for distribution, covering the different commands and scenarios you'll encounter.
Overview
Sigil provides two commands for creating distributable applications:
sigil bundle- Package your app using the existing CLI runtime (fast, simple)sigil build- Build a custom runtime with full control over native dependencies
The right choice depends on your application's needs.
Quick Reference
| Scenario | Command | Result |
|---|---|---|
| Simple script | sigil bundle script.sgl | Uses CLI runtime |
| Multi-module app (no native deps) | sigil bundle myapp | Uses CLI runtime |
| App with TLS/crypto/SQLite | sigil build myapp | Custom runtime with native libs |
| Minimal binary size | sigil build myapp | Custom runtime, only needed deps |
Understanding the Two Commands
sigil bundle - Fast Packaging
The sigil bundle command creates a standalone executable by packaging your application into the existing Sigil CLI runtime. This is the fast path for distribution.
Bundling a single script:
sigil bundle script.sgl # Creates ./script executable
sigil bundle script.sgl -o dist/mytool # Custom output pathFor simple scripts, no package.sgl is needed. The script is compiled and embedded directly. Top-level code in the script runs immediately when the executable starts—no main procedure is required.
Bundling a multi-module application:
sigil bundle . # Bundle current directory
sigil bundle myapp/ # Bundle specific directory
sigil bundle myapp/ -o dist/myapp # Custom output pathFor multi-module applications, create a minimal package.sgl specifying the entry point. The entry module must define a main procedure that will be called when the executable starts:
(package
name: "myapp"
entry: '(myapp main))What it does:
- Takes the pre-built Sigil CLI binary as the base
- Compiles and embeds your modules (
.sgl→.sgbfiles) - Embeds any assets from your
assets/directory - Sets the entry point as the startup module
When to use:
- Your app only uses packages included in the CLI (stdlib, json, http, etc.)
- You want fast iteration during development
- You don't need to minimize binary size
Limitations:
- Cannot add native packages not already in the CLI
- Binary includes all CLI dependencies (larger size)
sigil build - Custom Runtime
The sigil build command compiles your application with full control, building a custom runtime that includes exactly the native dependencies your app needs.
sigil build myapp
sigil build myapp --config releaseWhat it does:
- Compiles all dependency packages
- Detects native packages (C code) in your dependency tree
- Generates a custom entrypoint that initializes native modules
- Links a custom runtime with only the needed native libraries
- Creates a standalone executable
When to use:
- Your app uses native packages (crypto, TLS, SQLite, custom C extensions)
- You want minimal binary size (only include what you use)
- You're building for a platform different from your development machine
Scenarios
Scenario 1: Simple Application
A basic application that uses only the standard library and pure-Scheme packages.
;; package.sgl
(package
name: "hello-world"
version: "1.0.0"
entry: '(hello main)
dependencies: (list
(from-workspace name: "sigil-stdlib")
(from-workspace name: "sigil-json")))Recommended: sigil bundle hello-world
The app has no native dependencies, so bundling with the CLI runtime is fastest. Pure-Scheme dependencies (like sigil-json, sigil-http) work fine with bundle since all .sgb files are replaced in the bundle anyway.
Scenario 2: Web Application with TLS
An application that makes HTTPS requests or serves HTTPS traffic.
;; package.sgl
(package
name: "web-client"
version: "1.0.0"
entry: '(web-client main)
dependencies: (list
(from-workspace name: "sigil-stdlib")
(from-workspace name: "sigil-http")
(from-workspace name: "sigil-tls")
(from-workspace name: "sigil-crypto")))Recommended: sigil build web-client --config release
The app needs TLS and crypto native libraries, so a custom runtime is built automatically.
Scenario 3: Database Application
An application that uses SQLite for local storage.
;; package.sgl
(package
name: "notes-app"
version: "1.0.0"
entry: '(notes main)
dependencies: (list
(from-workspace name: "sigil-stdlib")
(from-workspace name: "sigil-sqlite")))Recommended: sigil build notes-app --config release
SQLite is a native package, so a custom runtime includes the SQLite library.
Scenario 4: Application with Assets
An application with bundled data files, templates, or other assets.
;; package.sgl
(package
name: "my-app"
version: "1.0.0"
entry: '(my-app main)
dependencies: (list
(from-workspace name: "sigil-stdlib")))Place assets in the assets/ directory (by convention). They are automatically included in the bundle.
my-app/
├── package.sgl
├── src/
│ └── my-app/
│ └── main.sgl
└── assets/
├── templates/
│ └── report.html
└── data/
└── config.jsonRecommended: sigil bundle my-app
Accessing bundled assets:
(import (sigil resources))
;; Read a bundled file
(define template (resource-read "templates/report.html"))
;; Check if a resource exists
(resource-exists? "data/config.json")
;; Assets are accessed via @bundle/assets/ path internallyScenario 5: Minimal Distribution
When binary size matters (embedded systems, quick downloads).
;; package.sgl
(package
name: "tiny-tool"
version: "1.0.0"
entry: '(tiny-tool main)
dependencies: (list
(from-workspace name: "sigil-stdlib")))Recommended: sigil build tiny-tool --config release
Using sigil build creates a custom runtime that only includes the modules your app actually uses, rather than the full CLI with all its dependencies.
Build Configurations
Both commands support the --config flag to select a build configuration:
sigil build myapp --config dev # Local development (dynamic linking, fast builds)
sigil build myapp --config debug # Debug build (static linking, debug symbols)
sigil build myapp --config release # Release (static linking, optimized)Common Configurations
| Config | Static Linking | Optimization | Use Case |
|---|---|---|---|
dev | No | None | Local development, fast iteration |
debug | Yes (musl) | None | Debugging static builds |
release | Yes (musl) | O2 | Distribution |
Cross-Compilation
For building on one platform and running on another:
sigil build myapp --config linux-arm64 # Linux ARM64
sigil build myapp --config windows-amd64 # Windows x64
sigil build myapp --config macos-arm64 # macOS Apple SiliconCross-compilation requires the appropriate toolchain (zig or platform-specific cross-compiler).
Windows icon and version information
A Windows config that declares icon: gives the executable an icon and fills in Explorer's Properties -> Details tab:
(config
name: 'windows-amd64
toolchain: 'zig
target: "x86_64-windows-gnu"
backend: 'native
icon: "windows/app.ico" ; relative to where sigil build runs
product-name: "My Game") ; optional; defaults to the package namesigil build writes a resource script from the package, compiles it with zig rc into <output-dir>/native/<exe>-resources.res, and links it. The version resource takes FILEVERSION, PRODUCTVERSION, FileVersion and ProductVersion from the package's version:, ProductName and FileDescription from product-name: (else the package name), and CompanyName from the first authors: entry with its email address removed. Nothing in it is maintained by hand, so bumping version: is enough.
- The icon must be a
.ico. sigil does not convert images, so the build needs nothing beyond zig. Commit the.ico; to make one from PNGs, for example:icotool -c -o app.ico icon-16.png icon-32.png icon-48.png icon-256.png(icoutils). - A missing file or a non-
.icofails the build with a message naming the config'sicon:key. It never produces an iconless executable. icon:needs zig as the linker (toolchain: 'zig, or'nativewith zig available and noc-compiler:), and a build that links the executable: abackend: 'nativeconfig, or one with native dependencies. A bytecode bundle that reuses the prebuilt runtime is refused with a message saying so.- Every executable the config bundles, or links natively without bundling, gets the icon and the same ProductName, because the keys are on the config. Each keeps its own package's version. Plain C executables from
link-executableare not touched. icon:andproduct-name:are ignored when the config targets anything but Windows. A config with notarget:builds for the host, so on a Windows host it counts as a Windows config.- The
.resis recompiled when the icon's bytes or the generated resource script (and so the version or names) changed. The comparison is by content, so an icon replaced by an older-dated file is still picked up.
What a native bundle runs natively
A backend: 'native config with bundle?: #t compiles your package's modules to native code. Your dependencies, the standard library included, ship in the bundle as bytecode by default. Set SIGIL_NATIVE_DEPS=all to compile in the dependency modules your package imports, directly or through other modules, as well. Dependency modules nothing imports are left out, so the binary grows only by what the app uses, and everything else still ships in the bundle as bytecode, so a module loaded at run time by name still works.
SIGIL_NATIVE_DEPS=0(the default for now) keeps dependency code as bytecode,1compiles in the standard library only, andallevery runtime dependency. Any other value stops the build.- A dependency module with a
#langheader stays bytecode: the build cannot see what the library its reader produces imports. - If your package declares a module with the same name as one of a dependency's, yours is used. Two dependencies that declare the same module name stop the build with a message naming both files.
- The
sigilCLI's own release build keeps its dependencies as bytecode.
One package, native and web configs
A package that builds both a native binary and a browser application declares a web config (target: "wasm32-wasi", toolchain: 'zig) beside its native ones. Three rules keep that from dragging the wrong things in:
- Dev-dependencies are built only when tests are built. A plain
sigil build, for any config, builds the workspace members and what they reach through runtime dependencies; a dev-dependency is built bysigil build --testsand bysigil test. A test runner's closure is never compiled for wasm just because awebconfig exists. - A task's
configs:gate is honoured.(task name: 'build configs: '(web) ...)runs for thewebconfig only; the same name may be declared again for other configs, and a config no user task covers gets the default task. So the web config can link a browser application in its ownbuildtask while the native configs keep the default one:
tasks: (list
(task name: 'build configs: '(web)
steps: (list
(compile-sigil-modules sources: "src/**/*.sgl")
(link-web-application name: "my-game" html-mount-id: "status"))))- A dependency library's
native-init:is called in a wasm build. The build generatesweb-app-register.c, a weaksigil_wasm_app_registerthat calls everynative-init:in the dependency closure, so a package that uses sigil-graphics' natives on the web needs no hand-written register file. A package that still ships its own strongsigil_wasm_app_registerkeeps winning.
Native Packages
Native packages contain C code that must be compiled and linked into the runtime. Sigil's native packages include:
| Package | Provides | Native Library |
|---|---|---|
sigil-crypto | SHA, HMAC, base64, random | libsigil-crypto.a |
sigil-tls | TLS/SSL connections | libsigil-tls.a |
sigil-sqlite | SQLite database | libsigil-sqlite.a |
When your app depends on a native package, sigil build automatically:
- Compiles the native code to a static library
- Generates an entrypoint that calls each package's init function
- Links everything into a custom runtime
Creating Native Packages
If you're writing a package with C code:
;; package.sgl
(package
name: "my-native-pkg"
version: "1.0.0"
dependencies: (list
(from-workspace name: "sigil-stdlib"))
libraries: (list
(library
name: 'my-native-pkg
c-sources: '("native/mycode.c")
c-include-dirs: '("native/include")
native-init: "my_pkg_init"))
tasks: (list
(task
name: 'build
steps: (list
(compile-c-sources
sources: '("native/mycode.c")
include-dirs: '("../sigil-lib/include"
"../sigil-lib/src"
"native/include"))
(create-static-library
name: "my-native-pkg")))))The native-init field specifies the C function called to register the module's primitives with the VM.
Troubleshooting
"Native packages detected but using CLI baseline"
This message appears when your app uses native packages that are already included in the CLI runtime. The build system reuses the CLI rather than building a custom runtime.
"Version mismatch detected"
When the CLI version differs from your project's dependency versions, a custom runtime is built to avoid compatibility issues.
"sigil-run executable not found"
The build system couldn't find a base runtime. Run make to build the bootstrap first.
Summary
- Use
sigil bundlefor fast packaging when your app fits within the CLI's capabilities - Use
sigil buildwhen you need native packages or minimal binary size - Native packages (crypto, TLS, SQLite) automatically trigger custom runtime builds
- Assets are bundled automatically from your
assets/directory - Cross-compilation is available via
--configflags