diff options
Diffstat (limited to 'src/luarocks_runtime.zig')
| -rw-r--r-- | src/luarocks_runtime.zig | 122 |
1 files changed, 105 insertions, 17 deletions
diff --git a/src/luarocks_runtime.zig b/src/luarocks_runtime.zig index 1505b8f..96b2e37 100644 --- a/src/luarocks_runtime.zig +++ b/src/luarocks_runtime.zig @@ -916,9 +916,20 @@ fn batteryInstalled( layout: panto_home.Layout, battery: manifest.Battery, ) !bool { + return rockMetaExists(allocator, io, layout, battery.name, battery.version); +} + +/// Whether `<tree>/lib/luarocks/rocks-<short>/<name>/<version>/` exists. +fn rockMetaExists( + allocator: Allocator, + io: Io, + layout: panto_home.Layout, + name: []const u8, + version: []const u8, +) !bool { const subpath = try std.fs.path.join( allocator, - &.{ layout.rocks_metadata_dir, battery.name, battery.version }, + &.{ layout.rocks_metadata_dir, name, version }, ); defer allocator.free(subpath); @@ -955,10 +966,92 @@ fn installBattery( L: *c.lua_State, battery: manifest.Battery, ) !void { - // We wrap the embedded driver in a function that overrides - // `os.exit` for the duration of the call. luarocks's `die()` - // ultimately calls `os.exit(1)`, which would terminate panto - // entirely; we want to catch the failure as a regular error. + _ = allocator; // no scratch needed + try runLuarocksDriver(L, &.{ "install", battery.name, battery.version }, battery.name); +} + +/// Install a user rock from a luarocks dependency spec (e.g. +/// `"panto-agent 1.4.2-1"`), running `luarocks install <spec-tokens>` in +/// process against `rt`'s `lua_State`. The spec is passed through verbatim, +/// split on whitespace into the positional `install` arguments luarocks +/// expects (`<name> [<version>]`). Idempotent: luarocks skips a rock that is +/// already installed at the requested version. +pub fn installRock(rt: *LuarocksRuntime, allocator: Allocator, spec: []const u8) !void { + var args: std.array_list.Managed([]const u8) = .init(allocator); + defer args.deinit(); + try args.append("install"); + var it = std.mem.tokenizeAny(u8, spec, " \t"); + while (it.next()) |tok| try args.append(tok); + if (args.items.len < 2) return error.LuarocksInstallFailed; // no rock name + try runLuarocksDriver(rt.L, args.items, spec); +} + +/// Like `installRock`, but avoids a per-startup network hit: it skips the +/// install when the local tree already has something matching the spec. +/// - exact pin (`name version-revision`, no operators): skip if that exact +/// version is installed. +/// - bare name or range/operators: skip if the rock name has *any* version +/// installed. Upgrading within a range is deferred to `panto update`. +/// A spec whose rock is absent (new pin / new rock) always installs once. +/// Returns true if an install actually ran. +pub fn installRockIfMissing( + rt: *LuarocksRuntime, + allocator: Allocator, + io: Io, + spec: []const u8, +) !bool { + var it = std.mem.tokenizeAny(u8, spec, " \t"); + const name = it.next() orelse return error.LuarocksInstallFailed; + const version = it.next(); + const rest = it.next(); + + const is_exact_pin = version != null and rest == null and + std.mem.indexOfAny(u8, version.?, "<>=~") == null; + + const present = if (is_exact_pin) + try rockMetaExists(allocator, io, rt.layout, name, version.?) + else + try rockAnyVersionInstalled(allocator, io, rt.layout, name); + + if (present) return false; + try installRock(rt, allocator, spec); + return true; +} + +/// Whether `<tree>/lib/luarocks/rocks-<short>/<name>/` has any installed +/// version subdirectory. +fn rockAnyVersionInstalled( + allocator: Allocator, + io: Io, + layout: panto_home.Layout, + name: []const u8, +) !bool { + const dirpath = try std.fs.path.join(allocator, &.{ layout.rocks_metadata_dir, name }); + defer allocator.free(dirpath); + + var dir = Io.Dir.cwd().openDir(io, dirpath, .{ .iterate = true }) catch |err| switch (err) { + error.FileNotFound, error.NotDir => return false, + else => return err, + }; + defer dir.close(io); + + var iter = dir.iterate(); + while (try iter.next(io)) |entry| { + if (entry.name.len == 0 or entry.name[0] == '.') continue; + if (entry.kind == .directory) return true; + } + return false; +} + +/// Run the embedded luarocks CLI driver in process with `cli_args` +/// (e.g. `{"install", "<name>", "<version>"}`). `label` is used only for +/// error messages. Overrides `os.exit` so a luarocks `die()` surfaces as a +/// Zig error instead of terminating panto. +fn runLuarocksDriver( + L: *c.lua_State, + cli_args: []const []const u8, + label: []const u8, +) !void { const driver = embedded_luarocks.luarocks_main; const wrapper = @@ -998,24 +1091,19 @@ fn installBattery( } } - // Pass the driver source as the first arg, then the luarocks - // CLI args. We use `lua_pushlstring` to push everything as - // Lua strings (no NUL terminator needed, since lua_pushlstring - // takes an explicit length). + // Pass the driver source as the first arg, then the luarocks CLI args. + // `lua_pushlstring` takes an explicit length (no NUL needed). _ = c.lua_pushlstring(L, driver_slice.ptr, driver_slice.len); - _ = c.lua_pushlstring(L, "install", 7); - _ = c.lua_pushlstring(L, battery.name.ptr, battery.name.len); - _ = c.lua_pushlstring(L, battery.version.ptr, battery.version.len); - - _ = allocator; // currently unused; reserved for future scratch needs + for (cli_args) |a| _ = c.lua_pushlstring(L, a.ptr, a.len); - if (c.lua_pcallk(L, 4, 0, 0, 0, null) != 0) { + const nargs: c_int = @intCast(1 + cli_args.len); + if (c.lua_pcallk(L, nargs, 0, 0, 0, null) != 0) { var len: usize = 0; const msg = c.lua_tolstring(L, -1, &len); if (msg != null) { std.log.err( - "panto: luarocks install of {s} {s} failed: {s}", - .{ battery.name, battery.version, msg[0..len] }, + "panto: luarocks install of '{s}' failed: {s}", + .{ label, msg[0..len] }, ); } c.lua_settop(L, c.lua_gettop(L) - 1); |
