The processor I’m working with is a bit unusual, so I can’t just switch toolchains freely. I have to use ARM’s armclang. My old setup was very straightforward: fire up a VM, install Keil inside it, and run the entire build there. It worked, but it never felt particularly pleasant.
Then I suddenly remembered that I had bought CrossOver before, but had long since mentally thrown it in the trash. Some people in my chat group managed to get it working with Wine, so I figured I should be able to do the same. I dug up the license, and with GPT’s help, I actually got it running.
Installing Keil itself wasn’t a big problem, and compiling a single file worked fine. The trouble only started once I tried building a more complex project:
ninjaruns on macOS- The paths it generates and passes around are naturally Unix-style
- But the actual
armclang.exebeing invoked is a Windows program - And Windows programs only understand Windows-style paths
That means the arguments produced by the build system can’t just be passed straight into the compiler running inside CrossOver. There has to be a translation layer in the middle to convert macOS-style paths into Windows-style ones, or a lot of those arguments will simply stop working.
So I vibe-coded a wrapper for exactly this purpose: cx-winpath-wrap
Its job is pretty simple: intercept the arguments, identify the paths inside them, translate those paths into a format Windows can understand, and then pass everything on to the real ARM toolchain.
That solved the compilation problem, but there was still one big pitfall left: dependency files. Whether ninja decides to rebuild something often depends on the dependency information generated by the compiler. If that part of the pipeline is not handled correctly, you end up with a very annoying situation: the source files haven’t changed, but the build system thinks the dependencies are wrong, so it recompiles everything every time.
After quite a few rounds of trial and error with GPT (probably because it really isn’t very good at writing bash), that problem was finally solved too.
Once the path translation script was in place, the rest became fairly straightforward: create a few local scripts with the same names as the tools, and have them forward the calls to the Windows executables inside CrossOver.
For example, armclang can look like this:
#!/bin/bash
exec "$HOME/.local/bin/cx-winpath-wrap" \
'C:\users\crossover\AppData\Local\Keil_v5\ARM\ARMCLANG\bin\armclang.exe' \
"$@"
The other tools are handled the same way, such as:
armararmlinkfromelf
Once those wrapper scripts are ready, you can point toolchain.cmake at them directly:
set(CMAKE_C_COMPILER "$ENV{HOME}/.local/bin/armclang")
set(CMAKE_AR "$ENV{HOME}/.local/bin/armar")
set(CMAKE_LINKER "$ENV{HOME}/.local/bin/armlink")
set(FROMELF "$ENV{HOME}/.local/bin/fromelf")
At that point, the whole chain is wired together:
cmakegenerates the build rulesninjaruns on macOS- Local wrapper scripts intercept the compiler commands
- The
wrapscript handles path and dependency translation - The
armclangtoolchain inside CrossOver does the actual compiling and linking