Build Windows Libraries with llvm-lib-18 on Linux

llvm-lib-18 turns Windows COFF object files into a library a Windows linker can use, then lets you check the symbols before you ship it. Allow about 15 minutes for a first test. This guide uses the llvm-lib-18 binary from Ubuntu's llvm-18 package, version 1:18.1.3-1ubuntu1, and its installed manual page.

The examples are ordinary user commands. They write only under the working directory you choose, so sudo is not required. Replace the example paths with your own. Keep generated libraries separate from files you already use in a build until the checks pass.

1. Check the installed toolchain

llvm-lib-18 is an LLVM implementation intended to be compatible with Microsoft's lib.exe. It is an archiver, not a compiler or linker: it packages object files and records their symbols. The input objects must already be built for the target ABI.

$ command -v llvm-lib-18
/usr/bin/llvm-lib-18
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-18
llvm-18 1:18.1.3-1ubuntu1
$ clang-18 --version | sed -n '1p'
Ubuntu clang version 18.1.3

Check that the output names the binary you expect. Do not use llvm-lib-18 --version as a version check on this installed build: it treats that GNU-style option as an unknown argument. The package query and the command path are the useful local checks.

2. Create a small COFF object for a safe test

If you already have Windows-targeted .obj files, skip to the next step. Otherwise, create a temporary C source file and compile it to a 64-bit Windows COFF object. This test does not execute the object and does not need a Windows installation.

$ workdir="$PWD/llvm-lib-demo"
$ mkdir -p "$workdir"
$ printf '%s\n' 'int guide_symbol(void) { return 42; }' > "$workdir/guide.c"
$ clang-18 --target=x86_64-pc-windows-msvc -c "$workdir/guide.c" -o "$workdir/guide.obj"
$ file "$workdir/guide.obj"
llvm-lib-demo/guide.obj: Intel amd64 COFF object file, not stripped, ...

The important part of the final line is COFF object file. A native Linux ELF object is the wrong input for a Windows library, even if the archiver accepts the file. If your build targets 32-bit Windows or another architecture, compile the objects for that same target and use the matching /machine setting when your build requires one.

Checkpoint: Stop here if the object is not COFF or if the compiler reports an unsupported target. Fix the compilation target before creating a library.

3. Build a normal library

Pass the output name with the documented /out:<output> form, followed by the input object paths. /nologo suppresses the compatibility banner. The colon belongs to the option; a separate argument after /out is not equivalent and fails on this build.

$ llvm-lib-18 /nologo /out:"$workdir/guide.lib" "$workdir/guide.obj"
$ file "$workdir/guide.lib"
llvm-lib-demo/guide.lib: current ar archive
$ llvm-nm-18 "$workdir/guide.lib"

llvm-lib-demo/guide.obj:
00000000 a @feat.00
00000000 T guide_symbol

A zero exit status and an archive file show that the operation completed. The symbol listing is the stronger check: guide_symbol is present in the library index. The exact file wording and symbol addresses can vary, but the archive type and expected symbol should remain clear.

Do not add sudo just because a later link fails. First check the object format, target architecture and library search path. Elevation does not repair an incompatible binary.

4. Add several objects without overwriting the wrong file

List each object as an input. Shell redirection is not involved here, but /out: names the file that will be written. Treat an existing output as disposable: choose a new name or make a backup before rebuilding it.

$ cp --preserve=all "$workdir/guide.lib" "$workdir/guide.lib.backup"
$ llvm-lib-18 /nologo /out:"$workdir/guide-new.lib" \
    "$workdir/guide.obj" "$workdir/another.obj"
$ llvm-nm-18 "$workdir/guide-new.lib" | grep -E 'guide_symbol|another_symbol'
00000000 T guide_symbol
00000000 T another_symbol

The second object is a placeholder for another real COFF object, so replace another.obj before running the command. Build to guide-new.lib and inspect it before replacing the previous library. If the new build fails, leave the old file in place and investigate the diagnostic. Once the replacement is confirmed, remove the backup manually only if you no longer need recovery.

5. Use a thin archive only when its consumers support it

Add /llvmlibthin when you need a smaller archive containing the symbol table and member headers rather than full copies of the object contents. The members remain external to the archive, so the object files must stay where the consuming tool can find them.

$ llvm-lib-18 /nologo /llvmlibthin \
    /out:"$workdir/guide-thin.lib" "$workdir/guide.obj"
$ file "$workdir/guide-thin.lib"
llvm-lib-demo/guide-thin.lib: thin archive with 1 symbol entry
$ llvm-nm-18 "$workdir/guide-thin.lib" | grep guide_symbol
00000000 T guide_symbol

The installed manual warns that thin archives are not compatible with Microsoft's link.exe, although lld can handle them. Use a normal library for a build that must work with link.exe. A thin archive is a deliberate compatibility choice, not a drop-in compression setting.

Recovery: If a consumer cannot link the thin archive, rebuild the same objects as a normal library by omitting /llvmlibthin. Do not delete the objects: they are required by the thin archive and may be needed for the rebuild.

6. Diagnose the common failures

A missing input is reported with a non-zero status and a path-specific error:

$ llvm-lib-18 /nologo /out:"$workdir/bad.lib" "$workdir/no-such.obj"
llvm-lib-demo/no-such.obj: no such file or directory
$ printf 'exit status: %s\n' "$?"
exit status: 1

Check the path and readability without changing anything:

$ ls -l "$workdir/no-such.obj"
$ test -r "$workdir/guide.obj" && echo readable
readable

If a link tool cannot find a symbol, inspect the archive with llvm-nm-18. If the symbol is absent, the object may not export the spelling you expect, or you may have archived an older build. If the symbol is present but linking still fails, compare the target architecture and calling convention of the object, library and final link command.

When the archive output already exists, avoid blind replacement. Build under a new name, verify it, then move it into place during a controlled build step. A move can still disrupt a running build, so coordinate it with the process that reads the library. The examples here do not change services or system-wide configuration.

Done means