It is a fairly common practice to compile Windows application on Linux build servers. However, this is usually done through an approach called cross-compiling. The essence of this approach is using a compiler for Windows applications, but the compiler itself is a Linux application. Usually, the compiler used for this is MinGW (or MinGW-w64 these days), a GCC implementation for Windows.

This works great when porting traditional Unix applications to Windows, since it meshes nicely with the traditional build system on Unix-like systems. But it is rather poor for standalone single .exe applications, which are more common in the Windows world. MinGW has a few DLLs that are needed to run the applications it compiles, and that ruins the single executable experience.

The traditional way to build these simple applications in the Windows world is with the Microsoft compilers, usually in the form of Visual C++. These compilers are fairly nice, but they have one problem: they do not exist as cross compilers. (Well, they can cross compile between different processors, but the compilers themselves will only run on Windows.) What do we do then? Do we resign ourselves into not having single executable applications, or do give up and buy a Windows build machine?

We don’t have to do either. Enter wine, a compatibility shim that allows Windows applications to run on Linux. We can use it to run many Windows applications on Linux, albeit with potentially reduced functionality.

So I went ahead and copied the Visual C++ compiler to a Linux machine with Wine installed. After some trial and error, I found that the 32-bit compiler binaries work fine on Wine, save one flaw: the linker, link.exe, cannot generate .pdb files. So we simply have to sacrifice these files, which is fine for my use on my build server. My only goal here is to automatically compile binaries for every single commit I push to GitHub.

I only tested using the basic Microsoft compiler cl.exe, and Microsoft version of make, nmake.exe. Your mileage may vary if you need to use msbuild.exe. I suspect it probably work, but I have not tried it.

To set up this environment, first install Wine and winetricks. (Google for instructions for your system.)

You then run these two commands:

WINEPREFIX=~/wine-vc14 WINEARCH=win32 wine wineboot
WINEPREFIX=~/wine-vc14 WINEARCH=win32 winetricks vcrun2015

You can of course change WINEPREFIX to whatever suits you.

You will also need to copy your Visual C++ compilers and your Windows SDK into Wine:

  • From C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC on your Windows machine, copy bin, include, lib into $WINEPREFIX/drive_c/VC14.
  • From C:\Program Files (x86)\Windows Kits\10 on Windows, copy bin to $WINEPREFIX/drive_c/VC14/winsdk/bin.
  • From C:\Program Files (x86)\Windows Kits\10\Include, pick a directory, and copy its contents into $WINEPREFIX/drive_c/VC14/winsdk/include.
  • From C:\Program Files (x86)\Windows Kits\10\Lib, pick the same directory as include, and merge the contents of the um and ucrt directories into $WINEPREFIX/drive_c/VC14/winsdk/lib.

You should adapt the paths based on which version of Visual C++ you are using. You can also rearrange the stuff on the Linux side if you wish to change my build script.

Here’s the bash script that I use to run batch files that build my applications: (save it in $WINEPREFIX or change the path finding logic)

#!/bin/bash
SCRIPT="$(mktemp --suffix=.bat)"
cleanup() {
    rm -f "$SCRIPT"
}
trap cleanup EXIT

cat > "$SCRIPT" <<EOF
@echo off
set VC14ROOT=C:\VC14
EOF

if [ "$1" = -64 ]; then
    cat >> "$SCRIPT" <<EOF
set PATH=%VC14ROOT%\bin\x86_amd64;%VC14ROOT%\bin;%VC14ROOT%\winsdk\bin\x86;%PATH%
set INCLUDE=%VC14ROOT%\include;%VC14ROOT%\winsdk\include\shared;%VC14ROOT%\winsdk\include\ucrt;%VC14ROOT%\winsdk\include\um
set LIB=%VC14ROOT%\lib\amd64;%VC14ROOT%\winsdk\lib\x64
EOF
    shift
else
    cat >> "$SCRIPT" <<EOF
set PATH=%VC14ROOT%\bin;%VC14ROOT%\winsdk\bin\x86;%PATH%
set INCLUDE=%VC14ROOT%\include;%VC14ROOT%\winsdk\include\shared;%VC14ROOT%\winsdk\include\ucrt;%VC14ROOT%\winsdk\include\um
set LIB=%VC14ROOT%\lib;%VC14ROOT%\winsdk\lib\x86
EOF
fi

echo echo on >> "$SCRIPT"
cat "$@" >> "$SCRIPT"

WINEPREFIX="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" WINEARCH=win32 wine cmd /c "Z:$SCRIPT"

You can pass the batch files to build your application as command line arguments, or through stdin.

An example use with stdin and a heredoc could be:

~/.wine-vc14/run <<EOF
nmake
EOF

Here’s an example of this setup in action on Jenkins, the output for which I have attached just in case it somehow vanishes:

[workspace] $ /bin/sh -xe /tmp/jenkins3918947166186784406.sh
+ rm -rf dist build
+ /home/jenkins/wine-vc14/run

Z:\home\jenkins\jenkins\jobs\MusicKeyboard\workspace>nmake NOPDB=1 

Microsoft (R) Program Maintenance Utility Version 14.00.23506.0
Copyright (C) Microsoft Corporation.  All rights reserved.

    rc /nologo /iinclude /fobuild\Release\keyboard.res keyboard.rc
    cl /nologo /c /O1 /Iinclude /W4 /DWIN32_LEAN_AND_MEAN /DWINVER=0x0601 /D_WIN32_WINNT=0x0601 /DUNICODE /D_UNICODE /Fobuild\Release\ /Fdbuild\Release\ src\Keyboard.cpp src\MainWindow.cpp src\PianoControl.cpp src\Window.cpp 
Keyboard.cpp
MainWindow.cpp
src\MainWindow.cpp(313): warning C4458: declaration of 'piano' hides class member
include\MainWindow.hpp(104): note: see declaration of 'MainWindow::piano'
src\MainWindow.cpp(650): warning C4800: 'UINT': forcing value to bool 'true' or 'false' (performance warning)
PianoControl.cpp
src\PianoControl.cpp(85): warning C4458: declaration of 'octaves' hides class member
include\PianoControl.hpp(99): note: see declaration of 'PianoControl::octaves'
Window.cpp
Generating Code...
    cl /nologo /c /O1 /Iinclude /W4 /DWIN32_LEAN_AND_MEAN /DWINVER=0x0601 /D_WIN32_WINNT=0x0601 /DUNICODE /D_UNICODE /Fobuild\Release\ /Fdbuild\Release\ src\midifile.c 
midifile.c
    link /nologo /out:dist\Release\Keyboard.exe /subsystem:windows /incremental:no /opt:REF build\Release\Keyboard.obj build\Release\MainWindow.obj build\Release\PianoControl.obj build\Release\Window.obj build\Release\midifile.obj build\Release\keyboard.res

As you can see, cl.exe and nmake.exe work just as well as they do Windows for basic compiling tasks. Note that this does not mean we can run the actual Visual Studio IDE on Wine, as we are only using the command line compiler component.