# Packaging Cross-Platform Apps

### Packaging Cross-Platform Apps

Packaging means preparing your program so other people can download it, install it, run it, and trust what they are running.

For Zig programs, packaging often starts with one simple advantage: Zig can build small native binaries for many targets.

A cross-platform release may include files like this:

```text
mytool-linux-x86_64
mytool-linux-aarch64
mytool-macos-x86_64
mytool-macos-aarch64
mytool-windows-x86_64.exe
```

Each file is built for a different target.

#### Start with Clear Targets

Do not say “all platforms” unless you test all platforms.

Start with a small release matrix:

```text
x86_64-linux
aarch64-linux
x86_64-macos
aarch64-macos
x86_64-windows
```

These cover many common desktop, server, and laptop users.

Build commands may look like:

```bash
zig build-exe src/main.zig -target x86_64-linux -O ReleaseFast -femit-bin=dist/mytool-linux-x86_64
zig build-exe src/main.zig -target aarch64-linux -O ReleaseFast -femit-bin=dist/mytool-linux-aarch64
zig build-exe src/main.zig -target x86_64-macos -O ReleaseFast -femit-bin=dist/mytool-macos-x86_64
zig build-exe src/main.zig -target aarch64-macos -O ReleaseFast -femit-bin=dist/mytool-macos-aarch64
zig build-exe src/main.zig -target x86_64-windows -O ReleaseFast -femit-bin=dist/mytool-windows-x86_64.exe
```

This produces one binary per target.

#### Use Release Modes

Debug builds are for development. Release builds are for users.

Common optimization modes include:

```text
ReleaseFast
ReleaseSmall
ReleaseSafe
```

`ReleaseFast` focuses on speed.

`ReleaseSmall` focuses on smaller binaries.

`ReleaseSafe` keeps more runtime safety checks while still optimizing.

For command-line tools, `ReleaseFast` is common. For small utilities, `ReleaseSmall` can be attractive. For software where safety checks are worth the overhead, consider `ReleaseSafe`.

Example:

```bash
zig build-exe src/main.zig -O ReleaseSmall
```

#### Name Files Clearly

A release file should make its target obvious.

Good names:

```text
mytool-0.1.0-linux-x86_64.tar.gz
mytool-0.1.0-linux-aarch64.tar.gz
mytool-0.1.0-macos-aarch64.tar.gz
mytool-0.1.0-windows-x86_64.zip
```

Poor names:

```text
mytool
release
final
binary
```

Users should know which file to download without reading source code.

#### Include More Than the Binary

A package should usually include:

```text
binary
README
LICENSE
CHANGELOG
shell completion files if available
example config if needed
```

For Linux and macOS, a `.tar.gz` package is common:

```text
mytool
README.md
LICENSE
CHANGELOG.md
```

For Windows, a `.zip` package is common:

```text
mytool.exe
README.md
LICENSE
CHANGELOG.md
```

The binary alone may run, but a package should help users understand what they downloaded.

#### Linux Packaging

The simplest Linux release is a tarball:

```bash
tar -czf mytool-0.1.0-linux-x86_64.tar.gz -C dist/linux-x86_64 .
```

A user can extract it:

```bash
tar -xzf mytool-0.1.0-linux-x86_64.tar.gz
./mytool
```

For deeper Linux integration, you may later provide:

```text
.deb
.rpm
AppImage
Arch package
Flatpak
Snap
```

But do not start there. First make a correct binary release.

Linux packaging complexity often comes from libc and dynamic linking. A binary built for one Linux environment may fail on another if it depends on incompatible shared libraries.

If possible, test your Linux binary on multiple distributions.

#### macOS Packaging

For command-line tools, macOS can use a tarball:

```text
mytool-0.1.0-macos-aarch64.tar.gz
mytool-0.1.0-macos-x86_64.tar.gz
```

For wider distribution, you may need to think about:

```text
universal binaries
code signing
notarization
Homebrew formula
app bundles for GUI apps
```

A universal binary can support both Intel and Apple Silicon Macs in one file.

Conceptually:

```bash
zig build-exe src/main.zig -target x86_64-macos -O ReleaseFast -femit-bin=mytool-x86_64
zig build-exe src/main.zig -target aarch64-macos -O ReleaseFast -femit-bin=mytool-aarch64
lipo -create -output mytool mytool-x86_64 mytool-aarch64
```

For a small developer tool, separate architecture builds are often acceptable. For a polished macOS release, a universal binary is more convenient.

#### Windows Packaging

For Windows, ship an `.exe`.

A simple package can be:

```text
mytool-0.1.0-windows-x86_64.zip
  mytool.exe
  README.md
  LICENSE
  CHANGELOG.md
```

Users may run it from PowerShell:

```powershell
.\mytool.exe
```

For more formal distribution, you may later provide:

```text
MSI installer
winget package
Scoop manifest
Chocolatey package
signed executable
```

As with macOS, signing matters when distributing to many users. Unsigned binaries may trigger security warnings.

