First Commit
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
-- packager.lua -- tarball creation, sha256 computation, and package.lua generation.
|
||||
--
|
||||
-- Converts a staged install tree (DESTDIR layout) into a Zeta-compatible
|
||||
-- binary tarball with archive strip=1 format. Computes the sha256 checksum
|
||||
-- of the output tarball and writes the package.lua manifest.
|
||||
|
||||
local packager = {}
|
||||
|
||||
local path = require("path")
|
||||
local log = require("log")
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- Private helpers (must be defined before the public API that uses them)
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
local function escape(s)
|
||||
return s:gsub('"', '\\"'):gsub("\n", "\\n")
|
||||
end
|
||||
|
||||
local function escape_lua_string(s)
|
||||
return s:gsub("\\", "\\\\"):gsub("'", "\\'")
|
||||
end
|
||||
|
||||
local function human_size(filepath)
|
||||
local f = io.open(filepath, "rb")
|
||||
if not f then return "? B" end
|
||||
local size = f:seek("end")
|
||||
f:close()
|
||||
if size >= 1024 * 1024 then
|
||||
return string.format("%.1f MiB", size / (1024 * 1024))
|
||||
elseif size >= 1024 then
|
||||
return string.format("%.0f KiB", size / 1024)
|
||||
end
|
||||
return string.format("%d B", size)
|
||||
end
|
||||
|
||||
local function sha256_file(filepath)
|
||||
local f = io.popen("sha256sum " .. path.quote(filepath) .. " 2>/dev/null")
|
||||
if f then
|
||||
local line = f:read("*l")
|
||||
f:close()
|
||||
local hex = line and line:match("^(%x+)")
|
||||
if hex then return hex end
|
||||
end
|
||||
f = io.popen("shasum -a 256 " .. path.quote(filepath) .. " 2>/dev/null")
|
||||
if f then
|
||||
local line = f:read("*l")
|
||||
f:close()
|
||||
local hex = line and line:match("^(%x+)")
|
||||
if hex then return hex end
|
||||
end
|
||||
f = io.popen("openssl dgst -sha256 " .. path.quote(filepath) .. " 2>/dev/null")
|
||||
if f then
|
||||
local line = f:read("*l")
|
||||
f:close()
|
||||
local hex = line and line:match("=(%x+)")
|
||||
if hex then return hex end
|
||||
end
|
||||
error("sha256: no supported tool found (install sha256sum, shasum, or openssl)", 0)
|
||||
end
|
||||
|
||||
local function write_atomic(filepath, content)
|
||||
local tmp = filepath .. ".tmp"
|
||||
local f, err = io.open(tmp, "wb")
|
||||
if not f then return nil, err end
|
||||
f:write(content)
|
||||
f:close()
|
||||
return os.rename(tmp, filepath)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- Public API
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
-- Tar up the staging directory, strip=1 format.
|
||||
-- Files in the tarball are rooted at "./" so Zeta's strip=1 produces clean
|
||||
-- relative paths (usr/bin/foo, not ./usr/bin/foo).
|
||||
function packager.create_tarball(stage_dir, dest_path)
|
||||
log.step("packaging " .. path.basename(dest_path))
|
||||
path.mkdir_p(path.dirname(dest_path))
|
||||
|
||||
local cmd = "tar -czf " .. path.quote(dest_path)
|
||||
.. " -C " .. path.quote(stage_dir) .. " ."
|
||||
|
||||
if not path.run(cmd) then
|
||||
error(("failed to create tarball %s"):format(dest_path), 0)
|
||||
end
|
||||
|
||||
local sha256 = sha256_file(dest_path)
|
||||
log.detail(("sha256: %s"):format(sha256))
|
||||
log.ok(("packaged %s (%s)"):format(path.basename(dest_path), human_size(dest_path)))
|
||||
|
||||
return sha256
|
||||
end
|
||||
|
||||
-- Generate a Zeta-compatible package.lua manifest and write it to pkg_dir.
|
||||
-- The generated manifest uses archive mode (strip=1) and includes the
|
||||
-- computed sha256 of the binary tarball. Optional fields (deps, files,
|
||||
-- test) are included when present in the recipe.
|
||||
function packager.write_manifest(recipe, tarball_sha256, pkg_dir)
|
||||
local lines = {}
|
||||
local function emit(fmt, ...)
|
||||
lines[#lines + 1] = string.format(fmt, ...)
|
||||
end
|
||||
|
||||
emit("return {")
|
||||
emit(' name = "%s",', recipe.name)
|
||||
emit(' version = "%s",', recipe.version)
|
||||
emit(' summary = "%s",', escape(recipe.summary))
|
||||
emit(' url = "https://github.com/gretagen/zeta-packages/packages/%s/%s-%s.tar.gz",',
|
||||
recipe.name, recipe.name, recipe.version)
|
||||
emit(' sha256 = "%s",', tarball_sha256)
|
||||
|
||||
if #recipe.deps > 0 then
|
||||
local dep_strs = {}
|
||||
for _, d in ipairs(recipe.deps) do
|
||||
dep_strs[#dep_strs + 1] = string.format('"%s"', escape(d))
|
||||
end
|
||||
emit(' deps = { %s },', table.concat(dep_strs, ", "))
|
||||
else
|
||||
emit(' deps = {},')
|
||||
end
|
||||
|
||||
emit(' archive = { strip = 1 },')
|
||||
|
||||
if recipe.files then
|
||||
local file_strs = {}
|
||||
for _, f in ipairs(recipe.files) do
|
||||
file_strs[#file_strs + 1] = string.format('"%s"', escape(f))
|
||||
end
|
||||
emit(' files = { %s },', table.concat(file_strs, ", "))
|
||||
end
|
||||
|
||||
if recipe.test then
|
||||
-- Generate a test function that substitutes ${DESTDIR} -> p.install_root.
|
||||
-- Splits the test command around each ${DESTDIR} and uses Lua's %q format
|
||||
-- so every string segment is properly quoted.
|
||||
emit(" test = function(p)")
|
||||
local parts = {}
|
||||
local rem = recipe.test
|
||||
while true do
|
||||
local si, ei = rem:find("${DESTDIR}", 1, true)
|
||||
if not si then
|
||||
if rem ~= "" then parts[#parts + 1] = string.format("%q", rem) end
|
||||
break
|
||||
end
|
||||
if si > 1 then
|
||||
parts[#parts + 1] = string.format("%q", rem:sub(1, si - 1))
|
||||
end
|
||||
parts[#parts + 1] = "p.install_root"
|
||||
rem = rem:sub(ei + 1)
|
||||
end
|
||||
emit(" p:run(%s)", table.concat(parts, " .. "))
|
||||
emit(" end,")
|
||||
end
|
||||
|
||||
emit("}")
|
||||
|
||||
local content = table.concat(lines, "\n") .. "\n"
|
||||
local dest = path.join(pkg_dir, "package.lua")
|
||||
|
||||
write_atomic(dest, content)
|
||||
log.ok("wrote package.lua")
|
||||
return dest
|
||||
end
|
||||
|
||||
return packager
|
||||
Reference in New Issue
Block a user