Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions .github/scripts/install-zig.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env bash
set -euo pipefail

if [ "$#" -ne 1 ]; then
echo "usage: $0 <zig-version>" >&2
exit 1
fi

version="$1"

python_bin="${PYTHON:-python3}"
if ! command -v "$python_bin" >/dev/null 2>&1; then
python_bin="python"
fi
if ! command -v "$python_bin" >/dev/null 2>&1; then
echo "python is required to install Zig" >&2
exit 1
fi

runner_os="${RUNNER_OS:-$(uname -s)}"
runner_arch="${RUNNER_ARCH:-$(uname -m)}"

case "$runner_os" in
Linux | linux)
zig_os="linux"
;;
Darwin | macOS)
zig_os="macos"
;;
Windows | MINGW* | MSYS* | CYGWIN*)
zig_os="windows"
;;
*)
echo "unsupported runner OS: $runner_os" >&2
exit 1
;;
esac

case "$runner_arch" in
X64 | x86_64 | amd64)
zig_arch="x86_64"
;;
ARM64 | arm64 | aarch64)
zig_arch="aarch64"
;;
*)
echo "unsupported runner architecture: $runner_arch" >&2
exit 1
;;
esac

host_key="${zig_arch}-${zig_os}"
tool_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/wasm3-zig"
install_dir="${tool_root}/${version}/${host_key}"
zig_bin="zig"
if [ "$zig_os" = "windows" ]; then
zig_bin="zig.exe"
fi

if [ ! -x "${install_dir}/${zig_bin}" ]; then
mkdir -p "$(dirname "$install_dir")"

zig_metadata="$(
"$python_bin" - "$version" "$host_key" <<'PY'
import json
import sys
import urllib.request

version = sys.argv[1]
host_key = sys.argv[2]

with urllib.request.urlopen("https://ziglang.org/download/index.json") as response:
data = json.load(response)

host = data.get(version, {}).get(host_key)
if not host:
raise SystemExit(f"missing Zig download metadata for version={version!r} host={host_key!r}")

archive_url = host.get("tarball") or host.get("zip")
checksum = host.get("shasum") or ""
if not archive_url:
raise SystemExit(f"missing archive URL for version={version!r} host={host_key!r}")

print(archive_url)
print(checksum)
PY
)"

archive_url="$(printf '%s\n' "$zig_metadata" | sed -n '1p')"
expected_sha="$(printf '%s\n' "$zig_metadata" | sed -n '2p')"
if [ -z "$archive_url" ]; then
echo "failed to resolve Zig download URL" >&2
exit 1
fi

archive_name="${archive_url##*/}"
archive_dir="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/zig-archive.XXXXXX")"
archive_path="${archive_dir}/${archive_name}"
extract_dir="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/zig-extract.XXXXXX")"
trap 'rm -rf "$archive_dir"; rm -rf "$extract_dir"' EXIT

curl -fsSL --retry 3 --retry-all-errors "$archive_url" -o "$archive_path"

"$python_bin" - "$archive_path" "$expected_sha" <<'PY'
import hashlib
import sys

path = sys.argv[1]
expected = sys.argv[2].strip().lower()
if not expected:
raise SystemExit(0)