#### Checksums

A checksum lets users verify that a downloaded file was not corrupted or replaced.

Common checksum files look like:

```text
mytool-0.1.0-linux-x86_64.tar.gz
mytool-0.1.0-linux-x86_64.tar.gz.sha256
```

Generate a SHA-256 checksum:

```bash
sha256sum mytool-0.1.0-linux-x86_64.tar.gz > mytool-0.1.0-linux-x86_64.tar.gz.sha256
```

On macOS:

```bash
shasum -a 256 mytool-0.1.0-macos-aarch64.tar.gz > mytool-0.1.0-macos-aarch64.tar.gz.sha256
```

Checksums are simple and useful. They are not a full substitute for signatures, but they are a good baseline.

#### Version Numbers

Use version numbers consistently.

A simple format is semantic versioning:

```text
0.1.0
0.2.0
1.0.0
1.1.0
```

Put the version in:

```text
Git tag
binary output
package file name
README
changelog
```

Your program can expose its version:

```zig
const std = @import("std");

pub fn main() !void {
    try std.io.getStdOut().writer().print("mytool 0.1.0\n", .{});
}
```

Later, you can pass version information through the build system instead of hardcoding it.

#### Build Script Packaging

For a real project, you usually do not want to type long build commands manually.

A `build.zig` file can define targets, build modes, install steps, and tests.

A package command may eventually do this:

```bash
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFast
```

Then CI can call the same build logic.

The principle is important: put repeatable release logic in scripts. Do not rely on memory.

#### Continuous Integration

A cross-platform project should build in CI.

A typical CI release job:

```text
check out source
install Zig
run tests
build each target
package artifacts
generate checksums
upload release files
```

CI helps prevent mistakes such as forgetting one target, shipping a stale binary, or releasing a build that no longer passes tests.

Cross-compilation helps here because one CI runner may build several targets. But some targets still need real runtime testing.

#### Runtime Testing

A binary that builds may still fail at runtime.

Test at least:

```text
program starts
--help works
--version works
basic command works
error path works
file paths work
non-ASCII paths if relevant
```

For platform-specific behavior, test on the actual platform.

Examples:

```text
Windows path with spaces
macOS Apple Silicon execution
Linux glibc compatibility
Linux musl build if provided
terminal output behavior
config directory behavior
```

Packaging without runtime testing is risky.

#### Directory Layout for Releases

A clean project may use this layout:

```text
project/
  src/
    main.zig
  build.zig
  README.md
  LICENSE
  CHANGELOG.md
  dist/
    mytool-0.1.0-linux-x86_64/
      mytool
      README.md
      LICENSE
      CHANGELOG.md
    mytool-0.1.0-windows-x86_64/
      mytool.exe
      README.md
      LICENSE
      CHANGELOG.md
```

Then archive each directory.

For Linux and macOS:

```bash
tar -czf mytool-0.1.0-linux-x86_64.tar.gz mytool-0.1.0-linux-x86_64
```

For Windows:

```bash
zip -r mytool-0.1.0-windows-x86_64.zip mytool-0.1.0-windows-x86_64
```

#### Avoid Hidden Dependencies

A cross-platform package should not secretly depend on tools or files from your development machine.

Common hidden dependencies:

```text
shared libraries
config files
certificate files
plugins
dynamic assets
relative paths
shell scripts
environment variables
```

Test your package in a clean environment.

For Linux, a container can help.

For Windows, test on a clean VM if possible.

For macOS, test on a machine that did not build the binary.

#### Complete Example Release Script

A simple Unix shell script for building several targets:

```bash
#!/usr/bin/env sh
set -eu

name="mytool"
version="0.1.0"

rm -rf dist
mkdir -p dist

build_one() {
    target="$1"
    out="$2"

    zig build-exe src/main.zig \
        -target "$target" \
        -O ReleaseFast \
        -femit-bin="dist/$out"
}

build_one x86_64-linux "$name-$version-linux-x86_64"
build_one aarch64-linux "$name-$version-linux-aarch64"
build_one x86_64-macos "$name-$version-macos-x86_64"
build_one aarch64-macos "$name-$version-macos-aarch64"
build_one x86_64-windows "$name-$version-windows-x86_64.exe"

cd dist

for file in *; do
    case "$file" in
        *.exe)
            zip "$file.zip" "$file"
            ;;
        *)
            tar -czf "$file.tar.gz" "$file"
            ;;
    esac
done

sha256sum *.tar.gz *.zip > SHA256SUMS
```

This is not a complete professional release system, but it shows the core idea: build repeatably, name outputs clearly, archive them, and generate checksums.

#### The Practical View

Packaging cross-platform Zig apps is mostly about discipline.

Choose clear targets. Build release binaries. Name files clearly. Include license and documentation. Generate checksums. Test on real platforms. Keep release commands repeatable.

Zig makes the compilation part unusually convenient. The remaining work is release engineering: naming, packaging, signing, testing, documentation, and trust.

