понедельник, 2 сентября 2019 г.

Как собирать С++ приложения в Windows C# кодом

Понадобилось тут собрать С++ Qmake приложение под Windows используя cl компилятор из под C# приложения. При этом хотелось не запускать это C# приложение из под VisualStudio comand prompt, а разрулить всё прямо в коде.

Обычно, для сборки приложения в консоли студийным компилятором, необходимо открыть VisualStudio command prompt и делать сборку там, либо вручную вызвать vcvars64.bat или vcvarsall.bat для настройки окружения, но  тут есть 2 проблемы: 1. Как найти этот bat файл, 2. Как его запустить внутри C# приложения так, что бы он правильно настроил environment variables.

Решение:
1. Найти все VisualStudio >= 2017 можно при помощи приложения vswhere, которое ставится с VisualStudio installer
2. Находим VisualStudio при помощи vswhere, находим vcvars64.bat, выполняем bat файл, забирая себе его environment в текстовом виде, парсим и применяем в своё приложение.

//Find vswhere path
var programFilesDir = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
var vsInstallationDir = Path.Combine(programFilesDir, @"Microsoft Visual Studio", "Installer");
var vsWherePath = Path.Combine(vsInstallationDir, "vswhere.exe");

//Find latest VisualStudio installation directory
var vsWhereResult = SystemProcess.Execute(vsWherePath, "-latest -property installationPath");
var vsInstallPath = vsWhereResult.outBuffer[0];

//Import VisualStudio environment
var vsEnvBatchFile = Path.Combine(vsInstallPath, @"VC\Auxiliary\Build\vcvars64.bat");
var vsEnvResult = SystemProcess.Execute("cmd", $"/C \"{vsEnvBatchFile}\" > nul 2>&1 && set");
Regex envVariableRegex = new Regex("^([^=]+)=(.*)");
foreach(var str in vsEnvResult.outBuffer) {
 var match = envVariableRegex.Match(str);
 if (match.Success) {
  System.Environment.SetEnvironmentVariable(match.Groups[1].Value, match.Groups[2].Value);
 }
}
В коде вообще нет ни каких проверок, потестил просто как proof of concept. SystemProcess - мой мини враппер над System.Diagnostics.Process, запускает процесс и возвращает массив строк stdout, stderr и exit code. По хорошему надо делать не совсем так, необходимо применить лишь diff переменных окружения, но в моей задаче это было не нужно. А дальше просто собираем своё Qmake приложение:
//run qmake
SystemProcess.Execute(@"C:\Qt\5.13.0\msvc2019_64_custom\bin\qmake.exe", proFileDir + " -spec win32-msvc \"CONFIG += release\"");
//run nmake
SystemProcess.Execute("nmake");