diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..5beb9a57 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,117 @@ +name: Release + +# Push a `vX.Y.Z` tag to cut a release: every supported target is built, the +# archives are attached to a GitHub release, and the Homebrew tap is updated. +# A tag carrying a suffix (`v0.3.0-rc1`) publishes as a prerelease and leaves +# the tap alone — use one to prove the matrix before the real tag. +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + build: + name: ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # macOS builds run natively per arch. scripts/macho-align.sh (the + # ld64 repack of libtile57.a) invokes `ld -r` and libtool without + # -arch, so it can only repack for the host. + - { target: aarch64-macos, os: macos-latest } + - { target: x86_64-macos, os: macos-15-intel } + # -gnu, not -musl: build.zig retargets the CLI to static musl on a + # glibc host anyway, so these carry a portable `tile57` AND a + # libtile57.a that a glibc C host can link. + - { target: x86_64-linux-gnu, os: ubuntu-latest, deb: amd64 } + - { target: aarch64-linux-gnu, os: ubuntu-latest, deb: arm64 } + - { target: x86_64-windows-gnu, os: ubuntu-latest } + - { target: aarch64-windows-gnu, os: ubuntu-latest } + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + # The version is written in three places and the tag is a fourth. A + # mismatch ships a binary whose `tile57 version` contradicts its download. + - name: Check the tag against the version in the source + run: | + v="${GITHUB_REF_NAME#v}"; v="${v%%-*}" + grep -q "\.version = \"$v\"" build.zig.zon + grep -q "pub const version = \"$v\"" src/tile57.zig + grep -q "pub const VERSION = \"tile57 $v\"" tools/common.zig + + - name: Build + run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseFast + + - name: Package + run: scripts/package-release.sh "${GITHUB_REF_NAME#v}" "${{ matrix.target }}" "${{ matrix.deb }}" + + - uses: actions/upload-artifact@v7 + with: + name: ${{ matrix.target }} + path: dist/* + + release: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v8 + with: + path: dist + merge-multiple: true + + - name: Checksums + run: cd dist && shasum -a 256 tile57* > SHA256SUMS + + - name: Publish + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh release create "$GITHUB_REF_NAME" --generate-notes --verify-tag \ + ${{ contains(github.ref_name, '-') && '--prerelease' || '' }} \ + dist/* + + homebrew: + needs: release + if: ${{ !contains(github.ref_name, '-') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/download-artifact@v8 + with: + path: dist + merge-multiple: true + + # A token with write access to the tap repo. Without it the release still + # stands; only the tap goes stale. + - name: Update the Homebrew tap + env: + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + run: | + if [ -z "$TAP_TOKEN" ]; then + echo "::warning::HOMEBREW_TAP_TOKEN is not set — skipping the tap update" + exit 0 + fi + v="${GITHUB_REF_NAME#v}" + git clone --depth 1 \ + "https://x-access-token:$TAP_TOKEN@github.com/beetlebugorg/homebrew-tap" tap + mkdir -p tap/Formula + scripts/brew-formula.sh "$v" dist > tap/Formula/tile57.rb + cd tap + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add Formula/tile57.rb + git diff --cached --quiet || git commit -m "tile57 $v" + git push diff --git a/.gitignore b/.gitignore index d6eeb1d8..0f6bff8e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ cache.sqlite /.zig-cache/ /zig-out/ +# Release archives staged by scripts/package-release.sh +/dist/ + # Editor / OS *.swp .DS_Store diff --git a/README.md b/README.md index 42088d31..44908985 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,7 @@ diagrams it carries. ## Start here ```sh -git submodule update --init --recursive # the vendored S-101 catalogue -zig build # writes zig-out/bin/tile57 +brew install beetlebugorg/tap/tile57 # or grab a binary from Releases tile57 bake ENC_ROOT -o out/ # every chart -> its own archive tile57 png ENC_ROOT --view -76.48,38.974,15 --size 1600x1200 -o chart.png @@ -75,8 +74,14 @@ tile57 bake harbour.KAP -o out/ # an RNC sheet -> the same archive tile57 png ENC_ROOT --over-image --view -76.48,38.974,15 -o over.png ``` -The first command bakes a catalogue. The second draws a chart straight to a PNG. -The last one removes the chart's solid areas, so you can draw it over a photo. +`bake` turns a catalogue into per-chart archives. `png` draws a chart straight to +an image. `--over-image` removes the chart's solid areas, so you can draw it over +a photo. + +Binaries for macOS, Linux and Windows are attached to every +[release](https://github.com/beetlebugorg/tile57/releases), alongside a `.deb` +and the static library. To build it yourself you need Zig 0.16 and the +submodules — see [Installation](docs/docs/installation.md). ## What you can get diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md index e2377370..3f3d8401 100644 --- a/docs/docs/contributing.md +++ b/docs/docs/contributing.md @@ -76,6 +76,32 @@ Direct pull requests are welcome. Please: [Installation](./installation.md)). - Expect review for correctness and safety, the same as AI-assisted changes. +## Cutting a release + +The version lives in three files and has to agree with the tag — the release +build checks all three and stops if they diverge: + +- `build.zig.zon` — `.version` +- `src/tile57.zig` — `pub const version` +- `tools/common.zig` — `pub const VERSION` + +Bump them, commit, then tag and push: + +```sh +git tag v0.4.0 && git push origin v0.4.0 +``` + +`.github/workflows/release.yml` builds macOS, Linux and Windows on x86-64 and +arm64, attaches the archives, the `.deb`s and `SHA256SUMS` to a GitHub release, +and updates the Homebrew tap. A tag with a suffix — `v0.4.0-rc1` — publishes as +a prerelease and leaves the tap alone; use one to exercise the matrix before the +real tag. `scripts/package-release.sh ` produces the same +archive locally after a `zig build`. + +The tap lives in [beetlebugorg/homebrew-tap](https://github.com/beetlebugorg/homebrew-tap) +and its formula is generated, not hand-edited; the release workflow needs a +`HOMEBREW_TAP_TOKEN` secret with write access to it. + ## The process 1. **Open** an issue or a requirement/prototype. diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 02b8c6d8..245df7ec 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -6,63 +6,94 @@ sidebar_position: 2 # Installation -tile57 builds from source with **Zig 0.16** — no CMake, no system libraries -beyond Zig itself. There are no pre-built binaries. +Every [release](https://github.com/beetlebugorg/tile57/releases) ships pre-built +binaries for macOS, Linux and Windows on x86-64 and arm64. Each archive carries +the `tile57` CLI, the static library `libtile57.a`, and the C header +`include/tile57.h`. The IHO S-101 Portrayal Catalogue is embedded in both, so +nothing else has to be on disk at run time. -## 1. Clone + fetch the submodules +## Homebrew (macOS and Linux) ```sh -git clone https://github.com/beetlebugorg/tile57.git -cd tile57 -git submodule update --init --recursive +brew install beetlebugorg/tap/tile57 ``` -The vendored **IHO S-101 Portrayal Catalogue** comes in as a submodule (under -`vendor/`). It is a **build-time** dependency: `zig build` embeds the -catalogue (the Lua portrayal rules plus the symbols, line styles, area fills and -colour profile) directly into the binary via `@embedFile`, so the resulting -`tile57` needs no on-disk catalogue at runtime. Lua 5.4 is vendored under -`vendor/lua` and compiled in, so no system Lua is needed either. +## Debian and Ubuntu + +```sh +curl -LO https://github.com/beetlebugorg/tile57/releases/latest/download/tile57_0.3.0_amd64.deb +sudo apt install ./tile57_0.3.0_amd64.deb +``` -## 2. Zig 0.16.0 (required) +`arm64` is published alongside `amd64`. There is no apt repository — the `.deb` +is a release asset, so upgrades mean downloading the next one. -The engine, the `tile57` CLI, and the static library all need **Zig 0.16.0**. -Install it from [ziglang.org/download](https://ziglang.org/download/) (pin -0.16.0) and put it on your `PATH`. +## Direct download -## 3. Build + test +Pick the archive for your platform from the +[latest release](https://github.com/beetlebugorg/tile57/releases/latest) — +`tile57---.tar.gz`, or `.zip` for Windows — and put `bin/tile57` +on your `PATH`: ```sh -zig build # builds zig-out/bin/tile57 + libtile57.a -zig build test # runs the test suite +tar xzf tile57-0.3.0-aarch64-macos.tar.gz +sudo install -m755 tile57-0.3.0-aarch64-macos/bin/tile57 /usr/local/bin/ +tile57 version ``` -`zig build` produces: +Every release includes a `SHA256SUMS` file. Verify before installing: + +```sh +curl -LO https://github.com/beetlebugorg/tile57/releases/latest/download/SHA256SUMS +shasum -a 256 -c SHA256SUMS --ignore-missing +``` + +macOS binaries are not notarized. Gatekeeper quarantines a downloaded archive, so +clear it once — `xattr -d com.apple.quarantine ./tile57` — or install through +Homebrew, which does it for you. + +## Build from source + +Building needs **Zig 0.16.0** and nothing else — no CMake, no system libraries. +Install Zig from [ziglang.org/download](https://ziglang.org/download/) (pin +0.16.0) and put it on your `PATH`. + +```sh +git clone https://github.com/beetlebugorg/tile57.git +cd tile57 +git submodule update --init --recursive # the vendored S-101 catalogue +zig build # builds zig-out/bin/tile57 + libtile57.a +zig build test # runs the test suite +``` | Target | What it is | |--------|-----------| | `tile57` (`zig-out/bin/tile57`) | the offline CLI: bake charts/ENC_ROOTs to PMTiles or a chart bundle, and emit portrayal assets. | | `libtile57.a` | the static library behind the [C ABI](./c-api.md) (`include/tile57.h`). | -The engine is also a Zig package named `tile57` (v0.3.0); a Zig consumer -depends on it and uses `@import("tile57")` — see the [Zig API](./zig-api.md). +The **IHO S-101 Portrayal Catalogue** comes in as a git submodule under `vendor/`. +It is a *build-time* dependency: `zig build` embeds the catalogue — the Lua +portrayal rules plus the symbols, line styles, area fills and colour profile — +directly into the binary via `@embedFile`. Lua 5.4 is vendored under `vendor/lua` +and compiled in, so no system Lua is needed either. + +## As a Zig package + +The engine is a Zig package named `tile57` (v0.3.0). Fetch it by tag: -:::note Consume it as a path dependency (for now) -Depend on a local clone via a `.path` dependency in your `build.zig.zon`, -with the clone's submodules initialised: +```sh +zig fetch --save "https://github.com/beetlebugorg/tile57/archive/refs/tags/v0.3.0.tar.gz" +``` ```zig -.dependencies = .{ - .tile57 = .{ .path = "../tile57" }, -}, +const tile57 = b.dependency("tile57", .{ .target = target, .optimize = optimize }); +exe.root_module.addImport("tile57", tile57.module("tile57")); ``` -**Known issue:** fetching by URL/hash (`zig fetch --save `) does not work -yet — `build.zig.zon`'s `.paths` currently ships only `build.zig`, -`build.zig.zon`, and `src/`, so a fetched package is missing `vendor/` (the -embedded catalogue + Lua), `include/`, `tools/`, and the LICENSE, and cannot -build. Until that's fixed, use a path dependency. -::: +A fetched package carries its own copy of the portrayal catalogue (Zig's fetcher +does not follow git submodules, so the catalogue arrives as a lazy package +dependency instead) — no submodule init needed. See the +[Zig API](./zig-api.md) for what the module exposes. ## Runtime knob diff --git a/docs/docs/zig-api.md b/docs/docs/zig-api.md index 387541b2..26fc047e 100644 --- a/docs/docs/zig-api.md +++ b/docs/docs/zig-api.md @@ -14,10 +14,9 @@ source charts bake once to per-chart archives, and every output is produced from baked archives. These pages are grouped the same way as the [C API](./c-api.md). :::note -Add it as a **path dependency** on a local clone (submodules initialised) — -`zig fetch` by URL/hash is currently broken because the package's declared -`.paths` omit `vendor/` and `include/`. See -[Installation](./installation.md). +`zig fetch --save "https://github.com/beetlebugorg/tile57/archive/refs/tags/v0.3.0.tar.gz"` +adds it. A fetched package carries the S-101 catalogue as a lazy dependency, so +there are no submodules to initialise. See [Installation](./installation.md). ::: ## Sections diff --git a/scripts/brew-formula.sh b/scripts/brew-formula.sh new file mode 100755 index 00000000..06aebde2 --- /dev/null +++ b/scripts/brew-formula.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Print the Homebrew formula for a released version, reading each archive's +# sha256 out of (the tarballs that release.yml just published). +# The tap job pipes this into Formula/tile57.rb. +# +# Usage: brew-formula.sh +set -euo pipefail + +version="$1" +dist="$2" +base="https://github.com/beetlebugorg/tile57/releases/download/v$version" + +sha() { shasum -a 256 "$dist/tile57-$version-$1.tar.gz" | cut -d' ' -f1; } + +# Resolved before the heredoc: a command substitution that fails inside one is +# not caught by `set -e`, and a missing archive would emit an empty sha256. +mac_arm="$(sha aarch64-macos)" +mac_intel="$(sha x86_64-macos)" +linux_arm="$(sha aarch64-linux-gnu)" +linux_intel="$(sha x86_64-linux-gnu)" + +cat <&1") + end +end +EOF diff --git a/scripts/package-release.sh b/scripts/package-release.sh new file mode 100755 index 00000000..06275b30 --- /dev/null +++ b/scripts/package-release.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Stage a built tree into the release archive for one target, and — when a +# Debian architecture is given — the matching .deb. Run it after +# `zig build -Dtarget= -Doptimize=ReleaseFast`; everything lands in +# dist/. Invoked per target by .github/workflows/release.yml. +# +# Usage: package-release.sh [deb-arch] +set -euo pipefail + +version="$1" +target="$2" +deb_arch="${3:-}" + +root="$(cd "$(dirname "$0")/.." && pwd)" +out="$root/dist" +name="tile57-$version-$target" +stage="$out/$name" + +exe=tile57 +case "$target" in *windows*) exe=tile57.exe ;; esac + +rm -rf "$stage" +mkdir -p "$stage/bin" "$stage/lib" "$stage/include" +cp "$root/zig-out/bin/$exe" "$stage/bin/" +cp "$root"/zig-out/lib/* "$stage/lib/" +cp "$root/include/tile57.h" "$stage/include/" +cp "$root/LICENSE" "$root/THIRD_PARTY_LICENSES.md" "$root/README.md" "$stage/" + +cd "$out" +case "$target" in + *windows*) rm -f "$name.zip" && zip -qr "$name.zip" "$name" ;; + *) tar czf "$name.tar.gz" "$name" ;; +esac + +# The same payload under /usr, for `apt install ./tile57_*.deb`. There is no +# apt repository — the .deb is a release asset. +if [ -n "$deb_arch" ]; then + pkg="$out/deb" + rm -rf "$pkg" + mkdir -p "$pkg/DEBIAN" "$pkg/usr/bin" "$pkg/usr/lib" "$pkg/usr/include" \ + "$pkg/usr/share/doc/tile57" + install -m755 "$stage/bin/tile57" "$pkg/usr/bin/" + install -m644 "$stage/lib/libtile57.a" "$pkg/usr/lib/" + install -m644 "$stage/include/tile57.h" "$pkg/usr/include/" + install -m644 "$root/LICENSE" "$pkg/usr/share/doc/tile57/copyright" + cat > "$pkg/DEBIAN/control" < +Section: science +Priority: optional +Homepage: https://github.com/beetlebugorg/tile57 +Description: Nautical chart engine for IHO S-101 and S-57 charts + tile57 reads IHO S-101 and S-57 electronic navigational charts and renders + them with the official IHO S-101 Portrayal Catalogue: vector tiles with a + matching MapLibre S-52 style, a draw-ready GPU scene, PNG, and PDF. It also + reads raster charts (MBTiles, BSB/KAP) and draws the official chart over + them. Ships the tile57 CLI plus libtile57.a and its C header. + . + Not for navigation. +EOF + dpkg-deb --build --root-owner-group "$pkg" "$out/tile57_${version}_${deb_arch}.deb" + rm -rf "$pkg" +fi + +rm -rf "$stage" +ls -l "$out"