Skip to content

Commit 102dcfa

Browse files
authored
Merge pull request #2 from nullclaw/feat/zig-0.16
chore: migrate nulltickets to zig 0.16
2 parents 6349dca + c002ae1 commit 102dcfa

19 files changed

Lines changed: 1006 additions & 203 deletions

.github/scripts/install-zig.sh

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
if [ "$#" -ne 1 ]; then
5+
echo "usage: $0 <zig-version>" >&2
6+
exit 1
7+
fi
8+
9+
version="$1"
10+
11+
python_bin="${PYTHON:-python3}"
12+
if ! command -v "$python_bin" >/dev/null 2>&1; then
13+
python_bin="python"
14+
fi
15+
if ! command -v "$python_bin" >/dev/null 2>&1; then
16+
echo "python is required to install Zig" >&2
17+
exit 1
18+
fi
19+
20+
runner_os="${RUNNER_OS:-$(uname -s)}"
21+
runner_arch="${RUNNER_ARCH:-$(uname -m)}"
22+
23+
case "$runner_os" in
24+
Linux | linux)
25+
zig_os="linux"
26+
;;
27+
Darwin | macOS)
28+
zig_os="macos"
29+
;;
30+
Windows | MINGW* | MSYS* | CYGWIN*)
31+
zig_os="windows"
32+
;;
33+
*)
34+
echo "unsupported runner OS: $runner_os" >&2
35+
exit 1
36+
;;
37+
esac
38+
39+
case "$runner_arch" in
40+
X64 | x86_64 | amd64)
41+
zig_arch="x86_64"
42+
;;
43+
ARM64 | arm64 | aarch64)
44+
zig_arch="aarch64"
45+
;;
46+
*)
47+
echo "unsupported runner architecture: $runner_arch" >&2
48+
exit 1
49+
;;
50+
esac
51+
52+
host_key="${zig_arch}-${zig_os}"
53+
tool_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/nulltickets-zig"
54+
install_dir="${tool_root}/${version}/${host_key}"
55+
zig_bin="zig"
56+
if [ "$zig_os" = "windows" ]; then
57+
zig_bin="zig.exe"
58+
fi
59+
60+
if [ ! -x "${install_dir}/${zig_bin}" ]; then
61+
mkdir -p "$(dirname "$install_dir")"
62+
63+
zig_metadata="$(
64+
"$python_bin" - "$version" "$host_key" <<'PY'
65+
import json
66+
import sys
67+
import urllib.request
68+
69+
version = sys.argv[1]
70+
host_key = sys.argv[2]
71+
72+
with urllib.request.urlopen("https://ziglang.org/download/index.json") as response:
73+
data = json.load(response)
74+
75+
host = data.get(version, {}).get(host_key)
76+
if not host:
77+
raise SystemExit(f"missing Zig download metadata for version={version!r} host={host_key!r}")
78+
79+
archive_url = host.get("tarball") or host.get("zip")
80+
checksum = host.get("shasum") or ""
81+
if not archive_url:
82+
raise SystemExit(f"missing archive URL for version={version!r} host={host_key!r}")
83+
84+
print(archive_url)
85+
print(checksum)
86+
PY
87+
)"
88+
89+
archive_url="$(printf '%s\n' "$zig_metadata" | sed -n '1p')"
90+
expected_sha="$(printf '%s\n' "$zig_metadata" | sed -n '2p')"
91+
if [ -z "$archive_url" ]; then
92+
echo "failed to resolve Zig download URL" >&2
93+
exit 1
94+
fi
95+
96+
archive_name="${archive_url##*/}"
97+
archive_dir="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/zig-archive.XXXXXX")"
98+
archive_path="${archive_dir}/${archive_name}"
99+
extract_dir="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/zig-extract.XXXXXX")"
100+
trap 'rm -rf "$archive_dir"; rm -rf "$extract_dir"' EXIT
101+
102+
curl -fsSL --retry 3 --retry-all-errors "$archive_url" -o "$archive_path"
103+
104+
"$python_bin" - "$archive_path" "$expected_sha" <<'PY'
105+
import hashlib
106+
import sys
107+
108+
path = sys.argv[1]
109+
expected = sys.argv[2].strip().lower()
110+
if not expected:
111+
raise SystemExit(0)
112+
113+
digest = hashlib.sha256()
114+
with open(path, "rb") as handle:
115+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
116+
digest.update(chunk)
117+
118+
actual = digest.hexdigest().lower()
119+
if actual != expected:
120+
raise SystemExit(f"checksum mismatch for {path}: expected {expected}, got {actual}")
121+
PY
122+
123+
"$python_bin" - "$archive_path" "$extract_dir" <<'PY'
124+
import pathlib
125+
import sys
126+
import tarfile
127+
import zipfile
128+
129+
archive = pathlib.Path(sys.argv[1])
130+
destination = pathlib.Path(sys.argv[2])
131+
destination.mkdir(parents=True, exist_ok=True)
132+
133+
def ensure_within_destination(relative_name: str) -> None:
134+
target = (destination / relative_name).resolve()
135+
if destination.resolve() not in target.parents and target != destination.resolve():
136+
raise SystemExit(f"archive entry escapes destination: {relative_name}")
137+
138+
if archive.suffix == ".zip":
139+
with zipfile.ZipFile(archive) as handle:
140+
for member in handle.namelist():
141+
ensure_within_destination(member)
142+
handle.extractall(destination)
143+
else:
144+
with tarfile.open(archive, "r:*") as handle:
145+
for member in handle.getnames():
146+
ensure_within_destination(member)
147+
handle.extractall(destination)
148+
PY
149+
150+
extracted_dir="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
151+
if [ -z "$extracted_dir" ]; then
152+
echo "failed to extract Zig archive: $archive_url" >&2
153+
exit 1
154+
fi
155+
156+
rm -rf "$install_dir"
157+
mv "$extracted_dir" "$install_dir"
158+
fi
159+
160+
if [ -n "${GITHUB_PATH:-}" ]; then
161+
printf '%s\n' "$install_dir" >> "$GITHUB_PATH"
162+
else
163+
echo "GITHUB_PATH is not set; add this directory to PATH manually: $install_dir" >&2
164+
fi
165+
166+
"${install_dir}/${zig_bin}" version

