Releasing a Nexium program

How to turn a .nx program into downloadable binaries for Windows, Linux, and macOS from a GitHub repository, with installers as an optional extra.

What nx produces#

commandoutput
nx build app.nxa native executable: app.exe on Windows, an ELF binary on Linux, a Mach-O binary on macOS
nx build app.nx --target x86_64-linux-gnuthe same, cross-compiled; any target zig cc knows works from any host
nx build app.nx --mode smallsize-optimized
nx ship app.nxevery declared artifact: shared and static libraries with a C header, a Python wheel, a Rust crate

Executables depend only on the target's C library. There is no runtime to install next to them.

Installers (.dmg, .msi, .deb) are not produced by nx; the platform tools make them from the binary, and the workflow below shows where.

The workflow#

Copy templates/release-nexium-program.yml to .github/workflows/release.yml in your repository and change the two values at the top: the source file and the program name. Then:

git tag v1.0.0 && git push origin v1.0.0

The tag triggers a build on three runners. Each installs Zig and nx through the Londopy/nexium/.github/actions/setup-nexium action, builds the program, packages it as a .zip (Windows) or .tar.gz (Linux, macOS), and uploads the three files to a GitHub Release whose notes are the tag's annotation. The whole thing is:

- uses: actions/checkout@v4
- uses: Londopy/nexium/.github/actions/setup-nexium@main
  with:
    version: v1.0.0          # pin the compiler
- run: nx build ${{ env.SOURCE }} --mode fast -o dist/${{ env.NAME }}

setup-nexium downloads the matching nx binary from Nexium's Releases and puts it and Zig on PATH. On the macOS runner nx uses the system compiler for native builds, since zig 0.14 cannot link against the newest Xcode SDKs; zig is still what cross-compiles. Pinning version keeps your builds reproducible; latest is the default.

Installers, when you want them#

The template has three optional jobs, off by default, each turning the built binary into an installer:

Each job is a few lines and is annotated in the template. Turn one on by setting its if: to true.

An installer for your program#

Declare it next to the cli artifact and nx ship produces one:

artifact cli { name = "taskdesk" }
artifact installer {
    name = "TaskDesk", publisher = "Londopy", version = "1.2.0",
    url = "https://example.com/taskdesk",
    license = "LICENSE", readme = "README.md",
    files = ["assets", "config.toml"],   // copied next to the program
    add_to_path = true,
}

In the release template, the Windows job installs Inno Setup with choco install innosetup before nx ship, and every job uploads nx-out/<name>/.

Shipping a library instead#

If the program declares artifact cabi, artifact python, artifact node or artifact rustlib, replace the build step with nx ship and upload the nx-out/<name>/ directory. The Python wheel and the npm package are platform specific, so build them on each runner; the C header and the Rust crate source are the same everywhere.

The npm package (nx-out/<name>/node/) is plain JavaScript over the shared library through koffi, with index.d.ts typings: no build step for the consumer, and npm publish from that directory ships it. Its os and cpu fields name the platform it was built on; publish one package per platform under a scoped name, or merge the shared libraries of every platform into one package and pick by process.platform.

Checklist before the first tag#