digest = hashlib.sha256()
with open(path, "rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)

actual = digest.hexdigest().lower()
if actual != expected:
raise SystemExit(f"checksum mismatch for {path}: expected {expected}, got {actual}")
PY

"$python_bin" - "$archive_path" "$extract_dir" <<'PY'
import pathlib
import sys
import tarfile
import zipfile

archive = pathlib.Path(sys.argv[1])
destination = pathlib.Path(sys.argv[2])
destination.mkdir(parents=True, exist_ok=True)

def ensure_within_destination(relative_name: str) -> None:
target = (destination / relative_name).resolve()
if destination.resolve() not in target.parents and target != destination.resolve():
raise SystemExit(f"archive entry escapes destination: {relative_name}")

if archive.suffix == ".zip":
with zipfile.ZipFile(archive) as handle:
for member in handle.namelist():
ensure_within_destination(member)
handle.extractall(destination)
else:
with tarfile.open(archive, "r:*") as handle:
for member in handle.getnames():
ensure_within_destination(member)
handle.extractall(destination)
PY

extracted_dir="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
if [ -z "$extracted_dir" ]; then
echo "failed to extract Zig archive: $archive_url" >&2
exit 1
fi

rm -rf "$install_dir"
mv "$extracted_dir" "$install_dir"
fi

if [ -n "${GITHUB_PATH:-}" ]; then
printf '%s\n' "$install_dir" >> "$GITHUB_PATH"
else
echo "GITHUB_PATH is not set; add this directory to PATH manually: $install_dir" >&2
fi

"${install_dir}/${zig_bin}" version
78 changes: 78 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: CI

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
ZIG_VERSION: "0.16.0"

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: linux-x86_64
zig_target: x86_64-linux-musl
- os: macos-latest
target: macos-aarch64
zig_target: aarch64-macos
- os: windows-latest
target: windows-x86_64
zig_target: x86_64-windows

steps:
- uses: actions/checkout@v6

- name: Install Zig 0.16.0
run: bash .github/scripts/install-zig.sh "${ZIG_VERSION}"

- name: Cache Zig build outputs
uses: actions/cache@v5
with:
path: |
.zig-cache
~/.cache/zig
key: zig-${{ matrix.target }}-${{ hashFiles('build.zig', 'build.zig.zon', 'source/**') }}
restore-keys: zig-${{ matrix.target }}-

- name: Build native Debug
run: zig build

- name: Build ReleaseSmall
run: zig build -Dtarget=${{ matrix.zig_target }} -Doptimize=ReleaseSmall

- name: Preserve package
run: |
mkdir -p ci-artifacts/include ci-artifacts/lib
find zig-out/include -maxdepth 1 -type f -name '*.h' -exec cp {} ci-artifacts/include/ \;
cp LICENSE ci-artifacts/
found_lib=0
for artifact in zig-out/lib/libwasm3.a zig-out/lib/wasm3.lib; do
if [ -f "$artifact" ]; then
cp "$artifact" ci-artifacts/lib/
found_lib=1
fi
done
if [ "$found_lib" -eq 0 ]; then
echo "No wasm3 library artifact found in zig-out/lib" >&2
ls -la zig-out/lib >&2 || true
exit 1
fi

- name: Upload package
if: success()
uses: actions/upload-artifact@v7
with:
name: wasm3-${{ matrix.target }}
path: ci-artifacts
127 changes: 127 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Release

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
ZIG_VERSION: "0.16.0"

on:
push:
tags: ['v*']
workflow_dispatch:

jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
strategy:
matrix:
include:
- os: ubuntu-latest
target: linux-x86_64
zig_target: x86_64-linux-musl
- os: ubuntu-latest
target: linux-aarch64
zig_target: aarch64-linux-musl
- os: ubuntu-latest
target: linux-riscv64
zig_target: riscv64-linux-musl
- os: macos-latest
target: macos-aarch64
zig_target: aarch64-macos
- os: macos-latest
target: macos-x86_64
zig_target: x86_64-macos
- os: windows-latest
target: windows-x86_64
zig_target: x86_64-windows
- os: windows-latest
target: windows-aarch64
zig_target: aarch64-windows

steps:
- uses: actions/checkout@v6

- name: Install Zig 0.16.0
run: bash .github/scripts/install-zig.sh "${ZIG_VERSION}"

- name: Build ReleaseSmall
run: zig build -Doptimize=ReleaseSmall -Dtarget=${{ matrix.zig_target }}

- name: Prepare package
run: |
mkdir -p release-package/include release-package/lib
find zig-out/include -maxdepth 1 -type f -name '*.h' -exec cp {} release-package/include/ \;
cp LICENSE release-package/
found_lib=0
for artifact in zig-out/lib/libwasm3.a zig-out/lib/wasm3.lib; do
if [ -f "$artifact" ]; then
cp "$artifact" release-package/lib/
found_lib=1
fi
done
if [ "$found_lib" -eq 0 ]; then
echo "No wasm3 library artifact found in zig-out/lib" >&2
ls -la zig-out/lib >&2 || true
exit 1
fi

- name: Upload package
uses: actions/upload-artifact@v7
with:
name: wasm3-package-${{ matrix.target }}
path: release-package

source:
name: Prepare source archive
runs-on: ubuntu-latest
defaults:
run:
shell: bash

steps:
- uses: actions/checkout@v6

- name: Create source archive
run: |
archive_name="wasm3-source-${GITHUB_REF_NAME}.tar.gz"
tar \
--exclude='.git' \
--exclude='.zig-cache' \
--exclude='zig-out' \
-czf "/tmp/${archive_name}" .
mv "/tmp/${archive_name}" .
echo "ARCHIVE_NAME=${archive_name}" >> "$GITHUB_ENV"

- name: Upload source archive
uses: actions/upload-artifact@v7
with:
name: wasm3-source
path: ${{ env.ARCHIVE_NAME }}

release:
needs: [build, source]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/download-artifact@v8

- name: Package release archives
run: |
for dir in wasm3-package-*; do
target="${dir#wasm3-package-}"
tar -czf "wasm3-${target}.tar.gz" -C "$dir" .
done

- name: Create release
uses: softprops/action-gh-release@v2
with:
files: |
wasm3-*.tar.gz
wasm3-source/*.tar.gz
generate_release_notes: true
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub fn build(b: *std.Build) void {
lib.root_module.addCSourceFile(.{ .file = b.path("source/m3_info.c") });
lib.root_module.addCSourceFile(.{ .file = b.path("source/m3_module.c") });
lib.root_module.addCSourceFile(.{ .file = b.path("source/m3_parse.c") });
lib.installHeadersDirectory(b.path("source"), "", .{});

b.installArtifact(lib);
}
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.name = .wasm3,
.version = "0.5.1",
.fingerprint = 0x377ef951351db386,
.minimum_zig_version = "0.15.2",
.minimum_zig_version = "0.16.0",
.paths = .{
"build.zig",
"build.zig.zon",
Expand Down