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
1 change: 1 addition & 0 deletions .github/badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.zig-cache/
zig-out/
coverage/

# Override global gitignore — vendored SQLite is intentional
!vendor/
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
![macOS only](https://img.shields.io/badge/platform-macOS-blue)
![Zig 0.15.x](https://img.shields.io/badge/zig-0.15.x-orange)
![License](https://img.shields.io/badge/license-MIT-green)
![Coverage](.github/badges/coverage.svg)
[![Built with Devbox](https://www.jetify.com/img/devbox/shield_galaxy.svg)](https://www.jetify.com/devbox/docs/contributor-quickstart/)

malt is a macOS-only package manager written in Zig that consumes Homebrew's existing formula, bottle, cask, and tap ecosystem. It ships as a single binary (`malt`, ~3 MB) with sub-millisecond cold start. malt downloads pre-built bottles from the Homebrew infrastructure — it is a fast client for Homebrew's package registry, not a fork. Requires macOS 11 (Big Sur) or later on Apple Silicon or Intel.
Expand Down
13 changes: 13 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub fn build(b: *std.Build) void {
};

const test_step = b.step("test", "Run all unit tests");
const test_bin_step = b.step("test-bin", "Install test binaries for coverage (kcov)");

// Shared library module — single root that re-exports all source modules.
// This avoids "file exists in multiple modules" errors from Zig's module system.
Expand All @@ -89,7 +90,12 @@ pub fn build(b: *std.Build) void {
malt_lib.addOptions("version_string", version_options);

inline for (test_modules) |test_file| {
// e.g. "tests/formula_test.zig" → "formula_test" (so each test binary
// has a unique install name, which `test-bin` / kcov need).
const test_name = comptime std.fs.path.stem(std.fs.path.basename(test_file));

const t = b.addTest(.{
.name = test_name,
.root_module = b.createModule(.{
.root_source_file = b.path(test_file),
.target = target,
Expand All @@ -105,6 +111,13 @@ pub fn build(b: *std.Build) void {

const run_t = b.addRunArtifact(t);
test_step.dependOn(&run_t.step);

// Only installed when the user asks for `zig build test-bin`
// (used by the coverage recipe and the coverage CI job).
const install_t = b.addInstallArtifact(t, .{
.dest_dir = .{ .override = .{ .custom = "test-bin" } },
});
test_bin_step.dependOn(&install_t.step);
}

// --- Universal binary step (macOS only) ---
Expand Down
69 changes: 69 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,74 @@ install:
test:
zig build test

# Run tests under kcov, print line-coverage percentage, and refresh
# the README badge SVG at .github/badges/coverage.svg.
# HTML report lands at coverage/merged/kcov-merged/index.html.
# Requires kcov (brew install kcov). Internet is needed for the badge fetch.
coverage:
#!/usr/bin/env bash
set -euo pipefail
if ! command -v kcov >/dev/null 2>&1; then
echo "error: kcov not found. Install with: brew install kcov" >&2
exit 1
fi
rm -rf coverage
mkdir -p coverage
zig build test-bin
# Only report coverage for files under the project's src/ directory.
# --include-path takes an absolute path and is more reliable than --include-pattern.
src_dir="$(pwd)/src"
# Run kcov once per test binary into a shared outdir
for bin in zig-out/test-bin/*; do
# Skip .dSYM debug bundles and any non-regular files
[ -f "$bin" ] || continue
[ -x "$bin" ] || continue
echo "→ kcov: $(basename "$bin")"
kcov --include-path="$src_dir" coverage "$bin" >/dev/null
done
# kcov 43 on macOS doesn't reliably auto-merge, so do it explicitly.
# The per-binary reports are in hash-suffixed dirs (e.g. cellar_test.a934ecd0).
shopt -s nullglob
per_bin_dirs=(coverage/*_test.*)
shopt -u nullglob
if [ ${#per_bin_dirs[@]} -eq 0 ]; then
echo "error: kcov produced no per-binary reports" >&2
exit 1
fi
kcov --merge coverage/merged "${per_bin_dirs[@]}" >/dev/null
report="coverage/merged/kcov-merged/coverage.json"
if [ ! -f "$report" ]; then
echo "error: merged report not found at $report" >&2
exit 1
fi
if command -v jq >/dev/null 2>&1; then
percent=$(jq -r '.percent_covered' "$report")
else
percent=$(grep -oE '"percent_covered"[^,}]*' "$report" | grep -oE '[0-9]+(\.[0-9]+)?' | head -1)
fi
# Pick a shields.io color based on the integer part of the percentage.
pct_int=${percent%.*}
if [ "$pct_int" -ge 90 ]; then color="brightgreen"
elif [ "$pct_int" -ge 80 ]; then color="green"
elif [ "$pct_int" -ge 70 ]; then color="yellowgreen"
elif [ "$pct_int" -ge 60 ]; then color="yellow"
elif [ "$pct_int" -ge 50 ]; then color="orange"
else color="red"
fi
# Fetch the static badge SVG from shields.io and commit it under .github/badges/.
# This keeps the README badge in-repo so it updates with normal commits — no CI needed.
mkdir -p .github/badges
badge_url="https://img.shields.io/badge/coverage-${percent}%25-${color}"
if curl -sSLf "$badge_url" -o .github/badges/coverage.svg; then
badge_msg=".github/badges/coverage.svg (refreshed — remember to commit it)"
else
badge_msg="warning: could not fetch badge from shields.io (offline?) — badge not refreshed"
fi
echo ""
echo "Coverage: ${percent}%"
echo "Report: coverage/merged/kcov-merged/index.html"
echo "Badge: ${badge_msg}"

# Check formatting
fmt-check:
zig fmt --check src/ tests/
Expand All @@ -39,6 +107,7 @@ lint:
# Pre-commit hook: auto-format staged .zig files in place and re-stage them
# so the formatted version is what actually lands in the commit.
# Note: if you `git add -p` partial hunks of a file, this will pick up the

# unstaged hunks too — a known limitation of format-on-commit hooks.
pre-commit:
#!/usr/bin/env bash
Expand Down
Loading