Skip to content
Open
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
79 changes: 72 additions & 7 deletions src/Package.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,75 @@ const Package = @This();
/// Rules:
/// - no element in array or hashmap are allowed to hold pointers (ArrayList and HashMaps are also pointers).
pub const State = struct {
/// Maps each unique `Package.ID` to its entry in `packages`.
package_table: std.AutoArrayHashMapUnmanaged(Id, Package.Idx) = .empty,
/// Owns the interned strings referenced by packages and dependencies.
string_state: string.State = .empty,
/// Append-only package storage; duplicate packages may share an ID.
packages: std.MultiArrayList(Package) = .empty,
/// Contiguous storage addressed by each package's dependency ranges.
dependencies: std.ArrayList(Dependency) = .empty,

pub const empty = State{};

// TODO: make wrapper api to minimize fault use.
pub const DependencyKind = enum { compile, runtime };

pub const Entry = struct {
package: Package,
};

pub const Iterator = struct {
packages_slice: std.MultiArrayList(Package).Slice,
table_iterator: std.AutoArrayHashMapUnmanaged(Id, Package.Idx).Iterator,

pub fn next(self: *Iterator) ?Package {
const entry = self.table_iterator.next() orelse return null;
assert(entry.key_ptr.* != .none);
const index = @intFromEnum(entry.value_ptr.*);
assert(index < self.packages_slice.len);
const package = self.packages_slice.get(index);
assert(package.id == entry.key_ptr.*);
return package;
}
};

pub fn get(self: *const State, id: Id) ?Package {
assert(id != .none);
const index = self.package_table.get(id) orelse return null;
const package_index = @intFromEnum(index);
assert(package_index < self.packages.len);
const package = self.packages.get(package_index);
assert(package.id == id);
return package;
}

pub fn getDependencies(self: *const State, package: Package, kind: DependencyKind) []const Dependency {
const deps = switch (kind) {
.compile => package.compile_deps,
.runtime => package.runtime_deps,
};
const start: usize = deps.start;
const dependency_count: usize = deps.count;
assert(package.id != .none);
assert(start <= self.dependencies.items.len);
assert(dependency_count <= self.dependencies.items.len - start);
return self.dependencies.items[start..][0..dependency_count];
}

pub fn count(self: *const State) usize {
assert(self.package_table.count() <= self.packages.len);
return self.package_table.count();
}

/// Iterate over Packages.
/// It is not safe to mofify state while iterating
pub fn iterator(self: *const State) Iterator {
assert(self.package_table.count() <= self.packages.len);
return .{
.packages_slice = self.packages.slice(),
.table_iterator = self.package_table.iterator(),
};
}

pub fn deinit(self: *State, gpa: Allocator) void {
self.package_table.deinit(gpa);
Expand All @@ -50,11 +111,15 @@ pub const Dependency = struct {
};
/// Unique hash(Id) of a Package by hashing OS, cpu Arch, manifests and dependecies manifests.
pub const Id = String;
/// idx into State.packages
/// Index into State.packages
pub const Idx = enum(u32) { _ };

/// Unique package id generated from hashing build input
id: Id = .none,
/// name of Package
name: String,
version: std.SemanticVersion,
/// Lua stack index of pkg from manifest
lua_idx: i32,
desc: String,
homepage: String,
Expand Down Expand Up @@ -117,7 +182,7 @@ pub fn init(
lua.pop(1);

if (!std.mem.eql(u8, pkg_name, name.slice(&state.string_state))) {
log.err("Name differs", .{});
log.err("Package name differs from expected name '{s}', got {s}", .{ pkg_name, name.slice(&state.string_state) });
return error.WrontPackageName;
}

Expand Down Expand Up @@ -220,6 +285,7 @@ pub fn init(
}
pop_count += 1; // deps
lua.pop(pop_count);
lua.pop(1); // package

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'lua_idx|const pkg = lua\.getTop\(\)|lua\.pop\(1\).*package|getField\(pkg\.lua_idx' src

Repository: D-Berg/packa

Length of output: 3250


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- src/Package.zig: Package.init and surrounding stack operations ---'
sed -n '100,215p' src/Package.zig
sed -n '250,310p' src/Package.zig

printf '%s\n' '--- src/actions/build_package.zig: package access and call context ---'
sed -n '1,130p' src/actions/build_package.zig
rg -n -C 6 'buildPackage|Package\.init|lua_idx|lua\.setTop|lua\.pop|lua\.push|pcall' src/actions src

printf '%s\n' '--- Lua API wrapper declarations and index semantics ---'
rg -n -C 5 'pub .*getField|fn getField|lua_getfield|pub .*pop|fn pop|lua_pop|absIndex|toAbsolute|registry|LUA_REGISTRYINDEX' src

Repository: D-Berg/packa

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Package.collect and state access ---'
sed -n '304,390p' src/Package.zig
rg -n -C 5 'pub fn get\(|fn get\(|getDependencies|Package\.collect|collect\(' src/Package.zig src/actions/build_package.zig

printf '%s\n' '--- Lua wrapper files and relevant APIs ---'
git ls-files | rg '(^|/)(lua|zlua|.*Lua.*)|build.zig.zon|build.zig$' | head -80
rg -n -C 4 'getField|pub fn pop|fn pop|lua_getfield|lua_settop|setTop|REGISTRYINDEX' . -g '*.zig' -g '*.zon' -g '!src/actions/build_package.zig' -g '!src/Package.zig' | head -240

printf '%s\n' '--- Exact stack-affecting calls in Package.init ---'
sed -n '170,305p' src/Package.zig | rg -n 'getTop|getField|rawGetI|pop|push|pcall|loadBuffer|setField'

Repository: D-Berg/packa

Length of output: 10817


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Stack:
    values: list[str]

    def top(self) -> int:
        return len(self.values)

    def push(self, value: str) -> None:
        self.values.append(value)

    def pop(self, count: int = 1) -> None:
        if count < 0 or count > len(self.values):
            raise AssertionError(f"invalid pop({count}) for {self.values}")
        del self.values[-count:] if count else []

    def get_field(self, index: int, field: str) -> str:
        if index < 1 or index > len(self.values):
            return "nil"
        self.push(f"{self.values[index - 1]}.{field}")
        return self.values[-1]

    def raw_get_i(self, index: int, i: int) -> str:
        if index < 1 or index > len(self.values):
            return "nil"
        self.push(f"{self.values[index - 1]}[{i}]")
        return self.values[-1]

def package_init(initial_stack: list[str], dep_count: int = 0):
    s = Stack(initial_stack[:])
    s.push("package")                 # pcall(0, 1, 0)
    pkg = s.top()                     # const pkg = lua.getTop()

    for field in ("name", "version", "desc", "homepage", "license", "url", "hash"):
        assert s.get_field(pkg, field) != "nil"
        s.pop()

    assert s.get_field(pkg, "build") != "nil"
    s.pop()

    pop_count = 0
    s.get_field(pkg, "deps")           # deps
    pop_count += 1

    # Model the compile/runtime table and dependency element pushes.
    if dep_count:
        for section in ("compile", "runtime"):
            s.get_field(s.top() - 0, section)
            for i in range(1, dep_count + 1):
                s.raw_get_i(s.top() - 0, i)
                s.pop()
                pop_count += 1
            pop_count += 1             # section table
    s.pop(pop_count + 1)               # deps and its children
    before_final_pop = s.values[:]
    s.pop(1)                           # lua.pop(1); // package
    return pkg, before_final_pop, s.values

for initial in ([], ["setup residue"]):
    pkg, before, after = package_init(initial, dep_count=2)
    print({
        "initial_stack": initial,
        "stored_lua_idx": pkg,
        "stack_before_package_pop": before,
        "stack_after_package_pop": after,
        "stored_index_is_package": pkg <= len(after) and after[pkg - 1] == "package",
    })
PY

Repository: D-Berg/packa

Length of output: 282


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Stack:
    values: list[str]

    def top(self) -> int:
        return len(self.values)

    def push(self, value: str) -> None:
        self.values.append(value)

    def pop(self, count: int = 1) -> None:
        if count < 0 or count > len(self.values):
            raise AssertionError(f"invalid pop({count}) for {self.values}")
        if count:
            del self.values[-count:]

    def get_field(self, index: int, field: str) -> None:
        if index < 1 or index > len(self.values):
            self.push("nil")
        else:
            self.push(f"{self.values[index - 1]}.{field}")

    def raw_get_i(self, index: int, i: int) -> None:
        if index < 1 or index > len(self.values):
            self.push("nil")
        else:
            self.push(f"{self.values[index - 1]}[{i}]")

def package_init(initial_stack: list[str], dep_count: int = 2):
    s = Stack(initial_stack[:])
    s.push("package")
    pkg = s.top()

    for field in ("name", "version", "desc", "homepage", "license", "url", "hash"):
        s.get_field(pkg, field)
        s.pop()

    s.get_field(pkg, "build")
    s.pop()

    pop_count = 0
    s.get_field(pkg, "deps")
    lua_deps = s.top()
    pop_count += 1

    # The source leaves compile/runtime tables and rawGetI results on the stack.
    for section in ("compile", "runtime"):
        s.get_field(lua_deps, section)
        section_index = s.top()
        for i in range(1, dep_count + 1):
            s.raw_get_i(section_index, i)
            pop_count += 1
        pop_count += 1

    pop_count += 1
    before_final_pop = s.values[:]
    s.pop(pop_count)
    assert s.values == initial_stack + ["package"], (s.values, initial_stack)
    s.pop(1)
    return pkg, before_final_pop, s.values

for initial in ([], ["setup residue"]):
    pkg, before, after = package_init(initial)
    print({
        "initial_stack": initial,
        "stored_lua_idx": pkg,
        "stack_before_package_pop": before,
        "stack_after_package_pop": after,
        "stored_index_is_package": pkg <= len(after) and after[pkg - 1] == "package",
    })
PY

Repository: D-Berg/packa

Length of output: 292


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Stack:
    values: list[str]

    def top(self) -> int:
        return len(self.values)

    def push(self, value: str) -> None:
        self.values.append(value)

    def pop(self, count: int = 1) -> None:
        if count < 0 or count > len(self.values):
            raise AssertionError(f"invalid pop({count}) for {self.values}")
        if count:
            del self.values[-count:]

    def get_field(self, index: int, field: str) -> None:
        self.push(
            f"{self.values[index - 1]}.{field}"
            if 1 <= index <= len(self.values) else "nil"
        )

    def raw_get_i(self, index: int, i: int) -> None:
        self.push(
            f"{self.values[index - 1]}[{i}]"
            if 1 <= index <= len(self.values) else "nil"
        )

def package_init(initial_stack: list[str], dep_count: int = 2):
    s = Stack(initial_stack[:])
    s.push("package")
    pkg = s.top()

    for field in ("name", "version", "desc", "homepage", "license", "url", "hash"):
        s.get_field(pkg, field)
        s.pop()

    s.get_field(pkg, "build")
    s.pop()

    pop_count = 0
    s.get_field(pkg, "deps")
    lua_deps = s.top()

    for section in ("compile", "runtime"):
        s.get_field(lua_deps, section)
        section_index = s.top()
        for i in range(1, dep_count + 1):
            s.raw_get_i(section_index, i)
            pop_count += 1
        pop_count += 1

    pop_count += 1                    # deps
    before_cleanup = s.values[:]
    s.pop(pop_count)
    assert s.values == initial_stack + ["package"], (s.values, initial_stack)
    s.pop(1)                           # lua.pop(1); // package
    return pkg, before_cleanup, s.values

for initial in ([], ["setup residue"]):
    pkg, before, after = package_init(initial)
    print({
        "initial_stack": initial,
        "stored_lua_idx": pkg,
        "stack_before_cleanup": before,
        "stack_after_package_pop": after,
        "stored_index_is_package": pkg <= len(after) and after[pkg - 1] == "package",
    })
PY

Repository: D-Berg/packa

Length of output: 831


Keep the package table alive until build completes.

Package.init stores its stack index in lua_idx, then lua.pop(1) removes that table. build_package.zig later calls lua.getField(pkg.lua_idx, "build"), so the index no longer identifies the package table. Remove this pop or store the table in the Lua registry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Package.zig` at line 288, Remove the lua.pop(1) call in Package.init so
the package table remains available at pkg.lua_idx when build_package.zig
invokes lua.getField for "build"; do not alter the existing stack-index usage.


return .{
.name = name,
Expand Down Expand Up @@ -250,11 +316,9 @@ pub fn collect(
var blake3: std.crypto.hash.Blake3 = .init(.{ .key = null });

try state.packages.ensureUnusedCapacity(gpa, 1);
const package = try Package.init(io, gpa, state, packa_dir, "core", name, lua, &blake3);
const pkg_idx = state.packages.addOneAssumeCapacity();
state.packages.set(
pkg_idx,
try .init(io, gpa, state, packa_dir, "core", name, lua, &blake3),
);
state.packages.set(pkg_idx, package);
state.packages.items(.install)[pkg_idx] = install;

const runtime_deps: Deps = state.packages.items(.runtime_deps)[pkg_idx];
Expand Down Expand Up @@ -289,5 +353,6 @@ pub fn collect(
assert(names[@intFromEnum(gop.value_ptr.*)] == names[pkg_idx]);
}
gop.value_ptr.* = @enumFromInt(pkg_idx);
state.packages.items(.id)[pkg_idx] = key_string;
return key_string;
}
19 changes: 7 additions & 12 deletions src/actions/build_package.zig
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,14 @@ pub fn build(io: Io, gpa: Allocator, arena: Allocator, env: *std.process.Environ
const pkg_id = try Package.collect(io, arena, &state, packa_dir, args.package_name, &lua, true);
// TODO: fetch and install deps

const pkg_idx = state.package_table.get(pkg_id) orelse return error.FailedToCollectPackage;
const pkg = state.packages.get(@intFromEnum(pkg_idx));
const pkg = state.get(pkg_id) orelse return error.FailedToCollectPackage;

const pkg_key = pkg_id.slice(&state.string_state);
const pkg_name = pkg.name.slice(&state.string_state);

const compile_deps = state.dependencies.items[pkg.compile_deps.start..][0..pkg.compile_deps.count];
const compile_deps = state.getDependencies(pkg, .compile);
for (compile_deps) |dependency| {
const dep_idx = state.package_table.get(dependency.pkg_id) orelse return error.FailedToCollectPackage;
const dep = state.packages.get(@intFromEnum(dep_idx));
const dep = state.get(dependency.pkg_id) orelse return error.FailedToCollectPackage;
const dep_name = dep.name.slice(&state.string_state);
const dep_key = dependency.pkg_id.slice(&state.string_state);
const store_path = try bufPrint(&print_buf, "/opt/packa/store/{s}-{f}-{s}", .{
Expand Down Expand Up @@ -521,7 +519,7 @@ fn luaDep(state: ?*zlua.LuaState) callconv(.c) c_int {

const arena = arena_impl.allocator();

const pkg_idx = ctx.pkg_state.package_table.get(ctx.pkg_id) orelse {
const pkg = ctx.pkg_state.get(ctx.pkg_id) orelse {
lua.pushNil();
_ = lua.pushLString(std.fmt.allocPrint(arena, "could not find idx for package id {s}, this should not happen", .{
ctx.pkg_id.slice(string_state),
Expand All @@ -531,9 +529,8 @@ fn luaDep(state: ?*zlua.LuaState) callconv(.c) c_int {
};

const dep_id = get_dep_id: {
const pkg_comp_deps: Package.Deps = ctx.pkg_state.packages.items(.compile_deps)[@intFromEnum(pkg_idx)];
for (0..pkg_comp_deps.count) |i| {
const pkg_dep: Package.Dependency = ctx.pkg_state.dependencies.items[pkg_comp_deps.start..][i];
const pkg_compile_deps = ctx.pkg_state.getDependencies(pkg, .compile);
for (pkg_compile_deps) |pkg_dep| {
if (std.mem.eql(u8, dep_name, pkg_dep.name.slice(string_state))) {
break :get_dep_id pkg_dep.pkg_id;
}
Expand All @@ -546,15 +543,13 @@ fn luaDep(state: ?*zlua.LuaState) callconv(.c) c_int {
return 2;
};

const dep_idx = ctx.pkg_state.package_table.get(dep_id) orelse {
const dep = ctx.pkg_state.get(dep_id) orelse {
lua.pushNil();
_ = lua.pushLString(std.fmt.allocPrint(arena, "failed to get package idx for dep id '{s}', this should'nt happen", .{
dep_id.slice(string_state),
}) catch @panic("OOM"));
return 2;
};
const dep = ctx.pkg_state.packages.get(@intFromEnum(dep_idx));

const store_path = std.fmt.allocPrint(arena, "/opt/packa/store/{s}-{f}-{s}", .{
dep.name.slice(string_state), dep.version, dep_id.slice(string_state)[0..32],
}) catch @panic("OOM");
Expand Down
10 changes: 5 additions & 5 deletions src/actions/info.zig
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn printInfo(
pkg_id: Package.Id,
state: *const Package.State,
) !void {
const pkg = state.packages.get(@intFromEnum(state.package_table.get(pkg_id).?));
const pkg = state.get(pkg_id) orelse return error.MissingPackageIdx;

try t.setColor(.bold);
try t.writer.print("{s}-{f}\n", .{ pkg.name.slice(&state.string_state), pkg.version });
Expand Down Expand Up @@ -130,17 +130,17 @@ fn printDeps(
pipes: u64,
path_buf: []u8,
) !void {
const pkg = state.packages.get(@intFromEnum(state.package_table.get(pkg_id).?));
const comp_deps = state.dependencies.items[pkg.compile_deps.start..][0..pkg.compile_deps.count];
const run_deps = state.dependencies.items[pkg.runtime_deps.start..][0..pkg.runtime_deps.count];
const pkg = state.get(pkg_id) orelse return error.MissingPackageIdx;
const comp_deps = state.getDependencies(pkg, .compile);
const run_deps = state.getDependencies(pkg, .runtime);
const total = comp_deps.len + run_deps.len;

for (0..total) |i| {
const is_comp = i < comp_deps.len;
const dep = if (is_comp) comp_deps[i] else run_deps[i - comp_deps.len];
const is_last = (i == total - 1);

const dep_pkg = state.packages.get(@intFromEnum(state.package_table.get(dep.pkg_id).?));
const dep_pkg = state.get(dep.pkg_id) orelse return error.MissingPackageIdx;

for (0..level) |l| {
const pipe = if ((pipes >> @intCast(l)) & 1 == 1) "│ " else " ";
Expand Down
27 changes: 11 additions & 16 deletions src/actions/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ pub fn install(
try fetchPackages(io, gpa, cache_dir, &state, &pub_key, progress);

for (package_ids.items) |id| {
const idx = state.package_table.get(id) orelse return error.MissingPackageIdx;
const pkg = state.packages.get(@intFromEnum(idx));
const pkg = state.get(id) orelse return error.MissingPackageIdx;
// TODO: install runtime deps first recursively
// for (pkg.runtime_deps.items) |dep_id| {}
//
Expand All @@ -110,7 +109,7 @@ fn fetchPackages(
pub_key: *const minizign.PublicKey,
progress: std.Progress.Node,
) !void {
const package_count = resolved.package_table.count();
const package_count = resolved.count();

var fetch_progress = progress.start("fetching", package_count);
defer fetch_progress.end();
Expand All @@ -125,21 +124,18 @@ fn fetchPackages(
var path_buf: [Io.Dir.max_path_bytes]u8 = undefined;

var fetch_count: usize = 0;
const pkg_slice = resolved.packages.slice();
var it = resolved.package_table.iterator();
while (it.next()) |entry| {
const pkg_id = entry.key_ptr.*;
const pkg_idx = entry.value_ptr.*;
const name = pkg_slice.items(.name)[@intFromEnum(pkg_idx)];
const version = pkg_slice.items(.version)[@intFromEnum(pkg_idx)];
var it = resolved.iterator();
while (it.next()) |pkg| {
const pkg_id = pkg.id;
const name = pkg.name;
const version = pkg.version;

const path = try std.fmt.bufPrint(&path_buf, "{s}-{f}-{s}.tar.zst", .{
name.slice(&resolved.string_state), version, pkg_id.slice(&resolved.string_state)[0..32],
});
cahce_dir.access(io, path, .{}) catch {
// try fetchPackage(io, gpa, pkg_id, pkg_idx, pkg_slice, &resolved.string_state, &queue, pub_key, fetch_progress);
group.async(io, fetchPackage, .{
io, gpa, pkg_id, pkg_idx, pkg_slice, &resolved.string_state, &queue, pub_key, fetch_progress,
io, gpa, pkg_id, pkg, &resolved.string_state, &queue, pub_key, fetch_progress,
});
fetch_count += 1;
continue;
Expand All @@ -165,16 +161,15 @@ fn fetchPackage(
io: Io,
gpa: Allocator,
pkg_id: Package.Id,
pkg_idx: Package.Idx,
packages_slice: std.MultiArrayList(Package).Slice,
pkg: Package,
string_state: *const string.State,
queue: *Io.Queue(anyerror!void),
pub_key: *const minizign.PublicKey,
progress: std.Progress.Node,
) Io.Cancelable!void {
const name = packages_slice.items(.name)[@intFromEnum(pkg_idx)];
const name = pkg.name;
const name_slice = name.slice(string_state);
const version = packages_slice.items(.version)[@intFromEnum(pkg_idx)];
const version = pkg.version;
const pkg_id_slice = pkg_id.slice(string_state);

defer std.debug.print("finished downloading {s}\n", .{name_slice});
Expand Down
1 change: 1 addition & 0 deletions src/string.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub const State = struct {
state.string_table.deinit(gpa);
}

/// Internet String
pub const String = enum(u32) {
none = std.math.maxInt(u32),
_,
Expand Down