.github/workflows/ci.yml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
name: CI
22

3+
env:
4+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
5+
ZIG_VERSION: "0.16.0"
6+
37
on:
48
push:
59
branches: [main]
@@ -28,17 +32,17 @@ jobs:
2832
zig_target: x86_64-windows
2933

3034
steps:
31-
- uses: actions/checkout@v4
35+
- uses: actions/checkout@v6
3236

33-
- name: Install Zig 0.15.2
34-
uses: mlugg/setup-zig@v2
35-
with:
36-
version: 0.15.2
37+
- name: Install Zig 0.16.0
38+
run: bash .github/scripts/install-zig.sh "${ZIG_VERSION}"
3739

38-
- name: Cache .zig-cache
39-
uses: actions/cache@v4
40+
- name: Cache Zig build outputs
41+
uses: actions/cache@v5
4042
with:
41-
path: .zig-cache
43+
path: |
44+
.zig-cache
45+
~/.cache/zig
4246
key: zig-${{ matrix.target }}-${{ hashFiles('src/**/*.zig', 'build.zig', 'build.zig.zon', 'deps/sqlite/**') }}
4347
restore-keys: zig-${{ matrix.target }}-
4448

@@ -87,7 +91,7 @@ jobs:
8791
8892
- name: Upload binary
8993
if: success()
90-
uses: actions/upload-artifact@v4
94+
uses: actions/upload-artifact@v7
9195
with:
9296
name: nulltickets-${{ matrix.target }}
9397
path: zig-out/bin/nulltickets${{ runner.os == 'Windows' && '.exe' || '' }}

.github/workflows/release.yml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
name: Release
22

3+
env:
4+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
5+
ZIG_VERSION: "0.16.0"
6+
37
on:
48
push:
59
tags: ['v*']
@@ -44,18 +48,16 @@ jobs:
4448
ext: ".exe"
4549

4650
steps:
47-
- uses: actions/checkout@v4
51+
- uses: actions/checkout@v6
4852

49-
- name: Install Zig 0.15.2
50-
uses: mlugg/setup-zig@v2
51-
with:
52-
version: 0.15.2
53+
- name: Install Zig 0.16.0
54+
run: bash .github/scripts/install-zig.sh "${ZIG_VERSION}"
5355

5456
- name: Build ReleaseSmall
5557
run: zig build -Doptimize=ReleaseSmall ${{ matrix.zig_target && format('-Dtarget={0}', matrix.zig_target) || '' }}
5658

5759
- name: Upload artifact
58-
uses: actions/upload-artifact@v4
60+
uses: actions/upload-artifact@v7
5961
with:
6062
name: nulltickets-${{ matrix.target }}
6163
path: zig-out/bin/nulltickets${{ matrix.ext }}
@@ -67,7 +69,7 @@ jobs:
6769
contents: write
6870

6971
steps:
70-
- uses: actions/download-artifact@v4
72+
- uses: actions/download-artifact@v8
7173

7274
- name: Rename binaries
7375
run: |
@@ -100,32 +102,32 @@ jobs:
100102
packages: write
101103

102104
steps:
103-
- uses: actions/checkout@v4
105+
- uses: actions/checkout@v6
104106

105107
- name: Set up QEMU
106-
uses: docker/setup-qemu-action@v3
108+
uses: docker/setup-qemu-action@v4
107109

108110
- name: Set up Docker Buildx
109-
uses: docker/setup-buildx-action@v3
111+
uses: docker/setup-buildx-action@v4
110112

111113
- name: Log in to ghcr.io
112-
uses: docker/login-action@v3
114+
uses: docker/login-action@v4
113115
with:
114116
registry: ghcr.io
115117
username: ${{ github.repository_owner }}
116118
password: ${{ secrets.GITHUB_TOKEN }}
117119

