Build and Inspect Static Libraries with llvm-ar-18
A static library is just an archive of object files, and llvm-ar-18 is the tool that builds and inspects one. You will finish with a small archive, a repeatable way to inspect it, and a safe method for extracting or removing members. The examples use the installed llvm-ar-18 from Ubuntu package llvm-18, which reports LLVM version 18.1.3.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and two object files, or any other ordinary files you are happy to copy into a test archive. Nothing here needs elevated privileges.
1. Check the installed tool
Confirm the command in your path is the one you actually intend to use. This is read-only:
$ command -v llvm-ar-18
/usr/bin/llvm-ar-18
$ llvm-ar-18 --version
Ubuntu LLVM version 18.1.3
Optimized build.
The local manual is generated from LLVM documentation and labels the tool llvm-ar, while this installation exposes the versioned command. The operation syntax is compact: one operation such as r or t, followed by any permitted modifiers, then the archive name and optional member names.
Checkpoint
If command -v finds a different path, stop and check your package or PATH before creating an archive.
2. Create a regular archive
Make a disposable workspace and two input files. If you are testing a real build, keep the source files outside the archive directory so you can compare them afterwards:
$ mkdir -p /tmp/llvm-ar-demo
$ cd /tmp/llvm-ar-demo
$ printf 'alpha\n' > alpha.txt
$ printf 'beta\n' > beta.txt
$ llvm-ar-18 rcs libdemo.a alpha.txt beta.txt
rreplaces a member with the same name, or inserts a new one.csuppresses the warning that would normally announce creation of a missing archive.screates the archive index linkers use to find externally visible symbols. LLVM ar builds that index by default anyway, but spelling outrcsmakes the intended library-building operation obvious to anyone reading the script later.
For object files, the same pattern is normally written as llvm-ar-18 rcs libexample.a file1.o file2.o. The files do not have to be objects though, the archive format happily holds other file types.
Verify both the member names and the exit status before using the archive in a build:
$ llvm-ar-18 t libdemo.a
alpha.txt
beta.txt
$ printf 'exit status: %s\n' "$?"
exit status: 0
3. Inspect contents without extracting
Use t for a name-only table. Add v when ownership, mode, size and timestamp matter:
$ llvm-ar-18 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
Those zero ownership fields and 1970 timestamps are expected: LLVM ar writes deterministic archives by default, using zero timestamps and UIDs/GIDs so repeated builds are easier to compare. The U modifier opts into real timestamps and ownership values; reach for it only when another tool genuinely requires them.
The O modifier adds each member's byte offset inside the archive. Handy when diagnosing an archive made by another tool, not something you need for routine verification:
$ llvm-ar-18 tvO libdemo.a
rw-r--r-- 0/0 6 Jan 1 01:00 1970 alpha.txt 0x44
rw-r--r-- 0/0 5 Jan 1 01:00 1970 beta.txt 0x86
4. Extract into a separate directory
Extraction writes files into the current directory unless you pass --output. Pick a fresh destination so an archive member cannot silently overwrite a working file you actually care about:
$ mkdir extracted
$ llvm-ar-18 x --output extracted libdemo.a
$ find extracted -maxdepth 1 -type f -printf '%f: ' -exec cat {} \;
alpha.txt: alpha
beta.txt: beta
x extracts every member when no member names follow the archive. To extract just one, give its exact name after the archive, for example llvm-ar-18 x --output extracted libdemo.a alpha.txt. Extracted files get the extraction time by default; add o if you need the member's stored modification time preserved instead.
Warning
Extraction can overwrite files already sitting in the destination. Use a new empty directory, or back things up first. To undo this example, remove only the disposable extracted directory after checking its contents, and do not run a broad recursive delete anywhere near source or build outputs.
5. Replace or delete members deliberately
To update an existing member, prepare the replacement and run r again:
$ printf 'new alpha\n' > alpha.txt.new
$ llvm-ar-18 r libdemo.a alpha.txt.new
$ llvm-ar-18 t libdemo.a
alpha.txt
beta.txt
alpha.txt.new
The member name comes from the input file name, so that inserted alpha.txt.new instead of replacing alpha.txt. Rename the prepared file first if the archive member needs to keep its original name:
$ mv alpha.txt.new alpha.txt
$ llvm-ar-18 rcs libdemo.a alpha.txt
$ llvm-ar-18 t libdemo.a
alpha.txt
beta.txt
Warning
Before replacing a library a build actually depends on, copy it to a backup such as libdemo.a.bak first. If the result is wrong, restore it with mv libdemo.a.bak libdemo.a. That restore changes state, so double-check the path and backup name before running it.
Delete named members with d:
$ llvm-ar-18 d libdemo.a beta.txt
$ llvm-ar-18 t libdemo.a
alpha.txt
Deletion is destructive to the archive file. It will not touch the original input file on disk, but it can remove the only archived copy you had. Keep a backup when the archive is not disposable. A requested name that is absent is not treated as an error by the local manual, so verify the table afterwards rather than trusting the exit status alone.
6. Treat thin archives as a different boundary
A thin archive stores references to external files instead of copying their contents in. Create one explicitly with --thin:
$ llvm-ar-18 --thin rcs libthin.a alpha.txt beta.txt
$ llvm-ar-18 t libthin.a
alpha.txt
beta.txt
$ file libthin.a
# file reports a thin archive; the exact description varies by file(1) version
The member list looks like an ordinary archive, but moving or deleting alpha.txt can make the thin archive unusable. Keep referenced files at the paths the build expects, and treat the thin archive and those files as one unit. When you modify a thin archive without --thin, the local manual says it gets converted to a regular archive; make that choice explicit in scripts rather than relying on an accidental conversion.
Do not reach for --thin merely to shrink a library without checking your linker and packaging workflow. A thin archive is not self-contained and cannot safely replace a regular archive in a hand-off or release bundle.
Common traps
- Missing archive or input. Usually a path or working-directory error. Run
pwd, then check each path withls -l. - Empty table. Check that the operation included member names:
r,qandddo nothing when no files are supplied. - Dropping the index. Use
Sonly when you deliberately want no index, for example while investigating a format issue. A library meant for normal linking should keep its symbol table. When an archive mixes LLVM bitcode and native objects, LLVM ar includes both kinds of externally visible symbols in the index. - Porting a script from another ar. Do not expect the ignored
--pluginoption to add support for an unfamiliar format, and do not use the unsupported filename-truncation optionffrom another ar implementation. Checkllvm-ar-18 --helpon the installed version first.
Done means
- Version confirmed.
llvm-ar-18 --versionidentified the installed LLVM 18.1.3 executable. - Archive built and verified. A regular archive was created with
rcsand its member list checked witht. - Verbose output understood. Deterministic archives normally show zero ownership and epoch timestamps, and that is expected.
- Extraction deliberate. It used a chosen destination, with overwrite risk considered.
- Changes verified. Replacement and deletion were followed by a fresh table-of-contents check.
- Thin archives respected. Any thin archive stays with the external files it references and is never mistaken for a self-contained library.