FileCheckerTest.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // //
  3. // FileCheckerTest.cpp //
  4. // Copyright (C) Microsoft Corporation. All rights reserved. //
  5. // This file is distributed under the University of Illinois Open Source //
  6. // License. See LICENSE.TXT for details. //
  7. // //
  8. // Provides tests that are based on FileChecker. //
  9. // //
  10. ///////////////////////////////////////////////////////////////////////////////
  11. #ifndef UNICODE
  12. #define UNICODE
  13. #endif
  14. #include <memory>
  15. #include <vector>
  16. #include <string>
  17. #include <cassert>
  18. #include <algorithm>
  19. #include "dxc/Support/WinIncludes.h"
  20. #include "dxc/dxcapi.h"
  21. #ifdef _WIN32
  22. #include <atlfile.h>
  23. #endif
  24. #include "HLSLTestData.h"
  25. #include "HlslTestUtils.h"
  26. #include "DxcTestUtils.h"
  27. #include "llvm/Support/raw_os_ostream.h"
  28. #include "dxc/Support/Global.h"
  29. #include "dxc/Support/dxcapi.use.h"
  30. #include "dxc/Support/HLSLOptions.h"
  31. #include "dxc/Support/Unicode.h"
  32. #include "dxc/DxilContainer/DxilContainer.h"
  33. #include "D3DReflectionDumper.h"
  34. #include "d3d12shader.h"
  35. using namespace std;
  36. using namespace hlsl_test;
  37. static std::string strltrim(std::string value) {
  38. return value.erase(0, value.find_first_not_of(" \t\r\n"));
  39. }
  40. static std::string strrtrim(std::string value) {
  41. size_t last = value.find_last_not_of(" \t\r\n");
  42. return last == string::npos ? value : value.substr(0, last + 1);
  43. }
  44. static std::string strtrim(const std::string &value) {
  45. return strltrim(strrtrim(value));
  46. }
  47. static string trim(string value) {
  48. size_t leading = value.find_first_not_of(' ');
  49. if (leading != std::string::npos) {
  50. value.erase(0, leading);
  51. }
  52. size_t trailing = value.find_last_not_of(' ');
  53. if (leading != std::string::npos) {
  54. value.erase(trailing + 1);
  55. }
  56. return value;
  57. }
  58. FileRunCommandPart::FileRunCommandPart(const std::string &command, const std::string &arguments, LPCWSTR commandFileName) :
  59. Command(command), Arguments(arguments), CommandFileName(commandFileName) { }
  60. FileRunCommandPart::FileRunCommandPart(FileRunCommandPart && other) :
  61. Command(std::move(other.Command)),
  62. Arguments(std::move(other.Arguments)),
  63. CommandFileName(other.CommandFileName),
  64. RunResult(other.RunResult),
  65. StdOut(std::move(other.StdOut)),
  66. StdErr(std::move(other.StdErr)) { }
  67. void FileRunCommandPart::Run(const FileRunCommandPart *Prior) {
  68. bool isFileCheck =
  69. 0 == _stricmp(Command.c_str(), "FileCheck") ||
  70. 0 == _stricmp(Command.c_str(), "%FileCheck");
  71. bool isXFail = 0 == _stricmp(Command.c_str(), "xfail");
  72. // For now, propagate errors.
  73. if (Prior && Prior->RunResult && !isFileCheck && !isXFail) {
  74. StdErr = Prior->StdErr;
  75. RunResult = Prior->RunResult;
  76. return;
  77. }
  78. // We would add support for 'not' and 'llc' here.
  79. if (isFileCheck) {
  80. RunFileChecker(Prior);
  81. }
  82. else if (isXFail) {
  83. RunXFail(Prior);
  84. }
  85. else if (0 == _stricmp(Command.c_str(), "StdErrCheck")) {
  86. RunStdErrChecker(Prior);
  87. }
  88. else if (0 == _stricmp(Command.c_str(), "tee")) {
  89. RunTee(Prior);
  90. }
  91. else if (0 == _stricmp(Command.c_str(), "%dxc")) {
  92. RunDxc(Prior);
  93. }
  94. else if (0 == _stricmp(Command.c_str(), "%dxv")) {
  95. RunDxv(Prior);
  96. }
  97. else if (0 == _stricmp(Command.c_str(), "%opt")) {
  98. RunOpt(Prior);
  99. }
  100. else if (0 == _stricmp(Command.c_str(), "%D3DReflect")) {
  101. RunD3DReflect(Prior);
  102. }
  103. else {
  104. RunResult = 1;
  105. StdErr = "Unrecognized command ";
  106. StdErr += Command;
  107. }
  108. }
  109. void FileRunCommandPart::RunFileChecker(const FileRunCommandPart *Prior) {
  110. std::string args(strtrim(Arguments));
  111. if (args != "%s") {
  112. StdErr = "Only supported pattern is a plain input file";
  113. RunResult = 1;
  114. return;
  115. }
  116. if (!Prior) {
  117. StdErr = "Prior command required to generate stdin";
  118. RunResult = 1;
  119. return;
  120. }
  121. CW2A fileName(CommandFileName, CP_UTF8);
  122. FileCheckForTest t;
  123. t.CheckFilename = fileName;
  124. if (Prior->RunResult)
  125. t.InputForStdin = Prior->StdErr;
  126. else
  127. t.InputForStdin = Prior->StdOut;
  128. RunResult = t.Run();
  129. StdOut = t.test_outs;
  130. StdErr = t.test_errs;
  131. // Capture the input as well.
  132. if (RunResult != 0 && Prior != nullptr) {
  133. StdErr += "\n<full input to FileCheck>\n";
  134. StdErr += t.InputForStdin;
  135. }
  136. }
  137. void FileRunCommandPart::RunStdErrChecker(const FileRunCommandPart *Prior) {
  138. std::string args(strtrim(Arguments));
  139. if (args != "%s") {
  140. StdErr = "Only supported pattern is a plain input file";
  141. RunResult = 1;
  142. return;
  143. }
  144. if (!Prior) {
  145. StdErr = "Prior command required to generate stdin";
  146. RunResult = 1;
  147. return;
  148. }
  149. CW2A fileName(CommandFileName, CP_UTF8);
  150. FileCheckForTest t;
  151. t.CheckFilename = fileName;
  152. t.InputForStdin = Prior->StdErr;
  153. RunResult = t.Run();
  154. StdOut = t.test_outs;
  155. StdErr = t.test_errs;
  156. // Capture the input as well.
  157. if (RunResult != 0 && Prior != nullptr) {
  158. StdErr += "\n<full input to StdErrCheck>\n";
  159. StdErr += t.InputForStdin;
  160. }
  161. }
  162. void FileRunCommandPart::ReadOptsForDxc(hlsl::options::MainArgs &argStrings,
  163. hlsl::options::DxcOpts &Opts) {
  164. std::string args(strtrim(Arguments));
  165. const char *inputPos = strstr(args.c_str(), "%s");
  166. if (inputPos == nullptr) {
  167. StdErr = "Only supported pattern includes input file as argument";
  168. RunResult = 1;
  169. return;
  170. }
  171. args.erase(inputPos - args.c_str(), strlen("%s"));
  172. llvm::StringRef argsRef = args;
  173. llvm::SmallVector<llvm::StringRef, 8> splitArgs;
  174. argsRef.split(splitArgs, " ");
  175. argStrings = hlsl::options::MainArgs(splitArgs);
  176. std::string errorString;
  177. llvm::raw_string_ostream errorStream(errorString);
  178. RunResult = ReadDxcOpts(hlsl::options::getHlslOptTable(), /*flagsToInclude*/ 0,
  179. argStrings, Opts, errorStream);
  180. errorStream.flush();
  181. if (RunResult) {
  182. StdErr = errorString;
  183. }
  184. }
  185. void FileRunCommandPart::RunDxc(const FileRunCommandPart *Prior) {
  186. // Support piping stdin from prior if needed.
  187. UNREFERENCED_PARAMETER(Prior);
  188. hlsl::options::MainArgs args;
  189. hlsl::options::DxcOpts opts;
  190. ReadOptsForDxc(args, opts);
  191. std::wstring entry =
  192. Unicode::UTF8ToUTF16StringOrThrow(opts.EntryPoint.str().c_str());
  193. std::wstring profile =
  194. Unicode::UTF8ToUTF16StringOrThrow(opts.TargetProfile.str().c_str());
  195. std::vector<LPCWSTR> flags;
  196. if (opts.CodeGenHighLevel) {
  197. flags.push_back(L"-fcgl");
  198. }
  199. std::vector<std::wstring> argWStrings;
  200. CopyArgsToWStrings(opts.Args, hlsl::options::CoreOption, argWStrings);
  201. for (const std::wstring &a : argWStrings)
  202. flags.push_back(a.data());
  203. CComPtr<IDxcLibrary> pLibrary;
  204. CComPtr<IDxcCompiler> pCompiler;
  205. CComPtr<IDxcOperationResult> pResult;
  206. CComPtr<IDxcBlobEncoding> pSource;
  207. CComPtr<IDxcBlobEncoding> pDisassembly;
  208. CComPtr<IDxcBlob> pCompiledBlob;
  209. CComPtr<IDxcIncludeHandler> pIncludeHandler;
  210. HRESULT resultStatus;
  211. if (RunResult) // opt parsing already failed
  212. return;
  213. IFT(DllSupport->CreateInstance(CLSID_DxcLibrary, &pLibrary));
  214. IFT(pLibrary->CreateBlobFromFile(CommandFileName, nullptr, &pSource));
  215. IFT(pLibrary->CreateIncludeHandler(&pIncludeHandler));
  216. IFT(DllSupport->CreateInstance(CLSID_DxcCompiler, &pCompiler));
  217. IFT(pCompiler->Compile(pSource, CommandFileName, entry.c_str(), profile.c_str(),
  218. flags.data(), flags.size(), nullptr, 0, pIncludeHandler, &pResult));
  219. IFT(pResult->GetStatus(&resultStatus));
  220. if (SUCCEEDED(resultStatus)) {
  221. IFT(pResult->GetResult(&pCompiledBlob));
  222. if (!opts.AstDump) {
  223. IFT(pCompiler->Disassemble(pCompiledBlob, &pDisassembly));
  224. StdOut = BlobToUtf8(pDisassembly);
  225. } else {
  226. StdOut = BlobToUtf8(pCompiledBlob);
  227. }
  228. CComPtr<IDxcBlobEncoding> pStdErr;
  229. IFT(pResult->GetErrorBuffer(&pStdErr));
  230. StdErr = BlobToUtf8(pStdErr);
  231. RunResult = 0;
  232. }
  233. else {
  234. IFT(pResult->GetErrorBuffer(&pDisassembly));
  235. StdErr = BlobToUtf8(pDisassembly);
  236. RunResult = resultStatus;
  237. }
  238. OpResult = pResult;
  239. }
  240. void FileRunCommandPart::RunDxv(const FileRunCommandPart *Prior) {
  241. std::string args(strtrim(Arguments));
  242. const char *inputPos = strstr(args.c_str(), "%s");
  243. if (inputPos == nullptr) {
  244. StdErr = "Only supported pattern includes input file as argument";
  245. RunResult = 1;
  246. return;
  247. }
  248. args.erase(inputPos - args.c_str(), strlen("%s"));
  249. llvm::StringRef argsRef = args;
  250. llvm::SmallVector<llvm::StringRef, 8> splitArgs;
  251. argsRef.split(splitArgs, " ");
  252. IFTMSG(splitArgs.size()==1, "wrong arg num for dxv");
  253. CComPtr<IDxcLibrary> pLibrary;
  254. CComPtr<IDxcAssembler> pAssembler;
  255. CComPtr<IDxcValidator> pValidator;
  256. CComPtr<IDxcOperationResult> pResult;
  257. CComPtr<IDxcBlobEncoding> pSource;
  258. CComPtr<IDxcBlob> pContainerBlob;
  259. HRESULT resultStatus;
  260. IFT(DllSupport->CreateInstance(CLSID_DxcLibrary, &pLibrary));
  261. IFT(pLibrary->CreateBlobFromFile(CommandFileName, nullptr, &pSource));
  262. IFT(DllSupport->CreateInstance(CLSID_DxcAssembler, &pAssembler));
  263. IFT(pAssembler->AssembleToContainer(pSource, &pResult));
  264. IFT(pResult->GetStatus(&resultStatus));
  265. if (FAILED(resultStatus)) {
  266. CComPtr<IDxcBlobEncoding> pAssembleBlob;
  267. IFT(pResult->GetErrorBuffer(&pAssembleBlob));
  268. StdErr = BlobToUtf8(pAssembleBlob);
  269. RunResult = resultStatus;
  270. return;
  271. }
  272. IFT(pResult->GetResult(&pContainerBlob));
  273. IFT(DllSupport->CreateInstance(CLSID_DxcValidator, &pValidator));
  274. CComPtr<IDxcOperationResult> pValidationResult;
  275. IFT(pValidator->Validate(pContainerBlob, DxcValidatorFlags_InPlaceEdit,
  276. &pValidationResult));
  277. IFT(pValidationResult->GetStatus(&resultStatus));
  278. if (resultStatus) {
  279. CComPtr<IDxcBlobEncoding> pValidateBlob;
  280. IFT(pValidationResult->GetErrorBuffer(&pValidateBlob));
  281. StdOut = BlobToUtf8(pValidateBlob);
  282. }
  283. RunResult = 0;
  284. }
  285. void FileRunCommandPart::RunOpt(const FileRunCommandPart *Prior) {
  286. std::string args(strtrim(Arguments));
  287. const char *inputPos = strstr(args.c_str(), "%s");
  288. if (inputPos == nullptr && Prior == nullptr) {
  289. StdErr = "Only supported patterns are input file as argument or prior "
  290. "command with disassembly";
  291. RunResult = 1;
  292. return;
  293. }
  294. CComPtr<IDxcLibrary> pLibrary;
  295. CComPtr<IDxcOptimizer> pOptimizer;
  296. CComPtr<IDxcBlobEncoding> pSource;
  297. CComPtr<IDxcBlobEncoding> pOutputText;
  298. CComPtr<IDxcBlob> pOutputModule;
  299. IFT(DllSupport->CreateInstance(CLSID_DxcLibrary, &pLibrary));
  300. IFT(DllSupport->CreateInstance(CLSID_DxcOptimizer, &pOptimizer));
  301. if (inputPos != nullptr) {
  302. args.erase(inputPos - args.c_str(), strlen("%s"));
  303. IFT(pLibrary->CreateBlobFromFile(CommandFileName, nullptr, &pSource));
  304. }
  305. else {
  306. assert(Prior != nullptr && "else early check should have returned");
  307. CComPtr<IDxcAssembler> pAssembler;
  308. IFT(DllSupport->CreateInstance(CLSID_DxcAssembler, &pAssembler));
  309. IFT(pLibrary->CreateBlobWithEncodingFromPinned(
  310. Prior->StdOut.c_str(), Prior->StdOut.size(), CP_UTF8,
  311. &pSource));
  312. }
  313. args = trim(args);
  314. llvm::StringRef argsRef = args;
  315. llvm::SmallVector<llvm::StringRef, 8> splitArgs;
  316. argsRef.split(splitArgs, " ");
  317. std::vector<LPCWSTR> options;
  318. std::vector<std::wstring> optionStrings;
  319. for (llvm::StringRef S : splitArgs) {
  320. optionStrings.push_back(
  321. Unicode::UTF8ToUTF16StringOrThrow(trim(S.str()).c_str()));
  322. }
  323. // Add the options outside the above loop in case the vector is resized.
  324. for (const std::wstring& str : optionStrings)
  325. options.push_back(str.c_str());
  326. IFT(pOptimizer->RunOptimizer(pSource, options.data(), options.size(),
  327. &pOutputModule, &pOutputText));
  328. StdOut = BlobToUtf8(pOutputText);
  329. RunResult = 0;
  330. }
  331. void FileRunCommandPart::RunD3DReflect(const FileRunCommandPart *Prior) {
  332. std::string args(strtrim(Arguments));
  333. if (args != "%s") {
  334. StdErr = "Only supported pattern is a plain input file";
  335. RunResult = 1;
  336. return;
  337. }
  338. if (!Prior) {
  339. StdErr = "Prior command required to generate stdin";
  340. RunResult = 1;
  341. return;
  342. }
  343. CComPtr<IDxcLibrary> pLibrary;
  344. CComPtr<IDxcBlobEncoding> pSource;
  345. CComPtr<IDxcAssembler> pAssembler;
  346. CComPtr<IDxcOperationResult> pResult;
  347. CComPtr<ID3D12ShaderReflection> pShaderReflection;
  348. CComPtr<ID3D12LibraryReflection> pLibraryReflection;
  349. CComPtr<IDxcContainerReflection> containerReflection;
  350. uint32_t partCount;
  351. CComPtr<IDxcBlob> pContainerBlob;
  352. HRESULT resultStatus;
  353. bool blobFound = false;
  354. std::ostringstream ss;
  355. D3DReflectionDumper dumper(ss);
  356. IFT(DllSupport->CreateInstance(CLSID_DxcLibrary, &pLibrary));
  357. IFT(DllSupport->CreateInstance(CLSID_DxcAssembler, &pAssembler));
  358. IFT(pLibrary->CreateBlobWithEncodingFromPinned(
  359. (LPBYTE)Prior->StdOut.c_str(), Prior->StdOut.size(), CP_UTF8,
  360. &pSource));
  361. IFT(pAssembler->AssembleToContainer(pSource, &pResult));
  362. IFT(pResult->GetStatus(&resultStatus));
  363. if (FAILED(resultStatus)) {
  364. CComPtr<IDxcBlobEncoding> pAssembleBlob;
  365. IFT(pResult->GetErrorBuffer(&pAssembleBlob));
  366. StdErr = BlobToUtf8(pAssembleBlob);
  367. RunResult = resultStatus;
  368. return;
  369. }
  370. IFT(pResult->GetResult(&pContainerBlob));
  371. VERIFY_SUCCEEDED(DllSupport->CreateInstance(CLSID_DxcContainerReflection, &containerReflection));
  372. VERIFY_SUCCEEDED(containerReflection->Load(pContainerBlob));
  373. VERIFY_SUCCEEDED(containerReflection->GetPartCount(&partCount));
  374. for (uint32_t i = 0; i < partCount; ++i) {
  375. uint32_t kind;
  376. VERIFY_SUCCEEDED(containerReflection->GetPartKind(i, &kind));
  377. if (kind == (uint32_t)hlsl::DxilFourCC::DFCC_DXIL) {
  378. blobFound = true;
  379. CComPtr<IDxcBlob> pPart;
  380. IFT(containerReflection->GetPartContent(i, &pPart));
  381. const hlsl::DxilProgramHeader *pProgramHeader =
  382. reinterpret_cast<const hlsl::DxilProgramHeader*>(pPart->GetBufferPointer());
  383. VERIFY_IS_TRUE(IsValidDxilProgramHeader(pProgramHeader, (uint32_t)pPart->GetBufferSize()));
  384. hlsl::DXIL::ShaderKind SK = hlsl::GetVersionShaderType(pProgramHeader->ProgramVersion);
  385. if (SK == hlsl::DXIL::ShaderKind::Library)
  386. VERIFY_SUCCEEDED(containerReflection->GetPartReflection(i, IID_PPV_ARGS(&pLibraryReflection)));
  387. else
  388. VERIFY_SUCCEEDED(containerReflection->GetPartReflection(i, IID_PPV_ARGS(&pShaderReflection)));
  389. break;
  390. }
  391. }
  392. if (!blobFound) {
  393. StdErr = "Unable to find DXIL part";
  394. RunResult = 1;
  395. return;
  396. } else if (pShaderReflection) {
  397. dumper.Dump(pShaderReflection);
  398. } else if (pLibraryReflection) {
  399. dumper.Dump(pLibraryReflection);
  400. }
  401. ss.flush();
  402. StdOut = ss.str();
  403. RunResult = 0;
  404. }
  405. void FileRunCommandPart::RunTee(const FileRunCommandPart *Prior) {
  406. if (Prior == nullptr) {
  407. StdErr = "tee requires a prior command";
  408. RunResult = 1;
  409. return;
  410. }
  411. // Ignore commands for now - simply log out through test framework.
  412. {
  413. CA2W outWide(Prior->StdOut.c_str(), CP_UTF8);
  414. WEX::Logging::Log::Comment(outWide.m_psz);
  415. }
  416. if (!Prior->StdErr.empty()) {
  417. CA2W errWide(Prior->StdErr.c_str(), CP_UTF8);
  418. WEX::Logging::Log::Comment(L"<stderr>");
  419. WEX::Logging::Log::Comment(errWide.m_psz);
  420. }
  421. StdErr = Prior->StdErr;
  422. StdOut = Prior->StdOut;
  423. RunResult = Prior->RunResult;
  424. }
  425. void FileRunCommandPart::RunXFail(const FileRunCommandPart *Prior) {
  426. if (Prior == nullptr) {
  427. StdErr = "XFail requires a prior command";
  428. RunResult = 1;
  429. return;
  430. }
  431. if (Prior->RunResult == 0) {
  432. StdErr = "XFail expected a failure from previous command";
  433. RunResult = 1;
  434. } else {
  435. RunResult = 0;
  436. }
  437. }
  438. class FileRunTestResultImpl : public FileRunTestResult {
  439. dxc::DxcDllSupport &m_support;
  440. void RunFileCheckFromCommands(LPCSTR commands, LPCWSTR fileName) {
  441. std::vector<FileRunCommandPart> parts;
  442. ParseCommandParts(commands, fileName, parts);
  443. FileRunCommandPart *prior = nullptr;
  444. for (FileRunCommandPart & part : parts) {
  445. part.DllSupport = &m_support;
  446. part.Run(prior);
  447. prior = &part;
  448. }
  449. if (prior == nullptr) {
  450. this->RunResult = 1;
  451. this->ErrorMessage = "FileCheck found no commands to run";
  452. }
  453. else {
  454. this->RunResult = prior->RunResult;
  455. this->ErrorMessage = prior->StdErr;
  456. }
  457. }
  458. public:
  459. FileRunTestResultImpl(dxc::DxcDllSupport &support) : m_support(support) {}
  460. void RunFileCheckFromFileCommands(LPCWSTR fileName) {
  461. // Assume UTF-8 files.
  462. std::string commands(GetFirstLine(fileName));
  463. return RunFileCheckFromCommands(commands.c_str(), fileName);
  464. }
  465. };
  466. FileRunTestResult FileRunTestResult::RunFromFileCommands(LPCWSTR fileName) {
  467. dxc::DxcDllSupport dllSupport;
  468. IFT(dllSupport.Initialize());
  469. FileRunTestResultImpl result(dllSupport);
  470. result.RunFileCheckFromFileCommands(fileName);
  471. return result;
  472. }
  473. FileRunTestResult FileRunTestResult::RunFromFileCommands(LPCWSTR fileName, dxc::DxcDllSupport &dllSupport) {
  474. FileRunTestResultImpl result(dllSupport);
  475. result.RunFileCheckFromFileCommands(fileName);
  476. return result;
  477. }
  478. void ParseCommandParts(LPCSTR commands, LPCWSTR fileName,
  479. std::vector<FileRunCommandPart> &parts) {
  480. // Barely enough parsing here.
  481. commands = strstr(commands, "RUN: ");
  482. if (!commands) {
  483. return;
  484. }
  485. commands += strlen("RUN: ");
  486. LPCSTR endCommands = strchr(commands, '\0');
  487. while (commands != endCommands) {
  488. LPCSTR nextStart;
  489. LPCSTR thisEnd = strchr(commands, '|');
  490. if (!thisEnd) {
  491. nextStart = thisEnd = endCommands;
  492. } else {
  493. nextStart = thisEnd + 2;
  494. }
  495. LPCSTR commandEnd = strchr(commands, ' ');
  496. if (!commandEnd)
  497. commandEnd = endCommands;
  498. parts.emplace_back(std::string(commands, commandEnd),
  499. std::string(commandEnd, thisEnd), fileName);
  500. commands = nextStart;
  501. }
  502. }
  503. void ParseCommandPartsFromFile(LPCWSTR fileName,
  504. std::vector<FileRunCommandPart> &parts) {
  505. // Assume UTF-8 files.
  506. std::string commands(GetFirstLine(fileName));
  507. ParseCommandParts(commands.c_str(), fileName, parts);
  508. }