From e14fcad965d6b539ba034e07fb5a9db24ac5bf93 Mon Sep 17 00:00:00 2001 From: Cyano Hao Date: Tue, 31 Dec 2024 09:36:46 +0800 Subject: [PATCH] Add support for running in WSL (#566) --- RedPandaIDE/compiler/compilermanager.cpp | 5 +- RedPandaIDE/debugger/debugger.cpp | 2 +- RedPandaIDE/utils.cpp | 49 +++++-- RedPandaIDE/utils.h | 2 +- tools/consolepauser/argparser.hpp | 4 + tools/consolepauser/main.windows.cpp | 168 ++++++++++++++++++++++- 6 files changed, 209 insertions(+), 21 deletions(-) diff --git a/RedPandaIDE/compiler/compilermanager.cpp b/RedPandaIDE/compiler/compilermanager.cpp index 76d4289bb..9fe17f331 100644 --- a/RedPandaIDE/compiler/compilermanager.cpp +++ b/RedPandaIDE/compiler/compilermanager.cpp @@ -248,7 +248,7 @@ void CompilerManager::run( } bool useCustomTerminal = pSettings->environment().useCustomTerminal(); ExecutableRunner * execRunner; - if (programHasConsole(filename)) { + if (!programIsWin32GuiApp(filename)) { QString consolePauserPath = getFilePath(pSettings->dirs().appLibexecDir(), CONSOLE_PAUSER); QStringList execArgs = {consolePauserPath}; if (redirectInput) { @@ -262,6 +262,9 @@ void CompilerManager::run( #ifdef Q_OS_WIN if (pSettings->executor().enableVirualTerminalSequence()) execArgs << "--enable-virtual-terminal-sequence"; + QString triplet = pSettings->compilerSets().defaultSet()->dumpMachine(); + if (triplet.contains("-linux-")) + execArgs << "--run-in-wsl"; QString sharedMemoryId = QUuid::createUuid().toString(); #else QString sharedMemoryId = "/r" + QUuid::createUuid().toString(QUuid::StringFormat::Id128); diff --git a/RedPandaIDE/debugger/debugger.cpp b/RedPandaIDE/debugger/debugger.cpp index 0da933afd..bd346af1a 100644 --- a/RedPandaIDE/debugger/debugger.cpp +++ b/RedPandaIDE/debugger/debugger.cpp @@ -2404,7 +2404,7 @@ void DebugTarget::run() #ifdef Q_OS_WIN process.setCreateProcessArgumentsModifier([this](QProcess::CreateProcessArguments * args){ - if (programHasConsole(mInferior)) { + if (!programIsWin32GuiApp(mInferior)) { args->flags |= CREATE_NEW_CONSOLE; args->flags &= ~CREATE_NO_WINDOW; } diff --git a/RedPandaIDE/utils.cpp b/RedPandaIDE/utils.cpp index 7c92e1bd6..57faa297d 100644 --- a/RedPandaIDE/utils.cpp +++ b/RedPandaIDE/utils.cpp @@ -147,30 +147,49 @@ FileType getFileType(const QString &filename) return FileType::Other; } -bool programHasConsole(const QString & filename) +bool programIsWin32GuiApp(const QString & filename) { #ifdef Q_OS_WIN bool result = false; HANDLE handle = CreateFileW(filename.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (handle != INVALID_HANDLE_VALUE) { - IMAGE_DOS_HEADER dos_header; - DWORD signature; - DWORD bytesread; - IMAGE_FILE_HEADER pe_header; - IMAGE_OPTIONAL_HEADER opt_header; - - ReadFile(handle, &dos_header, sizeof(dos_header), &bytesread, NULL); - SetFilePointer(handle, dos_header.e_lfanew, NULL, 0); - ReadFile(handle, &signature, sizeof(signature), &bytesread, NULL); - ReadFile(handle, &pe_header, sizeof(pe_header), &bytesread, NULL); - ReadFile(handle, &opt_header, sizeof(opt_header), &bytesread, NULL); - - result = (opt_header.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI); + result = [handle] { + IMAGE_DOS_HEADER dos_header; + DWORD signature; + DWORD bytesread; + IMAGE_FILE_HEADER pe_header; + IMAGE_OPTIONAL_HEADER opt_header; + + ReadFile(handle, &dos_header, sizeof(dos_header), &bytesread, NULL); + if (bytesread != sizeof(dos_header)) + return false; + if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) + return false; + + SetFilePointer(handle, dos_header.e_lfanew, NULL, 0); + ReadFile(handle, &signature, sizeof(signature), &bytesread, NULL); + if (bytesread != sizeof(signature)) + return false; + if (signature != IMAGE_NT_SIGNATURE) + return false; + + ReadFile(handle, &pe_header, sizeof(pe_header), &bytesread, NULL); + if (bytesread != sizeof(pe_header)) + return false; + + ReadFile(handle, &opt_header, sizeof(opt_header), &bytesread, NULL); + if (bytesread != sizeof(opt_header)) + return false; + if (opt_header.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && opt_header.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return false; + + return opt_header.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI; + } (); } CloseHandle(handle); return result; #else - return true; + return false; #endif } diff --git a/RedPandaIDE/utils.h b/RedPandaIDE/utils.h index 353bcc86d..5c8065489 100644 --- a/RedPandaIDE/utils.h +++ b/RedPandaIDE/utils.h @@ -149,7 +149,7 @@ constexpr bool isC_CPP_ASMSourceFile(FileType fileType) { || fileType == FileType::ATTASM || fileType == FileType::INTELASM; } -bool programHasConsole(const QString& filename); +bool programIsWin32GuiApp(const QString& filename); QString parseMacros(const QString& s); QString parseMacros(const QString& s, const QMap& variables); diff --git a/tools/consolepauser/argparser.hpp b/tools/consolepauser/argparser.hpp index 451e16052..7a3d5750d 100644 --- a/tools/consolepauser/argparser.hpp +++ b/tools/consolepauser/argparser.hpp @@ -71,6 +71,7 @@ struct ArgParser String redirectInput; bool pauseConsole = false; bool enableVirtualTerminalSeq = false; + bool runInWsl = false; }; inline static Args gArgs; @@ -136,6 +137,8 @@ struct ArgParser result.pauseConsole = true; } else if (MatchFlag(arg, "--enable-virtual-terminal-sequence")) { result.enableVirtualTerminalSeq = true; + } else if (MatchFlag(arg, "--run-in-wsl")) { + result.runInWsl = true; } else if (MatchOption(arg, "--shared-memory", valuePos)) { if (valuePos) { result.sharedMemory = valuePos; @@ -212,6 +215,7 @@ Internal options: --add-translation = --enable-virtual-terminal-sequence --redirect-input + --run-in-wsl --shared-memory )" ); diff --git a/tools/consolepauser/main.windows.cpp b/tools/consolepauser/main.windows.cpp index 521295e44..c6c78462f 100644 --- a/tools/consolepauser/main.windows.cpp +++ b/tools/consolepauser/main.windows.cpp @@ -23,13 +23,14 @@ // CRT #include #include +#include // Win32 #include #include #include #include -#include +#include // ours #include "argparser.hpp" @@ -62,6 +63,42 @@ using win32_filetime_duration = chrono::duration; #define EXIT_CREATE_PROCESS_FAILED -5 #define EXIT_ASSGIN_PROCESS_JOB_FAILED -6 +namespace WslApi +{ + BOOL (*pWslIsDistributionRegistered)( + PCWSTR distributionName + ) = nullptr; + HRESULT (*pWslRegisterDistribution)( + _In_ PCWSTR distributionName, + _In_ PCWSTR tarGzFilename + ) = nullptr; + HRESULT (*pWslLaunch)( + _In_ PCWSTR distributionName, + _In_opt_ PCWSTR command, + _In_ BOOL useCurrentWorkingDirectory, + _In_ HANDLE stdIn, + _In_ HANDLE stdOut, + _In_ HANDLE stdErr, + _Out_ HANDLE *process + ) = nullptr; + + bool Init() + { + // Microsoft say we should detect API set availability before dynamic loading, + // but we are targetting desktop only, simply load it, avoid dynamic loading too many APIs. + // (yes, API set detection API `IsApiSetImplemented` requires dynamic loading.) + HMODULE hModule = LoadLibraryW(L"api-ms-win-wsl-api-l1-1-0.dll"); + if (!hModule) + return false; + + // if an API set exists, the APIs are all available. further check is not necessary. + pWslIsDistributionRegistered = (decltype(pWslIsDistributionRegistered))GetProcAddress(hModule, "WslIsDistributionRegistered"); + pWslRegisterDistribution = (decltype(pWslRegisterDistribution))GetProcAddress(hModule, "WslRegisterDistribution"); + pWslLaunch = (decltype(pWslLaunch))GetProcAddress(hModule, "WslLaunch"); + return true; + } +}; + HANDLE hJob; bool enableJobControl = IsWindowsXPOrGreater(); @@ -229,6 +266,54 @@ wstring GetCommand(wstring_view prog, const vector &args, bool reI return result; } +wstring DosToWsl(wstring_view path, bool reInp) +{ + if (path.length() < 2 || path[1] != ':') { + PrintToStderr(L"Error: program path must be absolute DOS path\n"); + PauseExit(EXIT_WRONG_ARGUMENTS, reInp); + } + wchar_t drive = towlower(path[0]); + if (drive < 'a' || drive > 'z') { + PrintToStderr(L"Error: program path must be absolute DOS path\n"); + PauseExit(EXIT_WRONG_ARGUMENTS, reInp); + } + wstring result = { '/', 'm', 'n', 't', '/', drive }; + for (wchar_t ch : path.substr(2)) { + if (ch == '\\') + result.push_back('/'); + else + result.push_back(ch); + } + return result; +} + +wstring EscapeWslArgument(wstring_view arg) +{ + wstring result = {'\''}; + for (wchar_t ch : arg) { + if (ch == '\'') { + result.push_back('\''); // terminate single quoting + result.push_back('\\'); // escape next single quote + result.push_back('\''); // the single quote itself + result.push_back('\''); // re-start single quoting + } else { + result.push_back(ch); + } + } + result.push_back('\''); + return result; +} + +wstring GetWslCommand(wstring_view prog, const vector &args, bool reInp) +{ + wstring result = EscapeWslArgument(DosToWsl(prog, reInp)); + for (wstring_view arg : args) { + result.push_back(' '); + result.append(EscapeWslArgument(arg)); + } + return result; +} + win32_filetime_duration FiletimeToDuration(const FILETIME &ft) { return win32_filetime_duration(((int64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime); @@ -287,6 +372,75 @@ DWORD ExecuteCommand(wstring& command,bool reInp, LONGLONG &peakMemory, double & return result; } +DWORD ExecuteWslCommand(wstring &command, bool reInp, LONGLONG &peakMemory, double &cpuMilliSeconds) +{ + const wchar_t *wslDistro = L"redpanda-cpp-linux-runner-v0"; + const wchar_t *wslDistroName = L"Red Panda C++ Linux Runner V0"; + const wstring_view wslRootfsArchive = L"alpine-minirootfs.tar"; + + if (!WslApi::Init()) { + PrintToStderr(L"Error: WSL is not supported on this system\n"); + PauseExit(EXIT_CREATE_PROCESS_FAILED, reInp); + } + + HRESULT hr; + + if (!WslApi::pWslIsDistributionRegistered(wslDistro)) { + PrintToStderr(L"Info: %ls not found\n", wslDistroName); + wchar_t buffer[MAX_PATH]; + DWORD len = GetModuleFileNameW(nullptr, buffer, MAX_PATH); + if (len == 0) { + PrintWin32ApiError(L"GetModuleFileNameW"); + PauseExit(EXIT_CREATE_PROCESS_FAILED, reInp); + } + wstring path(buffer, len); + while (path.back() != '\\') + path.pop_back(); + for (wchar_t ch : wslRootfsArchive) + path.push_back(ch); + + PrintToStderr(L"Info: Try importing from archive: %ls\n", path.c_str()); + hr = WslApi::pWslRegisterDistribution(wslDistro, path.c_str()); + if (FAILED(hr)) { + PrintSplitLineToStderr(); + PrintToStderr(L"WslRegisterDistribution failed with error 0x%08lx\n", hr); + PrintToStderr(L"Did you enable WSL? Search \"Turn Windows features on or off\" in Start Menu and enable \"Windows Subsystem for Linux\"."); + PauseExit(EXIT_CREATE_PROCESS_FAILED, reInp); + } + } + + HANDLE hProcess; + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); + + hr = WslApi::pWslLaunch(wslDistro, command.c_str(), 1, hStdin, hStdout, hStderr, &hProcess); + if (FAILED(hr)) { + PrintToStderr(L"WslLaunch failed with error 0x%08lx\n", hr); + PauseExit(EXIT_CREATE_PROCESS_FAILED, reInp); + } + WaitForSingleObject(hProcess, INFINITE); // Wait for it to finish + + peakMemory = 0; + PROCESS_MEMORY_COUNTERS counter; + counter.cb = sizeof(counter); + if (GetProcessMemoryInfo(hProcess,&counter, + sizeof(counter))){ + peakMemory = counter.PeakPagefileUsage/1024; + } + FILETIME creationTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + if (GetProcessTimes(hProcess,&creationTime,&exitTime,&kernelTime,&userTime)) { + auto cpuDuration = FiletimeToDuration(kernelTime) + FiletimeToDuration(userTime); + cpuMilliSeconds = DurationToSeconds(cpuDuration) * 1000; + } + DWORD result = 0; + GetExitCodeProcess(hProcess, &result); + return result; +} + void EnableVtSequence() { DWORD mode; HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); @@ -324,7 +478,11 @@ int wmain(int argc, wchar_t** argv) { sa.bInheritHandle = FALSE; // Then build the to-run application command - wstring command = GetCommand(AP::gArgs.program, AP::gArgs.args, reInp); + wstring command; + if (AP::gArgs.runInWsl) + command = GetWslCommand(AP::gArgs.program, AP::gArgs.args, reInp); + else + command = GetCommand(AP::gArgs.program, AP::gArgs.args, reInp); if (enableJobControl) { hJob= CreateJobObject( &sa, NULL ); @@ -391,7 +549,11 @@ int wmain(int argc, wchar_t** argv) { LONGLONG peakMemory=0; double cpuMilliSeconds = 0; // Then execute said command - DWORD returnvalue = ExecuteCommand(command,reInp,peakMemory,cpuMilliSeconds); + DWORD returnvalue; + if (AP::gArgs.runInWsl) + returnvalue = ExecuteWslCommand(command,reInp,peakMemory,cpuMilliSeconds); + else + returnvalue = ExecuteCommand(command,reInp,peakMemory,cpuMilliSeconds); // Get ending timestamp LONGLONG endtime = GetClockTick();