How to get clang++ to find link.exe
By Casey Muratori
When using clang++ on Windows, it typically falls back to Microsoft’s linker to do the final state of executable output. I don’t follow the LLVM project closely, but my limited understanding is that they are actively working on a Windows-compatible linker, but it’s not ready for production use yet.
Of course, clang++ can only fall back to Microsoft’s linker if it can actually find it. And one of the biggest problems with using clang on Windows is that it often can’t.
I would love to tell you the reason why it can’t find the linker, link.exe. But I can only really tell you a pseudo-answer, which is why the code that’s checked into clang can’t find the linker. The reason why that code exists and does the things it does… well, that I can’t even pretend to understand.
In a sane world, clang++ would have a command line switch that worked like this:
clang++ --linkexe="w:/apps/msvc/x64/link.exe" test.cpp
This would tell clang where to find the linker, and everything would “just work”. But that is not the world we live in, and so instead there is no such switch, and clang has around 1500 lines of source code dedicated to “finding” where the linker is. I use quotes, of course, because basic things you might want it to do, such as using “link.exe” if it is in the system path, probably won’t happen. Instead, the only way to point clang++ at a specific link.exe and guarantee that it will work is to reverse engineer the 1500 lines of code and then construct a fake Visual Studio install for it to “find”.
Since I’ve already done that, I figured I’d save everyone the trouble and post the easiest way I found. We’ll start with the minimal necessary steps:
mkdir %temp_path%\bin\HostX64\x64
copy %linker_path%\link.exe %temp_path%\bin\HostX64\x64
set VCToolsInstallDir=%temp_path%
set PATH=%path%;%linker_path%
These four lines will do the trick, but you’ll need to set %linker_path% to be the directory where link.exe actually is, and %temp_path% to be a temporary location where you don’t mind link.exe being copied. And please note one very important thing: in case my scare quotes haven’t done the trick, let me reinforce the notion that the clang code for finding the linker is extremely janky. So I would not recommend allowing either of those paths to have spaces, or otherwise require quoting, because they may very well not work.
OK, so what’s going on here? Let’s take the lines in order:
mkdir %temp_path%\bin\HostX64\x64
The first thing we’re doing here is making a fake Visual Studio installation path for the linker, and then copying link.exe to it. The reason we need to do this is because the clang code for “finding” the linker can only “find” it if the paths closely resemble the exact paths used by the Visual Studio installer. Because we’d like to use the most up-to-date Visual Studio builds (2017 and up), we want to have clang go down it’s “modern Visual Studio installation” path, which is hard-coded to look for the link.exe executable under a bin\HostX64\x64 directory. So we’ve made one.
Now we just need to put the actual linker there so clang can “find” it:
copy %linker_path%\link.exe %temp_path%\bin\HostX64\x64
This copies the linker into the fake installation directory. You could also try using a reparse point here if you wanted, but since link.exe isn’t very big, it’s probably not worth the hassle.
Note that we don’t copy the entire linker  —  we don’t try to copy DLLs or anything else that it might need, because we’re trying to do the minimum necessary work here. So we need one more thing to make sure the linker will actually work when invoked:
set PATH=%path%;%linker_path%
This is setting your executable path to make sure that all the supporting DLLs for the linker can be found by Windows automatically. This is necessary because remember, we can’t run link.exe from its normal location, which would allow Windows to automatically locate its DLLs (which reside in the same directory). We’re running a copied link.exe, which is in a directory all by itself, so we need to give Windows the correct path so it knows how to find the missing DLLs.
Finally, now that we’ve set everything up, we just need a way to tell clang++ where to start “finding” the linker. The easiest way to do this, and also signal to clang that we are using a modern Visual Studio (2017 or later), is to set the VCToolsInstallDir environment variable:
set VCToolsInstallDir=%temp_path%
That environment variable is the first thing clang will look for, so by pointing it at the base of our fake installation, it will find it right away.
Now, if you’re like me, you want to be able to completely isolate Visual Studio’s compiler and LLVM’s compiler, and you want to be able to call whichever one you want at any time and not have to worry about it. You don’t want to have to set environment variables, or have a “state” that you switch between by calling environment-contaminating batch files like vcvarsall.bat and the like..
So the way I like to wrap this whole clang handholding up is to have a clang++.bat file that does everything wrapped inside a setlocal so it doesn’t contaminate the wider execution environment. Mine looks like this:
@echo off
setlocal
set PATH=%path%;w:\apps\msvc\x64
set absurd=w:\temp\msvc\bin\HostX64\x64
IF NOT EXIST %absurd% mkdir %absurd%
set VCToolsInstallDir=w:\temp\msvc\
copy w:\apps\msvc\x64\link.exe %absurd%
set INCLUDE=w:\apps\winsdk\include\vc;w:\apps\winsdk\include\shared;w:\apps\winsdk\include\ucrt;w:\apps\winsdk\include\um
set LIB=w:\apps\winsdk\lib\vc;w:\apps\winsdk\lib\sys;w:\apps\winsdk\lib\ucrt
w:\apps\llvm\bin\clang++ %*
I have MSVC’s compiler in w:/apps/msvc/x64, CLANG’s compiler in w:/apps/llvm, and the Windows SDK / CRT in w:/apps/winsdk. Using this batch file, I can just invoke clang++ out of the blue, no environment setup necessary, and it will compile and link without setting any environment variables outside of the batch itself. Similarly, I have a cl.bat which does exactly the same thing, it’s just much simpler because it doesn’t need the handholding to find link.exe:
@echo off
setlocal
set INCLUDE=w:\apps\winsdk\include\vc;w:\apps\winsdk\include\shared;w:\apps\winsdk\include\ucrt;w:\apps\winsdk\include\um
set LIB=w:\apps\winsdk\lib\vc;w:\apps\winsdk\lib\sys;w:\apps\winsdk\lib\ucrt
w:\apps\msvc\x64\cl.exe %*
This allows for any build script, at any time, to build with both cl and clang++, without ever needing to worry about what environment variables may or may not be set.
For more information on my current projects, join the Molly Rocket Mailing List: