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

ScenarioCommandResult
Simple scriptsigil bundle script.sglUses CLI runtime
Multi-module app (no native deps)sigil bundle myappUses CLI runtime
App with TLS/crypto/SQLitesigil build myappCustom runtime with native libs
Minimal binary sizesigil build myappCustom 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 path

For 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 path

For 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:

  1. Takes the pre-built Sigil CLI binary as the base
  2. Compiles and embeds your modules (.sgl → .sgb files)
  3. Embeds any assets from your assets/ directory
  4. 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 release

What it does:

  1. Compiles all dependency packages
  2. Detects native packages (C code) in your dependency tree
  3. Generates a custom entrypoint that initializes native modules
  4. Links a custom runtime with only the needed native libraries
  5. 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.json

Recommended: 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 internally

Scenario 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

ConfigStatic LinkingOptimizationUse Case
devNoNoneLocal development, fast iteration
debugYes (musl)NoneDebugging static builds
releaseYes (musl)O2Distribution

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 Silicon

Cross-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 name

sigil 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-.ico fails the build with a message naming the config's icon: key. It never produces an iconless executable.
  • icon: needs zig as the linker (toolchain: 'zig, or 'native with zig available and no c-compiler:), and a build that links the executable: a backend: 'native config, 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-executable are not touched.
  • icon: and product-name: are ignored when the config targets anything but Windows. A config with no target: builds for the host, so on a Windows host it counts as a Windows config.
  • The .res is 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, 1 compiles in the standard library only, and all every runtime dependency. Any other value stops the build.
  • A dependency module with a #lang header 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 sigil CLI'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 by sigil build --tests and by sigil test. A test runner's closure is never compiled for wasm just because a web config exists.
  • A task's configs: gate is honoured. (task name: 'build configs: '(web) ...) runs for the web config 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 own build task 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 generates web-app-register.c, a weak sigil_wasm_app_register that calls every native-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 strong sigil_wasm_app_register keeps winning.

Native Packages

Native packages contain C code that must be compiled and linked into the runtime. Sigil's native packages include:

PackageProvidesNative Library
sigil-cryptoSHA, HMAC, base64, randomlibsigil-crypto.a
sigil-tlsTLS/SSL connectionslibsigil-tls.a
sigil-sqliteSQLite databaselibsigil-sqlite.a

When your app depends on a native package, sigil build automatically:

  1. Compiles the native code to a static library
  2. Generates an entrypoint that calls each package's init function
  3. 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 bundle for fast packaging when your app fits within the CLI's capabilities
  • Use sigil build when 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 --config flags