fix: honor source date in mod packages

This commit is contained in:
MaxTomahawk
2026-08-07 17:54:06 +02:00
parent 112120e8fe
commit 02fd21bcdb
2 changed files with 65 additions and 2 deletions
+46
View File
@@ -636,6 +636,52 @@ local packed = io.open(cleanPkg, "rb")
check(packed ~= nil, "pack writes the package") check(packed ~= nil, "pack writes the package")
if packed then packed:close() end if packed then packed:close() end
-- Reproducible-build callers pin the informational pack timestamp through the
-- standard SOURCE_DATE_EPOCH contract. Two clean invocations over the same
-- input must then produce identical archive bytes and metadata.
local epoch = "1234567890"
local envPrefix = isWindows
and ('set "SOURCE_DATE_EPOCH=%s" && '):format(epoch)
or ("SOURCE_DATE_EPOCH=%s "):format(epoch)
local deterministicA = root .. "/declared-a.modpkg"
local deterministicB = root .. "/declared-b.modpkg"
out, code = run(envPrefix ..
("%s tools/modkit.py pack %q -o %q --base fixture")
:format(python, declared, deterministicA))
check(code == 0, "SOURCE_DATE_EPOCH package A succeeds: " .. out)
out, code = run(envPrefix ..
("%s tools/modkit.py pack %q -o %q --base fixture")
:format(python, declared, deterministicB))
check(code == 0, "SOURCE_DATE_EPOCH package B succeeds: " .. out)
local archiveA = assert(io.open(deterministicA, "rb"))
local bytesA = archiveA:read("*a")
archiveA:close()
local archiveB = assert(io.open(deterministicB, "rb"))
local bytesB = archiveB:read("*a")
archiveB:close()
check(bytesA == bytesB, "SOURCE_DATE_EPOCH makes package bytes reproducible")
local inspectPack = root .. "/inspect_pack.py"
write(inspectPack, [[
import json, sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as archive:
meta = json.loads(archive.read(".modkit/pack.json"))
assert meta["packed_at"] == "2009-02-13T23:31:30Z", meta["packed_at"]
]])
out, code = run(("%s %q %q"):format(python, inspectPack, deterministicA))
check(code == 0, "pack metadata honors SOURCE_DATE_EPOCH: " .. out)
local invalidEpochPrefix = isWindows
and 'set "SOURCE_DATE_EPOCH=not-a-time" && '
or "SOURCE_DATE_EPOCH=not-a-time "
local invalidEpochPkg = root .. "/declared-invalid-epoch.modpkg"
out, code = run(invalidEpochPrefix ..
("%s tools/modkit.py pack %q -o %q --base fixture")
:format(python, declared, invalidEpochPkg))
check(code == 2, "invalid SOURCE_DATE_EPOCH is a usage failure: " .. out)
check(out:find("SOURCE_DATE_EPOCH", 1, true) ~= nil,
"invalid source epoch names the failed contract")
check(io.open(invalidEpochPkg, "rb") == nil,
"invalid source epoch writes no package")
-- MK305 diffs shipped tables against the imported dataset; fake one under -- MK305 diffs shipped tables against the imported dataset; fake one under
-- a scratch repo root so the check exercises the same on ROM-less machines -- a scratch repo root so the check exercises the same on ROM-less machines
local fake = root .. "/fakerepo" local fake = root .. "/fakerepo"
+19 -2
View File
@@ -1085,6 +1085,20 @@ def cmd_lint(args, repo):
# ---------------------------------------------------------------- pack # ---------------------------------------------------------------- pack
def pack_timestamp():
raw = os.environ.get("SOURCE_DATE_EPOCH")
if raw is None:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), None
try:
epoch = int(raw, 10)
if epoch < 0:
raise ValueError("negative epoch")
stamp = datetime.fromtimestamp(epoch, timezone.utc)
except (ValueError, OverflowError, OSError):
return None, "SOURCE_DATE_EPOCH must be a nonnegative Unix timestamp"
return stamp.strftime("%Y-%m-%dT%H:%M:%SZ"), None
def cmd_pack(args, repo): def cmd_pack(args, repo):
mod_dir = resolve_mod_dir(repo, args.mod) mod_dir = resolve_mod_dir(repo, args.mod)
if not mod_dir: if not mod_dir:
@@ -1119,6 +1133,10 @@ def cmd_pack(args, repo):
mod_id = manifest["id"] mod_id = manifest["id"]
version = manifest.get("version", "0.0.0") version = manifest.get("version", "0.0.0")
out = args.output or f"{mod_id}-{version}.modpkg" out = args.output or f"{mod_id}-{version}.modpkg"
packed_at, timestamp_problem = pack_timestamp()
if timestamp_problem:
print(f"modkit: {timestamp_problem}")
return 2
files = mod_files(mod_dir) files = mod_files(mod_dir)
records = [] records = []
for rel in files: for rel in files:
@@ -1127,8 +1145,7 @@ def cmd_pack(args, repo):
"sha256": hashlib.sha256(body).hexdigest()}) "sha256": hashlib.sha256(body).hexdigest()})
pack_meta = { pack_meta = {
"modkit": MODKIT_VERSION, "modkit": MODKIT_VERSION,
"packed_at": datetime.now(timezone.utc) "packed_at": packed_at,
.strftime("%Y-%m-%dT%H:%M:%SZ"),
"id": mod_id, "id": mod_id,
"version": version, "version": version,
"api": manifest.get("api", 1), "api": manifest.get("api", 1),