Compare commits
3 commits
255f23928a
...
3c9ec72b88
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9ec72b88 | |||
| 95b2db5e02 | |||
| 59feb4b5e8 |
9 changed files with 267 additions and 34 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: |
|
||||
|
|
@ -181,13 +195,15 @@ jobs:
|
|||
# included, each logged to sigstore. One power cycle of the HSM for all of it.
|
||||
# The APK's signature is the app's identity on every device that installs it, for
|
||||
# good: the key is the HSM's, and its certificate is the one `make-cert` wrote to
|
||||
# the token (README, "Signing").
|
||||
# the token (android/signing-cert.pem; README, "Signing"). apk_cert_sha256 pins
|
||||
# it: an APK signed with any other certificate fails here instead of shipping.
|
||||
id: sign
|
||||
uses: https://git.lerch.org/lobo/action-hsm-sign@v3
|
||||
uses: https://git.lerch.org/lobo/action-hsm-sign@v3.1
|
||||
with:
|
||||
pin: ${{ secrets.HSM_USER_PIN }}
|
||||
apk: unsigned/app-release-unsigned.apk
|
||||
apk_output: dist/tally.apk
|
||||
apk_cert_sha256: '4b41d0b1b3a184342fdc348e67563c8ca6cbc3765fa7341dee8a4a8fbb5d4aad'
|
||||
files: dist/*
|
||||
public_key: 'https://emil.lerch.org/serverpublic.pem'
|
||||
uhub_control: 'true'
|
||||
|
|
|
|||
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/
|
||||
|
|
|
|||
90
README.md
90
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`.
|
||||
|
|
@ -225,28 +258,47 @@ included. The action power cycles the HSM on its smart USB hub for the duration,
|
|||
the private key never leaves the HSM.
|
||||
|
||||
An APK signature carries a certificate, and Java only offers a PKCS#11 key that has one
|
||||
on the token. **Once, before the first signed build**, make a self-signed certificate for
|
||||
the key and store it beside the key. On the HSM host, with the HSM powered, from a clone
|
||||
of action-hsm-sign:
|
||||
on the token; whatever certificate the token holds for the key is used. It is not
|
||||
Tally's own: the token holds one certificate per key, so every app signed with the key
|
||||
carries it, and its subject is generic. It is the identity of all of them on every
|
||||
device: an APK signed with a different one is refused as an update, and users uninstall
|
||||
and reinstall. So it is chosen once, kept in this repository as
|
||||
[android/signing-cert.pem](android/signing-cert.pem) (it is public), and pinned in the
|
||||
workflow by its SHA-256 (`apk_cert_sha256`), so a certificate replaced on the token fails
|
||||
the build instead of silently changing the app's identity.
|
||||
|
||||
It was made on the HSM host, with the HSM powered, from a clone of action-hsm-sign. The
|
||||
certificate is assembled in the container and signed on the token with the user PIN, so
|
||||
the key never leaves it, and written to the card with the Admin PIN (the card's own
|
||||
rule for certificates). `REPLACE_CERT=1` because the key already had a certificate (the
|
||||
card's 2023 "AUT certificate", which signed the very first build, 255f239):
|
||||
|
||||
```
|
||||
docker build -t hsm-signer signer
|
||||
read -rs PKCS11_PIN && export PKCS11_PIN
|
||||
read -rs ADMIN_PIN && export ADMIN_PIN
|
||||
docker run --rm -v /run/pcscd/pcscd.comm:/run/pcscd/pcscd.comm:ro \
|
||||
-e PKCS11_PIN hsm-signer make-cert '/CN=Tally/O=lerch.org'
|
||||
-e PKCS11_PIN -e ADMIN_PIN hsm-signer check-pin
|
||||
docker run --rm -v /run/pcscd/pcscd.comm:/run/pcscd/pcscd.comm:ro \
|
||||
-e PKCS11_PIN -e ADMIN_PIN -e REPLACE_CERT=1 hsm-signer make-cert '/CN=Emil Lerch/O=lerch.org'
|
||||
```
|
||||
|
||||
It prints the certificate; keep a copy. The key is `03` by default, the one the detached
|
||||
signatures use (`KEY_ID` changes it). The certificate is the app's identity on every
|
||||
device from then on: replacing it means users uninstall and reinstall. Its 30-year
|
||||
validity does not matter for APKs, whose certificate dates Android does not check.
|
||||
`check-pin` tries each PIN once first; a wrong PIN costs a try, and the card locks a PIN
|
||||
after three. `make-cert` prints the certificate and its `sha256 (apk_cert_sha256)`:
|
||||
`4b41d0b1b3a184342fdc348e67563c8ca6cbc3765fa7341dee8a4a8fbb5d4aad` (`CN=Emil Lerch,
|
||||
O=lerch.org`, valid to 2056). The key is `03` by default, the one the detached
|
||||
signatures use (`KEY_ID` changes it); a new certificate does not change the key, so the
|
||||
detached signatures and `serverpublic.pem` are unaffected. Its 30-year validity does not
|
||||
matter for APKs, whose certificate dates Android does not check, but is long enough for
|
||||
Google Play (validity past 2033-10-22).
|
||||
|
||||
`mise run apk-signer-test` builds the release APK and runs action-hsm-sign's
|
||||
`signer/test.sh` on it, at the version the workflow uses (or a local checkout named by
|
||||
`ACTION_HSM_SIGN`). That is the same signer image against SoftHSM: a token with a bare
|
||||
key, a detached signature, `make-cert`, APK signing, `apksigner verify`, and a check that
|
||||
the APK's signing key is the token's. What it cannot cover is the HSM itself and the
|
||||
host's pcscd, so the first run on the HSM runner is the real test of those.
|
||||
key, a detached signature, `make-cert` (and its refusal to replace a certificate), APK
|
||||
signing with the certificate pinned, `apksigner verify`, and a check that the APK's
|
||||
signing key is the token's. What it cannot cover is the HSM itself and the host's pcscd,
|
||||
so the first run on the HSM runner is the real test of those.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ manager outside mise is involved, and no NDK is needed at any point.
|
|||
|
||||
It is not mise alone, and the distinction matters when something breaks.
|
||||
|
||||
| Layer | Provided by | Pinned? | Lives in |
|
||||
|---|---|---|---|
|
||||
| Zig, JDK, Gradle, SDK command-line tools | mise, from `.mise.toml` | yes, by version | `~/.local/share/mise/installs/` |
|
||||
| Layer | Provided by | Pinned? | Lives in |
|
||||
|-------------------------------------------------------------------------------|----------------------------------------|--------------------|-------------------------------------------------|
|
||||
| Zig, JDK, Gradle, SDK command-line tools | mise, from `.mise.toml` | yes, by version | `~/.local/share/mise/installs/` |
|
||||
| SDK packages: platforms, build-tools, platform-tools, emulator, system images | `sdkmanager`, via the mise tasks below | partly - see below | inside the mise `android-sdk` install directory |
|
||||
| The AVD (the emulator's virtual device) | `avdmanager`, via a mise task | n/a | `.tmp/avd` in this repo, gitignored |
|
||||
| The AVD (the emulator's virtual device) | `avdmanager`, via a mise task | n/a | `.tmp/avd` in this repo, gitignored |
|
||||
|
||||
`mise install` is necessary but not sufficient. It gives you `sdkmanager`; it does not give
|
||||
you an SDK you can build against. mise has no idea the SDK packages exist, so they are not
|
||||
|
|
|
|||
28
android/signing-cert.pem
Normal file
28
android/signing-cert.pem
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE2zCCAsMCFHrZq0V4qQfkUv0emf3CkUnuZztZMA0GCSqGSIb3DQEBCwUAMCkx
|
||||
EzARBgNVBAMMCkVtaWwgTGVyY2gxEjAQBgNVBAoMCWxlcmNoLm9yZzAgFw0yNjEw
|
||||
MDQxOTE1MjhaGA8yMDU2MDkyNjE5MTUyOFowKTETMBEGA1UEAwwKRW1pbCBMZXJj
|
||||
aDESMBAGA1UECgwJbGVyY2gub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||
CgKCAgEA0rlFG2I641ARuKWcyFVrL81pwP8O0KGSd7uYkLpf4F3ut6VUZOsaGzCz
|
||||
fACXu6x57DFH1DMZkaf71vg+btRUw/b4eGmTXiAfq5D4CM5Bk1kgxOqat5JL+YF1
|
||||
EunxYV3EqAhtaL9FLrafNL+guX/slQ7ol8RjlWddJqkS+tpoHu6XrmMgChosDfHY
|
||||
cZeGI/B8wkiD4sLVy/VwnlyGPK1Ww4o/EnIwiRH6m3bP/bxfKslXg+Z6mUch530F
|
||||
4O75fhUzBzzrvbluKEH+9uWviVHHdNM3NSmNX5xYqaVr9OdiMHO18lynNZK8DBW4
|
||||
vsrpGO5GX0F5spafa5bBzTPdtJw52v5Mw5pTtKtocwn9ML26lq4KEIBW5aANKvVc
|
||||
hcyFOELJFMzvuE4pxvVzM0SlpG7p7hU7cz7kE83QnZzGup1d9vxpCbBtDIsaB+B5
|
||||
/43YHLc+mTCzTdHNRy91RwYiZKOL3zEsMyWHJXwoO0ELaET0gmrRymbEETdCoWNP
|
||||
qVKxea+zuPpeGGamIPoATgxnVAgfYlnFhSiMDuwERiJ1HIUqlcVaFAETLw/jdvih
|
||||
TgS7FJ7xfZy8+nT+sQt7P2s77A8ogX+NsRyMNic8/pCVOkfWYGCf5n5aOu9EGXiR
|
||||
LHn1X28ZJIfuxJ16JlclkIxnBHp4okJ1mYWHuN2SRFvXu6tCOrkCAwEAATANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAOwGr7hXIv9+2bx4ngGYAq9FRJeAesaG4inn8L9eSQq7S
|
||||
peJbtyL4BO5/wAZt6gD/qTVWRVFiaLDvs2oyoh+mv74TsAyYHUFKHVptMOaG6OBu
|
||||
3rGdDbQFKpi62/v+aC9zakgNx4oH+0Ev/WbWf3u+VXlZGnF51eBZh3E7B4DW5TkG
|
||||
sMEaXP1wOMdx2cZWg0e84qaHNi4eSUIgB0QSwbXlw2AZEcTVcqAws7sLvhUgphxG
|
||||
aH9n2+a0CCfPZRlyJFJlQ+DXohOOjVENG++PbxPeMmRi0EvmVAhbnrMGhVi6ayxH
|
||||
KmoRwOfoe+Ti6kStPGaYXLIVVA2BKnBCYL3pi/ZWCKSsueJuKLUYzddsZe9exirr
|
||||
y8/Jpct3KvlYaA4dMIaUlYRAGn609hWt7sAqNEnkSHsjqtycPbAi74+AulUpdcWO
|
||||
GL+77YiCx1I4+rXBFLV43kz+Fw0A9btdrv4/gC+siAzDYv7Y21+rM1IxAU+xnHnm
|
||||
btvc04C6CDG2UpPSSdJRm9yugtXk7AH4AtboKUvpCWbwHK0Kd9kAUbJ0Mbbuxf13
|
||||
7YppxMGGSSJjG+XV917tSWG+gNNz2V9KLxsGnCJIRRHaGsBfn8p+TY05PvRFcj7Z
|
||||
vxxEGvrZMBaQz2noQjKsoAsWjr199mfFKm4/rxHpHe3vI3kzfCHaU0MVVCUGrtk=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -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