Pantry Integration Guide
On this page 28
Overview
Home uses Pantry for dependency management. This document explains how pantry integration works and how to properly use it in your Home projects.
Architecture
Home Project
├── pantry.json # Dependency declarations
├── pantry-lock.json # Lockfile with resolved paths (or .freezer for backwards compat)
├── pantry_modules/ # Local project dependencies
│ └── {package}/
│ └── {version}/
└── packages/
└── pantry/ # Pantry integration module
└── src/
└── pantry.zig
Installed packages live in:
- Local dependencies:
./pantry_modules/{package-name}/{version}/ - Global dependencies:
~/.local/share/pantry/global/packages/{package-name}/{version}/
Files
pantry.json
Declares your project's dependencies:
{
"name": "home",
"version": "0.1.0",
"dependencies": {
"bun": "^1.3.0",
"ziglang.org": "0.17.0-dev.263+0add2dfc4",
"craft": "^0.1.0"
}
}
.freezer
Lockfile with resolved package information:
{
"version": "1",
"lockfileVersion": 1,
"generatedAt": "2025-10-31T00:00:00.000Z",
"packages": {
"craft@0.1.0": {
"name": "craft",
"version": "0.1.0",
"resolved": "/Users/username/Code/craft",
"integrity": "",
"source": "path",
"installedAt": "2025-10-31T00:00:00.000Z"
},
"ziglang.org@0.17.0-dev.263+0add2dfc4": {
"name": "ziglang.org",
"version": "0.17.0-dev.263+0add2dfc4",
"resolved": "https://ziglang.org/builds/",
"integrity": "sha512-...",
"source": "registry",
"installedAt": "2025-10-26T00:00:00.000Z"
}
}
}
Usage in Zig Code
Basic Path Resolution
const std = @import("std");
const pantry = @import("pantry");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Initialize path resolver
const project_root = try pantry.findProjectRoot(allocator);
defer allocator.free(project_root);
var resolver = try pantry.PathResolver.init(allocator, project_root);
defer resolver.deinit();
// Resolve package path
const craft_path = try resolver.resolvePath("craft");
defer allocator.free(craft_path);
std.debug.print("Craft installed at: {s}\n", .{craft_path});
// Resolve with subpath
const craft_zig = try resolver.resolveSubpath("craft", "packages/zig");
defer allocator.free(craft_zig);
std.debug.print("Craft Zig bindings: {s}\n", .{craft_zig});
}
Dynamic Path Resolution (craft.zig example)
Instead of hardcoding paths:
// ❌ BAD: Hardcoded path
const CRAFT_PATH = "/Users/chrisbreuer/Code/craft/packages/zig";
Use dynamic resolution:
// ✅ GOOD: Dynamic resolution
fn resolveCraftPath(allocator: std.mem.Allocator) ![]const u8 {
const home = std.posix.getenv("HOME") orelse return error.HomeNotSet;
// Try local pantry_modules first
var buf: [std.fs.max_path_bytes]u8 = undefined;
const cwd = std.fs.cwd().realpath(".", &buf) catch {
return tryGlobalOrFallback(allocator, home);
};
const local_craft_base = try std.fs.path.join(allocator, &.{ cwd, "pantry_modules", "craft" });
defer allocator.free(local_craft_base);
if (std.fs.openDirAbsolute(local_craft_base, .{ .iterate = true })) |_dir| {
defer dir.close();
var iter = dir.iterate();
if (try iter.next()) |entry| {
if (entry.kind == .directory) {
return try std.fs.path.join(allocator, &.{
local_craft_base,
entry.name,
"packages",
"zig",
});
}
}
} else |_| {}
return tryGlobalOrFallback(allocator, home);
}
fn tryGlobalOrFallback(allocator: std.mem.Allocator, home: []const u8) ![]const u8 {
// Check pantry global cache
const global_craft_base = try std.fs.path.join(allocator, &.{
home,
".local",
"share",
"pantry",
"global",
"packages",
"craft",
});
defer allocator.free(global_craft_base);
if (std.fs.openDirAbsolute(global_craft_base, .{ .iterate = true })) |_dir| {
defer dir.close();
var iter = dir.iterate();
if (try iter.next()) |entry| {
if (entry.kind == .directory) {
return try std.fs.path.join(allocator, &.{
global_craft_base,
entry.name,
"packages",
"zig",
});
}
}
} else |_| {}
return error.PantryPackageNotFound;
}
// Use it:
pub fn init(allocator: std.mem.Allocator) !void {
const craft_path = try resolveCraftPath(allocator);
defer allocator.free(craft_path);
// Now use craft_path...
}
CLI Usage
Installing Dependencies
# Install from pantry.json
pantry install
# Install specific package
pantry install craft
pantry install ziglang.org
# Install from git
pantry install github.com/user/repo
# Install local path
pantry install --path ../local-package
Managing Packages
# List installed packages
pantry list
# Show package info
pantry info craft
# Update packages
pantry update
# Remove package
pantry remove craft
# Clean cache
pantry clean
Lockfile Management
# Generate lockfile
pantry lock
# Update lockfile
pantry lock --update
# Verify integrity
pantry verify
Path Resolution Strategy
Pantry uses the following resolution strategy:
- Check lockfile for package entry (tries
pantry-lock.jsonfirst, then.freezer) - Resolve based on source type:
path: Use the resolved path directlyregistryorgit:- First check:
./pantry_modules/{name}/{version}/(local install) - Then check:
~/.local/share/pantry/global/packages/{name}/{version}/(global install)
- First check:
- Fail fast if the Pantry package is not installed; run
pantry installrather than falling back to system paths.
Build.zig Integration
const std = @import("std");
pub fn build(b: _std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Resolve pantry packages
const pantry_path = resolvePantryPackage(b.allocator, "craft") catch null;
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Add pantry-resolved packages
if (pantry_path) |path| {
exe.addIncludePath(.{ .cwd_relative = path });
}
b.installArtifact(exe);
}
fn resolvePantryPackage(allocator: std.mem.Allocator, name: []const u8) ![]const u8 {
// Use pantry resolver...
const home = std.posix.getenv("HOME") orelse return error.HomeNotSet;
return try std.fs.path.join(allocator, &.{
home,
".local",
"share",
"pantry",
"global",
"packages",
name,
});
}
Best Practices
1. Never Hardcode Paths
// ❌ BAD
const CRAFT_PATH = "/Users/chrisbreuer/Code/craft";
// ✅ GOOD
fn getCraftPath(allocator: std.mem.Allocator) ![]const u8 {
// Dynamic resolution...
}
2. Always Use Allocator for Paths
// ✅ GOOD
const path = try resolver.resolvePath("craft");
defer allocator.free(path); // Always free!
3. Handle Missing Packages Gracefully
const craft_path = resolver.resolvePath("craft") catch |err| {
std.log.warn("Craft not found: {}", .{err});
return error.CraftRequired;
};
defer allocator.free(craft_path);
4. Cache Resolved Paths
pub const App = struct {
craft_path: []const u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !App {
const craft_path = try resolveCraftPath(allocator);
return .{
.craft_path = craft_path,
.allocator = allocator,
};
}
pub fn deinit(self: _App) void {
self.allocator.free(self.craft_path);
}
};
Environment Variables
Pantry respects these environment variables:
HOME: User home directoryPANTRY_HOME: Override pantry cache locationPANTRY_REGISTRY: Custom package registry URL{PACKAGE}_HOME: Package-specific override (e.g.,CRAFT_HOME)
Troubleshooting
Package Not Found
# Verify package is in lockfile
cat pantry-lock.json | grep "craft"
# Or check .freezer for backwards compatibility
cat .freezer | grep "craft"
# Check if pantry cache exists (local first)
ls ./pantry_modules/
# Then check global
ls ~/.local/share/pantry/global/packages/
# Reinstall
pantry install craft
Wrong Version
# Check locked version
pantry list
# Update to latest
pantry update craft
# Lock to specific version
pantry install craft@0.2.0
Path Resolution Fails
// Add debug logging
const craft_path = resolver.resolvePath("craft") catch |err| {
std.log.err("Failed to resolve craft: {}", .{err});
// Try manual resolution
const home = std.posix.getenv("HOME") orelse return error.HomeNotSet;
return try std.fs.path.join(allocator, &.{
home, "Code", "craft"
});
};
Migration Guide
From Hardcoded Paths
-
Identify hardcoded paths:
grep -r "const._PATH._=" packages/ -
Replace with resolver:
// Before const CRAFT_PATH = "/Users/..."; // After fn resolveCraftPath(allocator: std.mem.Allocator) ![]const u8 { // Use pantry resolver } -
Update usage:
// Before const path = CRAFT_PATH; // After const path = try resolveCraftPath(allocator); defer allocator.free(path); -
Add to pantry.json:
{ "dependencies": { "craft": "^0.1.0" } } -
Install:
pantry install
Future Enhancements
- Automatic pantry.json generation
- Build-time package resolution
- Workspace support (monorepo)
- Package verification and signatures
- Offline mode
- Mirror support
- Custom registries