Skip to content
Merged
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
49 changes: 49 additions & 0 deletions scripts/build-ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,52 @@
is_musllinux = plat == "Linux" and platform.libc_ver()[0] != "glibc"


def make_archive_deterministic(path: str) -> None:
"""Zero mtime/uid/gid in every ar member header to make the archive reproducible.

Static archives (.a files) embed the file modification time (and uid/gid) of
each member in a 60-byte header. Tools like ar(1), libtool -static, and ar -M
do not always produce deterministic timestamps even when SOURCE_DATE_EPOCH is set
or the -D flag is requested, especially in custom CMake commands (e.g. x265's
multi-lib merge). Zeroing these fields in-place is the safest cross-platform fix.
"""
MAGIC = b"!<arch>\n"
HEADER_SIZE = 60
with open(path, "r+b") as f:
if f.read(len(MAGIC)) != MAGIC:
return
while True:
pos = f.tell()
header = f.read(HEADER_SIZE)
if len(header) < HEADER_SIZE:
break
if header[58:60] != b"`\n":
break # not a valid ar member header
try:
size = int(header[48:58].decode().strip())
except ValueError:
break
# ar member header layout (60 bytes):
# [0:16] name 16 bytes
# [16:28] mtime 12 bytes ← zero this
# [28:34] uid 6 bytes ← zero this
# [34:40] gid 6 bytes ← zero this
# [40:48] mode 8 bytes
# [48:58] size 10 bytes
# [58:60] end magic 2 bytes (`\n)
patched = (
header[:16]
+ b"0 " # mtime: 12 bytes
+ b"0 " # uid: 6 bytes
+ b"0 " # gid: 6 bytes
+ header[40:] # mode + size + end magic unchanged
)
f.seek(pos)
f.write(patched)
# advance past member data; ar pads to even offset
f.seek(size + (size % 2), 1)


def calculate_sha256(filename: str) -> str:
sha256_hash = hashlib.sha256()
with open(filename, "rb") as f:
Expand Down Expand Up @@ -478,6 +524,9 @@ def main():
else:
run(["strip", "-s"] + libraries)

for lib in glob.glob(os.path.join(dest_dir, "lib", "*.a")):
make_archive_deterministic(lib)

# build output tarball (reproducible: fixed timestamps, sorted entries)
os.makedirs(output_dir, exist_ok=True)
with gzip.GzipFile(output_tarball, "wb", mtime=0) as gz:
Expand Down
1 change: 1 addition & 0 deletions scripts/cibuildpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ def _build_with_cmake(self, package: Package, for_builder: bool) -> None:
"-DCMAKE_INSTALL_LIBDIR=lib",
"-DCMAKE_INSTALL_PREFIX=" + prefix,
]

if platform.system() == "Darwin":
cmake_args.append("-DCMAKE_INSTALL_NAME_DIR=" + os.path.join(prefix, "lib"))

Expand Down
Loading