Build and Inspect Deterministic Archives with llvm-ar-20

Two builds of the same code should produce identical archives, and llvm-ar-20 makes that the default rather than a fight. You will finish with a small static library archive that you can list, update, extract and check without guessing what changed. The examples use llvm-ar-20 from Ubuntu's llvm-20 package, version 20.1.8.

Allow about fifteen minutes, plus time to identify the object files or other members you actually want to package. You need a shell and write access to a scratch directory. These commands do not need sudo.

Warning: an archive is a file, not a running service, but the update and delete operations change it in place. Work on a copy when the archive matters, and keep the original around until the verification step has passed.

1. Confirm the installed command

Check the executable and package version before relying on anything below. This is read-only:

$ command -v llvm-ar-20
/usr/bin/llvm-ar-20
$ llvm-ar-20 --version
Ubuntu LLVM version 20.1.8
  Optimized build.
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-20
llvm-20 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139

The operation comes first, followed by modifiers, the archive name and then member files. rcs, for example, means replace or insert, create the archive if needed, and build its index.

Checkpoint: if the command is missing, stop here and install the matching LLVM package through your normal system administration process. Do not substitute an unverified archiver when the archive format matters.

2. Create an archive with an index

Use harmless text files first so the archive's contents are easy to inspect. In a real build, swap these for object files such as module.o:

$ mkdir -p /tmp/llvm-ar-demo
$ cd /tmp/llvm-ar-demo
$ printf 'alpha\n' > alpha.txt
$ printf 'beta\n' > beta.txt
$ llvm-ar-20 rcs libdemo.a alpha.txt beta.txt

The r operation replaces a member with the same name or inserts it at the end. c creates a missing archive without the usual creation warning. s writes an archive index, the same as running the indexing operation separately afterwards. llvm-ar-20 creates an index by default anyway, but spelling out s makes the intent visible in build scripts.

Confirm the member names and order:

$ llvm-ar-20 t libdemo.a
alpha.txt
beta.txt

For a normal compiled library, that index lets a linker find externally visible symbols without scanning every member. It can include symbols from native object files and from LLVM bitcode files alike.

3. Check deterministic metadata

Run the verbose table operation when you need metadata as well as names:

$ llvm-ar-20 tv libdemo.a
rw-r--r-- 0/0      6 Jan  1 01:00 1970 alpha.txt
rw-r--r-- 0/0      5 Jan  1 01:00 1970 beta.txt

The exact spacing depends on the build, but the zero owner and epoch-style timestamp are the useful clues. llvm-ar-20 enables deterministic archives by default: timestamps and user and group IDs are all written as zero. That makes repeated builds less likely to differ merely because the clock or the invoking account changed.

If a consumer genuinely needs real timestamps and IDs, add the U modifier when writing the archive, for example llvm-ar-20 rU libdemo.a module.o. That deliberately gives up deterministic metadata, so do not add it just to make a listing look more familiar.

4. Replace, append or delete members deliberately

Use r for the usual incremental update. It replaces a matching member rather than creating another copy:

$ printf 'new alpha\n' > alpha.txt
$ llvm-ar-20 r libdemo.a alpha.txt
$ llvm-ar-20 t libdemo.a
alpha.txt
beta.txt

Use q only when you explicitly want a quick append. It does not remove duplicates:

$ llvm-ar-20 q libdemo.a alpha.txt
$ llvm-ar-20 t libdemo.a
alpha.txt
beta.txt
alpha.txt

That duplicate is real. If a consumer selects members by name, duplicates can make the result surprising. Use r for a replacement workflow, or rebuild a clean archive from the intended input list instead.

Warning: deletion is irreversible for that archive file, so make a backup first:

$ cp -- libdemo.a libdemo.a.before-delete
$ llvm-ar-20 d libdemo.a beta.txt
$ llvm-ar-20 t libdemo.a
alpha.txt
alpha.txt

To undo that particular change, restore the copy with cp -- libdemo.a.before-delete libdemo.a. If the named member is absent, deletion is not treated as an error; verify the table afterwards instead of assuming a change happened.

5. Extract into a controlled directory

x writes members into the current working directory by default. Treat extraction as a filesystem change: pick an empty destination and inspect the names first when extracting an untrusted archive.

$ mkdir extracted
$ (cd extracted && llvm-ar-20 x ../libdemo.a alpha.txt)
$ sha256sum extracted/alpha.txt
b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060  extracted/alpha.txt

Omitting the member list extracts everything. The default extracted timestamp is the time of extraction; add o if you specifically need the stored member times instead. Use --output=DIR to choose an extraction directory without changing directory first. An archive member can overwrite a file at the same path in the destination, so do not extract straight into a source tree or a system directory.

6. Use thin archives only when paths remain valid

A thin archive stores references to the original files instead of copying their contents in. Create one with --thin or its short modifier T:

$ llvm-ar-20 rcsT libthin.a alpha.txt beta.txt
$ llvm-ar-20 tv libthin.a
rw-r--r-- 0/0      6 Jan  1 01:00 1970 alpha.txt
rw-r--r-- 0/0      5 Jan  1 01:00 1970 beta.txt

This avoids copying large object files, but the referenced files have to stay available at the paths recorded in the archive. Moving or deleting them can break later linking. A thin archive is not a portable bundle, keep ordinary archives for hand-off, caching or storage where the original files might disappear.

When changing archive kind, read the command's exact rules. Modifying a thin archive without the thin modifier can convert it to a regular archive, and appending archives behaves differently depending on whether each input is thin.

7. Automate inspection with a response file

For a long command line, put the arguments in a response file and pass it with @. Keep that file under the same review and access controls as the build script:

$ printf '%s\n' 't libdemo.a' > list.rsp
$ llvm-ar-20 @list.rsp
alpha.txt
beta.txt
alpha.txt

A response file is input, not an archive manifest with validation behind it. Review it before execution, especially when a generated build directory is shared with other users.

Done means