Rebuild an LLVM Archive Index with llvm-ranlib-18

The linker cannot find a symbol that is obviously in your static library, and the fix is often a stale index: llvm-ranlib-18 rebuilds it in seconds. This guide uses Ubuntu's llvm-18 package, version 1:18.1.3-1ubuntu1, and the installed program reports LLVM 18.1.3.

Allow about ten minutes. You need a shell, an existing Unix archive such as libexample.a, and write permission for its directory. The examples use llvm-ar-18 and clang-18 only to create a small test archive. In a normal build, your compiler and archiver will already have produced the archive.

1. Confirm the installed command

Check the executable and package before putting it into a build script. These are ordinary read-only commands and do not need elevated privileges:

$ command -v llvm-ranlib-18
/usr/bin/llvm-ranlib-18
$ llvm-ranlib-18 --version
Ubuntu LLVM version 18.1.3
  Optimized build.
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-18
llvm-18 1:18.1.3-1ubuntu1

Checkpoint: if the command is missing, stop here and use your normal package-management process. Do not replace it with an unversioned ranlib until you have checked that the toolchain expects that program.

2. Understand what ranlib changes

An archive is a collection of object files, commonly named libexample.a. Its symbol index lets a linker find an object member that supplies a needed function without scanning every member in turn. llvm-ranlib-18 generates that index in the archive supplied as its positional argument:

$ llvm-ranlib-18 /path/to/libexample.a

Warning: the command updates the archive in place. It does not compile source files, add object members, or choose a library name. Treat the archive as the state that will change. Before running the command on an important build artefact, keep the source objects or a reproducible build available. If you need a reversible trial, copy the archive to a scratch path first:

$ cp --preserve=all /path/to/libexample.a /tmp/libexample-ranlib-test.a
$ llvm-ranlib-18 /tmp/libexample-ranlib-test.a

The copy is the one changed by that test. There is no separate undo option; restore the original from a known-good copy or rebuild the archive if a later build step has changed more than its index.

3. Create a small archive for a safe test

If you do not already have an archive to inspect, create one in a temporary directory. The following commands do not need root privileges:

$ workdir=$(mktemp -d /tmp/llvm-ranlib-example.XXXXXX)
$ printf '%s\n' 'int ranlib_demo(void) { return 7; }' > "$workdir/demo.c"
$ clang-18 -c "$workdir/demo.c" -o "$workdir/demo.o"
$ llvm-ar-18 rc "$workdir/libdemo.a" "$workdir/demo.o"
$ llvm-ranlib-18 "$workdir/libdemo.a"

llvm-ar-18 rc creates an archive containing the object file. The final command then creates or refreshes its symbol table. If your system has a different compiler command, use that command to produce an object file and keep the llvm-ranlib-18 step unchanged.

Checkpoint: confirm that the archive contains the object member:

$ llvm-ar-18 t "$workdir/libdemo.a"
demo.o

4. Verify the symbol index

Use an archive inspection tool to check the result. llvm-ranlib-18 is quiet on success, so silence alone is not a useful inspection result:

$ nm -s "$workdir/libdemo.a"

Archive index:
ranlib_demo in demo.o

demo.o:
0000000000000000 T ranlib_demo

The address and symbol type can vary with the object format, but the archive index should name ranlib_demo and point to demo.o. You can also ask the archiver to print the index:

$ llvm-ar-18 s "$workdir/libdemo.a"

That command is a separate archiver operation. Use it as an inspection or repair step only when it matches your toolchain's workflow; the command documented here for generating the index is llvm-ranlib-18.

5. Choose deterministic or real metadata

LLVM 18 defaults to -D, which uses zero for archive member timestamps and user and group IDs. That default helps make build outputs reproducible. Make it explicit in a build command when reproducibility is part of the requirement:

$ llvm-ranlib-18 -D /path/to/libexample.a

-U selects actual timestamps and user and group IDs instead:

$ llvm-ranlib-18 -U /path/to/libexample.a

Do not switch to -U merely to make an archive look newer. It introduces machine- and time-dependent metadata. Conversely, do not assume that -D makes the whole build reproducible: object contents, archive member order and compiler inputs still matter.

6. Diagnose the likely failures

A missing or unreadable archive normally fails before any useful index is written. Check the path and permissions without using sudo:

$ ls -l /path/to/libexample.a
$ test -r /path/to/libexample.a && echo readable
$ test -w /path/to/libexample.a && echo writable
$ llvm-ranlib-18 /path/to/libexample.a
$ printf 'exit status: %s\n' "$?"

A zero status means the command completed successfully. It does not prove that the archive contains the symbol your application needs, so repeat the nm -s check when that distinction matters.

Recovery: if the archive is in a protected build directory, ask the build owner to fix ownership or permissions rather than routinely running the command as root. Elevated privileges can hide a deployment mistake and may leave root-owned output behind. If a build process has already replaced the archive, recover by rebuilding from its object files or restoring the last known-good copy.

7. Know the small option set

The installed command accepts an archive path, -h/--help, -v/--version, -D, -U, and the AIX-only -X choice. -X accepts 32, 64, 32_64 or any to specify which archive symbol tables should be generated when they do not already exist. It is not a general Linux compatibility switch, so leave it out on an ordinary Linux build unless an AIX archive workflow specifically requires it.

Do not add source files or linker flags to this command. If a symbol is missing, inspect the archive members and the build command that produced them first. Rebuilding an index cannot create a symbol that no object file defines.

Done means