add nix flake/fix release mode tests
This commit is contained in:
parent
95b2db5e02
commit
3c9ec72b88
7 changed files with 201 additions and 17 deletions
|
|
@ -55,13 +55,27 @@ jobs:
|
|||
# Engine, CLI, TUI, the C ABI from Zig, the JNI layer against a synthetic
|
||||
# function table, and the C ABI from C through the installed header.
|
||||
run: zig build test --summary all
|
||||
- name: Run tests in each release mode
|
||||
# What ships is not Debug: the CLI and TUI are ReleaseSafe, the Android library
|
||||
# ReleaseSmall. Debug fills undefined memory with 0xaa, so code that reads
|
||||
# something it never wrote can pass there by luck and fail in a release build:
|
||||
# the JNI tests did exactly that. ReleaseFast has no safety checks at all, which
|
||||
# is where such a read shows up most readily, so it runs too.
|
||||
run: |
|
||||
for mode in ReleaseSafe ReleaseSmall ReleaseFast; do
|
||||
echo "== $mode"
|
||||
zig build test -Doptimize="$mode" --summary all
|
||||
done
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
- name: Prove the JNI table against a real JVM
|
||||
run: zig build jvm-test --summary all
|
||||
# Debug, and ReleaseSmall: the optimization the Android library ships with.
|
||||
run: |
|
||||
zig build jvm-test --summary all
|
||||
zig build jvm-test -Doptimize=ReleaseSmall --summary all
|
||||
- name: Package the CLI and TUI
|
||||
# One binary holds both. Static musl on Linux, so it runs on any distribution.
|
||||
run: |
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -2,6 +2,10 @@
|
|||
.zig-cache/
|
||||
zig-out/
|
||||
|
||||
# nix build output links
|
||||
result
|
||||
result-*
|
||||
|
||||
# Gradle and Android build artifacts
|
||||
android/.gradle/
|
||||
android/.kotlin/
|
||||
|
|
|
|||
49
README.md
49
README.md
|
|
@ -104,6 +104,35 @@ Android ties an app to its signing key permanently, so a published APK cannot up
|
|||
copy you built yourself (which is signed with your debug key), and the reverse. Uninstall
|
||||
one before installing the other; the app's saved state goes with it.
|
||||
|
||||
### With nix
|
||||
|
||||
This repository is a flake, for the CLI and TUI (the Android app stays with gradle). To
|
||||
add it to a `buildEnv`-style home profile:
|
||||
|
||||
```nix
|
||||
inputs = {
|
||||
tally.url = "git+https://git.lerch.org/lobo/tally.git";
|
||||
# Optional: reuse your own nixpkgs instead of instantiating another one.
|
||||
tally.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
```
|
||||
|
||||
then add `tally.packages.${system}.default` to your package list. Or run it directly:
|
||||
|
||||
```
|
||||
nix run git+https://git.lerch.org/lobo/tally.git -- '2^100 + 1'
|
||||
```
|
||||
|
||||
The package is the `tally` binary, plus the engine as `lib/libtally-engine.a`,
|
||||
`lib/libtally.so` and `include/tally.h`. `nix flake update tally` picks up a new version.
|
||||
The build runs `zig build test` in the sandbox, in ReleaseSafe like the binary it
|
||||
installs, so a version that fails its tests will not install. `nix develop` gives a shell
|
||||
with Zig 0.16 and zls.
|
||||
|
||||
Changing the dependencies in `build.zig.zon` invalidates the `zigDeps` hash in
|
||||
`nix/package.nix`. To refresh it: set the hash to `lib.fakeHash`, build, and paste the
|
||||
hash nix reports.
|
||||
|
||||
## Building
|
||||
|
||||
Every tool comes from [mise](https://mise.jdx.dev), pinned in [.mise.toml](.mise.toml):
|
||||
|
|
@ -186,9 +215,13 @@ A few decisions shape everything else, and the design document explains each:
|
|||
- `zig build test`: about a thousand Zig tests across the engine, CLI, TUI (rendering
|
||||
real frames and reading the cells back), the C ABI, and the JNI layer against a
|
||||
synthetic function table; plus a C program that links the shared library through
|
||||
`tally.h`, which catches a header that disagrees with the library.
|
||||
`tally.h`, which catches a header that disagrees with the library. CI also runs it with
|
||||
`-Doptimize=ReleaseSafe`, `ReleaseSmall` and `ReleaseFast`: what ships is not Debug,
|
||||
and Debug's 0xaa fill of undefined memory can hide a read of something never written
|
||||
(it hid one in the JNI tests).
|
||||
- `zig build jvm-test`: the JNI entry points from Java, which is the only way to check
|
||||
the function-table indices without a device.
|
||||
the function-table indices without a device. CI runs it in Debug and in ReleaseSmall,
|
||||
the optimization the Android library ships with.
|
||||
- `cd android && gradle testDebugUnitTest`: JVM tests of the app's rules (the converter,
|
||||
programmer mode, saved state), no device needed.
|
||||
- `cd android && gradle connectedAndroidTest`: the engine through the real app on a
|
||||
|
|
@ -203,12 +236,12 @@ punctuation: the source is ASCII.
|
|||
|
||||
[.forgejo/workflows/build.yaml](.forgejo/workflows/build.yaml) runs on every push:
|
||||
|
||||
| Job | Runner | What |
|
||||
|---------------------|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Engine, CLI and TUI | `ubuntu-latest` | `zig fmt --check`, zlint, build, `zig build test`, `zig build jvm-test`, then release builds for x86_64 and aarch64 Linux and aarch64 macOS |
|
||||
| Android | `ubuntu-latest` | `zig build android`, the library audit, the JVM unit tests, debug and unsigned release APKs (with lint's release checks) |
|
||||
| Sign | `ubuntu-latest-with-hsm` | master only: signs the APK with the HSM, then every release file with a detached signature logged to sigstore |
|
||||
| Publish | `ubuntu-latest` | master only: uploads the signed files to the generic package registry under the short SHA and `latest` |
|
||||
| Job | Runner | What |
|
||||
|---------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Engine, CLI and TUI | `ubuntu-latest` | `zig fmt --check`, zlint, build, `zig build test` in Debug and each release mode, `zig build jvm-test` in Debug and ReleaseSmall, then release builds for x86_64 and aarch64 Linux and aarch64 macOS |
|
||||
| Android | `ubuntu-latest` | `zig build android`, the library audit, the JVM unit tests, debug and unsigned release APKs (with lint's release checks) |
|
||||
| Sign | `ubuntu-latest-with-hsm` | master only: signs the APK with the HSM, then every release file with a detached signature logged to sigstore |
|
||||
| Publish | `ubuntu-latest` | master only: uploads the signed files to the generic package registry under the short SHA and `latest` |
|
||||
|
||||
Every job reports to ntfy. Zlint and Gradle are downloaded at pinned versions and checked
|
||||
against pinned SHA-256 digests; Zig's version comes from `build.zig.zon`.
|
||||
|
|
|
|||
|
|
@ -508,20 +508,24 @@ const FakeJvm = struct {
|
|||
|
||||
var current: ?*FakeJvm = null;
|
||||
|
||||
/// Puts every field in its starting state, defaults included: the tests declare the
|
||||
/// fake `undefined` and call this, so a field set nowhere here would be whatever the
|
||||
/// stack held. It was: `misplace_new_string` was never reset, Debug's 0xaa fill
|
||||
/// happened to read as false, and release builds, which do not fill, failed the JNI
|
||||
/// tests whenever the leftover byte was odd.
|
||||
fn init(self: *FakeJvm) void {
|
||||
self.table = .{ .entries = @splat(null) };
|
||||
self.* = .{
|
||||
.table = .{ .entries = @splat(null) },
|
||||
.vm_table = .{ .entries = @splat(null) },
|
||||
// Addresses inside `self`, which is where this value is being written.
|
||||
.env = .{ .functions = &self.table },
|
||||
.vm = .{ .functions = &self.vm_table },
|
||||
};
|
||||
self.table.entries[@intFromEnum(EnvIndex.get_version)] = @ptrCast(&getVersion);
|
||||
self.table.entries[@intFromEnum(EnvIndex.new_string_utf)] = @ptrCast(&newStringUtf);
|
||||
self.table.entries[@intFromEnum(EnvIndex.get_string_utf_chars)] = @ptrCast(&getStringUtfChars);
|
||||
self.table.entries[@intFromEnum(EnvIndex.release_string_utf_chars)] = @ptrCast(&releaseStringUtfChars);
|
||||
self.env = .{ .functions = &self.table };
|
||||
|
||||
self.vm_table = .{ .entries = @splat(null) };
|
||||
self.vm_table.entries[@intFromEnum(VmIndex.get_env)] = @ptrCast(&getEnv);
|
||||
self.vm = .{ .functions = &self.vm_table };
|
||||
|
||||
self.made_len = 0;
|
||||
self.releases = 0;
|
||||
current = self;
|
||||
}
|
||||
|
||||
|
|
|
|||
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1791142733,
|
||||
"narHash": "sha256-x1jawtfPqMTQYpt1d6H+9rxXweqTzU5hYhdfz0T5MPM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "466eaf945dddfd32abd006bd94cc55660fdb56c8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "master",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
46
flake.nix
Normal file
46
flake.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
description = "Calculator with exact arithmetic, unit conversion and a programmer mode, as a CLI and TUI";
|
||||
|
||||
inputs.nixpkgs.url = "nixpkgs/master";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
|
||||
# Helper to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
|
||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
tally = pkgs.callPackage ./nix/package.nix {
|
||||
# Pinned deliberately rather than tracking `pkgs.zig`: build.zig.zon
|
||||
# declares 0.16.0 as the minimum, and 0.16 was a large standard
|
||||
# library refactor, so a newer default would not build unchanged.
|
||||
zig = pkgs.zig_0_16;
|
||||
# The git-tracked tree, which keeps .zig-cache, zig-out, zig-pkg and
|
||||
# the Android build outputs out of the derivation.
|
||||
src = self;
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit tally;
|
||||
default = tally;
|
||||
});
|
||||
|
||||
devShells = forAllSystems (system:
|
||||
let pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
# `nix develop` for the CLI/TUI without mise. The versions here are the
|
||||
# same ones .mise.toml pins; the Android toolchain stays with mise.
|
||||
default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.zig_0_16
|
||||
pkgs.zls
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
57
nix/package.nix
Normal file
57
nix/package.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{ lib, stdenv, zig, src }:
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "tally";
|
||||
# Kept in step with build.zig.zon.
|
||||
version = "0.1.0";
|
||||
|
||||
inherit src;
|
||||
|
||||
__structuredAttrs = true;
|
||||
strictDeps = true;
|
||||
|
||||
# Zig resolves build.zig.zon dependencies (libvaxis) over the network, which the
|
||||
# build sandbox forbids. `fetchDeps` runs `zig build --fetch` inside a fixed-output
|
||||
# derivation, so the fetch happens once against a known hash and the real build
|
||||
# stays offline.
|
||||
#
|
||||
# This hash changes whenever build.zig.zon's dependencies change. To update it: set
|
||||
# `lib.fakeHash`, run the build, and paste the hash nix reports.
|
||||
zigDeps = zig.fetchDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
fetchAll = true;
|
||||
hash = "sha256-Wy/H3z2dBsF/WTF2LrCofOuE15pbgdwEJLSUrKCOQGI=";
|
||||
};
|
||||
|
||||
postConfigure = ''
|
||||
# A writable copy rather than a symlink into the store: Zig writes cache
|
||||
# metadata alongside the fetched packages while it verifies them.
|
||||
cp -rLT ${finalAttrs.zigDeps} "$ZIG_GLOBAL_CACHE_DIR/p"
|
||||
chmod -R u+w "$ZIG_GLOBAL_CACHE_DIR/p"
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [ zig ];
|
||||
|
||||
# The zig setup hook's check phase runs `zig build test`, with the same flags as the
|
||||
# build (ReleaseSafe): the engine, CLI, TUI, C ABI and JNI tests, and the C program
|
||||
# linked through tally.h. None of them touch the network or anything outside the
|
||||
# build directory, so every rebuild is a test run. (`jvm-test`, `coverage` and
|
||||
# `android` are separate steps, not run here.)
|
||||
doCheck = true;
|
||||
|
||||
meta = {
|
||||
description = "Calculator with exact arithmetic, unit conversion and a programmer mode, as a CLI and TUI";
|
||||
longDescription = ''
|
||||
One Zig engine behind a command line, a terminal UI and an Android app:
|
||||
exact rational arithmetic where the operations allow it, programmer mode
|
||||
(8 to 128-bit integers in every base), unit conversion as part of the
|
||||
expression language, and financial functions. This package is the CLI/TUI
|
||||
binary, plus the engine as a static library and as a shared library with
|
||||
its C header (tally.h).
|
||||
'';
|
||||
homepage = "https://git.lerch.org/lobo/tally";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "tally";
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue