Build Windows-compatible libraries with llvm-lib-20

llvm-lib-20 turns Windows object files into a .lib archive on Linux, so a cross-compiled library never needs a Windows machine to package it. Allow about fifteen minutes if the object files already exist. The examples use the llvm-lib-20 executable from Debian package llvm-20, version 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139.

You need a shell and object files produced for a Windows target, normally .obj files. The tool is intended to be compatible with Microsoft's lib.exe command-line shape. It creates an archive; it does not link an executable, compile source code, or require root privileges.

1. Confirm the installed command

Check which executable will run before putting it into a build script:

$ command -v llvm-lib-20
/usr/bin/llvm-lib-20
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-20
llvm-20 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139

This version does not use GNU-style --help or --version options. Passing either is treated as an unknown argument and can still lead to a warning about the missing output. Use the installed manpage for the short synopsis and keep the package version with your build notes when reproducibility matters.

Checkpoint: The command path is /usr/bin/llvm-lib-20 and the package query identifies llvm-20. If either differs, verify the option behaviour on that installation before copying these examples into automation.

2. Create a normal archive

Use /out: to name the result and put object files after the options. Replace the placeholders with real paths:

$ llvm-lib-20 /out:/path/to/build/widgets.lib \
    /path/to/build/widget.obj \
    /path/to/build/format.obj

A successful run is quiet and returns status 0. The output is a current ar archive, which is the container format used by this LLVM tool for the Windows-compatible library:

$ printf 'status=%s\n' "$?"
status=0
$ file /path/to/build/widgets.lib
/path/to/build/widgets.lib: current ar archive

Run this as the build user. Elevation is not needed unless your chosen input or output directory is deliberately inaccessible, and using sudo would make ownership and later clean-up harder.

3. Verify the archive's symbols

Check the archive with an inspection tool rather than trusting only the exit status. LLVM's matching utility is commonly installed as llvm-nm-20:

$ llvm-nm-20 /path/to/build/widgets.lib

/path/to/build/widget.obj:
00000000 a @feat.00
00000000 T widget_create

The exact addresses and symbol list depend on your objects. Look for the public symbols that the eventual linker must resolve. If the archive exists but the expected name is absent, inspect the object file and the source's export or naming rules; rebuilding the archive cannot add a symbol that was never emitted.

Checkpoint: file recognises the archive and llvm-nm-20 lists the expected object members and symbols. Keep the original objects until this check and a real link have succeeded.

4. Use a thin archive only with a compatible linker

Add /llvmlibthin when you want a smaller archive containing the symbol table and member headers rather than full copies of the object contents:

$ llvm-lib-20 /out:/path/to/build/widgets-thin.lib \
    /llvmlibthin \
    /path/to/build/widget.obj \
    /path/to/build/format.obj
$ file /path/to/build/widgets-thin.lib
/path/to/build/widgets-thin.lib: thin archive with 2 symbol entries

A thin archive depends on its member object files remaining available at the paths recorded in the archive. It is not compatible with Microsoft's link.exe; the LLVM documentation says that lld can handle it. Use a normal archive for a library that must travel independently or be consumed by link.exe.

This is a build-layout choice, not a compression setting. Do not delete or relocate the object files after creating a thin archive unless you have confirmed how your linker resolves its members.

5. Handle an empty output deliberately

With no input files, the installed command warns and does not write an archive:

$ llvm-lib-20 /out:/path/to/build/empty.lib
warning: no input files, not writing output file
        pass /llvmlibempty to write empty .lib file
        pass /ignore:emptyoutput to suppress warning

If an empty archive is genuinely required, say so explicitly:

$ llvm-lib-20 /out:/path/to/build/empty.lib /llvmlibempty
$ file /path/to/build/empty.lib
/path/to/build/empty.lib: current ar archive

Use /ignore:emptyoutput only when an absent input list is expected and the lack of an archive is acceptable. On this installed version, bare /ignore is parsed as a file name and fails with "no such file or directory".

6. Avoid losing an existing library

Warning: /out: writes the named output. Do not point it at a library that you may need to recover unless replacement is intentional. Build to a temporary name, verify it, then replace the old file:

$ llvm-lib-20 /out:/path/to/build/widgets.lib.new \
    /path/to/build/widget.obj
$ file /path/to/build/widgets.lib.new
/path/to/build/widgets.lib.new: current ar archive
$ mv /path/to/build/widgets.lib.new /path/to/build/widgets.lib

If the command fails, leave the old library untouched and investigate the diagnostic. To undo the final replacement, restore your normal build artefact or backup; there is no llvm-lib-20 undo operation. Do not remove the old file until a consumer has linked successfully.

7. Diagnose the common failures

A missing input produces a non-zero status and no valid replacement:

$ llvm-lib-20 /out:/path/to/build/widgets.lib.new /path/to/missing.obj
/path/to/missing.obj: no such file or directory
$ printf 'status=%s\n' "$?"
status=1

Check the path and readability without changing anything:

$ ls -l /path/to/input.obj
$ test -r /path/to/input.obj && echo readable

If the archive is unexpectedly unusable, first compare the member paths and target format. A thin archive may be pointing at moved objects, while a normal archive may contain objects for a different architecture. Use file, llvm-nm-20, and the linker diagnostic together. Do not switch to elevated privileges as a substitute for fixing a bad path or incompatible object.

Done means