118120
- name: Extract metadata
119121
id: meta
120-
uses: docker/metadata-action@v5
122+
uses: docker/metadata-action@v6
121123
with:
122124
images: ghcr.io/${{ github.repository }}
123125
tags: |
124126
type=semver,pattern={{raw}}
125127
type=raw,value=latest
126128
127129
- name: Build and push
128-
uses: docker/build-push-action@v6
130+
uses: docker/build-push-action@v7
129131
with:
130132
context: .
131133
platforms: linux/amd64,linux/arm64

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ bash tests/test_e2e.sh
7272

7373
## 4) Zig + SQLite Rules
7474

75-
- Zig baseline: `0.15.2`.
75+
- Zig baseline: `0.16.0`.
7676
- Use `std.ArrayListUnmanaged(...)=.empty` correctly with allocator on each call.
7777
- Do not rely on allocator leaks for correctness.
7878
- Use `SQLITE_STATIC` (`null`) for sqlite text/blob binds in this codebase.

Dockerfile

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,49 @@
11
# syntax=docker/dockerfile:1
22

33
# -- Stage 1: Build ---------------------------------------------------------
4-
FROM alpine:3.23 AS builder
4+
FROM --platform=$BUILDPLATFORM alpine:3.23 AS builder
55

6-
RUN apk add --no-cache zig musl-dev
6+
ARG ZIG_VERSION=0.16.0
7+
8+
RUN apk add --no-cache bash curl musl-dev python3 tar xz
9+
10+
COPY .github/scripts/install-zig.sh /tmp/install-zig.sh
11+
RUN set -eu; \
12+
export GITHUB_PATH=/tmp/zig-path; \
13+
export RUNNER_OS=Linux; \
14+
case "$(uname -m)" in \
15+
x86_64) export RUNNER_ARCH=X64 ;; \
16+
aarch64|arm64) export RUNNER_ARCH=ARM64 ;; \
17+
*) echo "Unsupported host arch: $(uname -m)" >&2; exit 1 ;; \
18+
esac; \
19+
bash /tmp/install-zig.sh "${ZIG_VERSION}"; \
20+
zig_dir="$(cat /tmp/zig-path)"; \
21+
ln -sf "${zig_dir}/zig" /usr/local/bin/zig; \
22+
zig version
723

824
WORKDIR /app
925
COPY build.zig build.zig.zon ./
1026
COPY src/ src/
1127
COPY deps/ deps/
1228

13-
RUN zig build -Doptimize=ReleaseSmall
29+
ARG TARGETARCH
30+
RUN --mount=type=cache,target=/root/.cache/zig \
31+
--mount=type=cache,target=/app/.zig-cache \
32+
set -eu; \
33+
arch="${TARGETARCH:-}"; \
34+
if [ -z "${arch}" ]; then \
35+
case "$(uname -m)" in \
36+
x86_64) arch="amd64" ;; \
37+
aarch64|arm64) arch="arm64" ;; \
38+
*) echo "Unsupported host arch: $(uname -m)" >&2; exit 1 ;; \
39+
esac; \
40+
fi; \
41+
case "${arch}" in \
42+
amd64) zig_target="x86_64-linux-musl" ;; \
43+
arm64) zig_target="aarch64-linux-musl" ;; \
44+
*) echo "Unsupported TARGETARCH: ${arch}" >&2; exit 1 ;; \
45+
esac; \
46+
zig build -Dtarget="${zig_target}" -Doptimize=ReleaseSmall
1447

1548
# -- Stage 2: Runtime Base (shared) ----------------------------------------
1649
FROM alpine:3.23 AS release-base

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ You do not have to use all three components.
5151

5252
## Tech Stack
5353

54-
- Zig `0.15.2`
54+
- Zig `0.16.0`
5555
- SQLite (vendored, static dependency)
5656
- JSON over HTTP/1.1
5757

build.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void {
1919
.optimize = optimize,
2020
}),
2121
});
22-
exe.linkLibrary(sqlite3_lib);
22+
exe.root_module.linkLibrary(sqlite3_lib);
2323
b.installArtifact(exe);
2424

2525
// Run step
@@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void {
3939
.optimize = optimize,
4040
}),
4141
});
42-
exe_unit_tests.linkLibrary(sqlite3_lib);
42+
exe_unit_tests.root_module.linkLibrary(sqlite3_lib);
4343
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
4444
const test_step = b.step("test", "Run unit tests");
4545
test_step.dependOn(&run_exe_unit_tests.step);

build.zig.zon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
.name = .nulltickets,
33
.version = "2026.3.2",
44
.fingerprint = 0x8d7cc7c0ca874218,
5-
.minimum_zig_version = "0.15.2",
5+
.minimum_zig_version = "0.16.0",
66
.dependencies = .{
77
.sqlite3 = .{
88
.path = "deps/sqlite",

0 commit comments

Comments
 (0)