cmQtAutoGeneratorRcc.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmQtAutoGen.h"
  4. #include "cmQtAutoGeneratorRcc.h"
  5. #include "cmAlgorithms.h"
  6. #include "cmCryptoHash.h"
  7. #include "cmFileLockResult.h"
  8. #include "cmMakefile.h"
  9. #include "cmSystemTools.h"
  10. #include "cmUVHandlePtr.h"
  11. #include <functional>
  12. // -- Class methods
  13. cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc()
  14. : MultiConfig_(false)
  15. , SettingsChanged_(false)
  16. , Stage_(StageT::SETTINGS_READ)
  17. , Error_(false)
  18. , Generate_(false)
  19. , BuildFileChanged_(false)
  20. {
  21. // Initialize libuv asynchronous iteration request
  22. UVRequest().init(*UVLoop(), &cmQtAutoGeneratorRcc::UVPollStage, this);
  23. }
  24. cmQtAutoGeneratorRcc::~cmQtAutoGeneratorRcc()
  25. {
  26. }
  27. bool cmQtAutoGeneratorRcc::Init(cmMakefile* makefile)
  28. {
  29. // -- Utility lambdas
  30. auto InfoGet = [makefile](std::string const& key) {
  31. return makefile->GetSafeDefinition(key);
  32. };
  33. auto InfoGetList =
  34. [makefile](std::string const& key) -> std::vector<std::string> {
  35. std::vector<std::string> list;
  36. cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
  37. return list;
  38. };
  39. auto InfoGetConfig = [makefile,
  40. this](std::string const& key) -> std::string {
  41. const char* valueConf = nullptr;
  42. {
  43. std::string keyConf = key;
  44. keyConf += '_';
  45. keyConf += InfoConfig();
  46. valueConf = makefile->GetDefinition(keyConf);
  47. }
  48. if (valueConf == nullptr) {
  49. valueConf = makefile->GetSafeDefinition(key);
  50. }
  51. return std::string(valueConf);
  52. };
  53. auto InfoGetConfigList =
  54. [&InfoGetConfig](std::string const& key) -> std::vector<std::string> {
  55. std::vector<std::string> list;
  56. cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
  57. return list;
  58. };
  59. // -- Read info file
  60. if (!makefile->ReadListFile(InfoFile().c_str())) {
  61. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "File processing failed");
  62. return false;
  63. }
  64. // - Configurations
  65. MultiConfig_ = makefile->IsOn("ARCC_MULTI_CONFIG");
  66. // - Directories
  67. AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
  68. if (AutogenBuildDir_.empty()) {
  69. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Build directory empty");
  70. return false;
  71. }
  72. IncludeDir_ = InfoGetConfig("ARCC_INCLUDE_DIR");
  73. if (IncludeDir_.empty()) {
  74. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Include directory empty");
  75. return false;
  76. }
  77. // - Rcc executable
  78. RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
  79. RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
  80. // - Job
  81. LockFile_ = InfoGet("ARCC_LOCK_FILE");
  82. QrcFile_ = InfoGet("ARCC_SOURCE");
  83. QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_);
  84. QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_);
  85. RccPathChecksum_ = InfoGet("ARCC_OUTPUT_CHECKSUM");
  86. RccFileName_ = InfoGet("ARCC_OUTPUT_NAME");
  87. Options_ = InfoGetConfigList("ARCC_OPTIONS");
  88. Inputs_ = InfoGetList("ARCC_INPUTS");
  89. // - Settings file
  90. SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
  91. // - Validity checks
  92. if (LockFile_.empty()) {
  93. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Lock file name missing");
  94. return false;
  95. }
  96. if (SettingsFile_.empty()) {
  97. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Settings file name missing");
  98. return false;
  99. }
  100. if (AutogenBuildDir_.empty()) {
  101. Log().ErrorFile(GeneratorT::RCC, InfoFile(),
  102. "Autogen build directory missing");
  103. return false;
  104. }
  105. if (RccExecutable_.empty()) {
  106. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc executable missing");
  107. return false;
  108. }
  109. if (QrcFile_.empty()) {
  110. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc input file missing");
  111. return false;
  112. }
  113. if (RccFileName_.empty()) {
  114. Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc output file missing");
  115. return false;
  116. }
  117. // Init derived information
  118. // ------------------------
  119. RccFilePublic_ = AutogenBuildDir_;
  120. RccFilePublic_ += '/';
  121. RccFilePublic_ += RccPathChecksum_;
  122. RccFilePublic_ += '/';
  123. RccFilePublic_ += RccFileName_;
  124. // Compute rcc output file name
  125. if (IsMultiConfig()) {
  126. RccFileOutput_ = AutogenBuildDir_;
  127. RccFileOutput_ += '/';
  128. RccFileOutput_ += IncludeDir_;
  129. RccFileOutput_ += '/';
  130. RccFileOutput_ += MultiConfigOutput();
  131. } else {
  132. RccFileOutput_ = RccFilePublic_;
  133. }
  134. return true;
  135. }
  136. bool cmQtAutoGeneratorRcc::Process()
  137. {
  138. // Run libuv event loop
  139. UVRequest().send();
  140. if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
  141. if (Error_) {
  142. return false;
  143. }
  144. } else {
  145. return false;
  146. }
  147. return true;
  148. }
  149. void cmQtAutoGeneratorRcc::UVPollStage(uv_async_t* handle)
  150. {
  151. reinterpret_cast<cmQtAutoGeneratorRcc*>(handle->data)->PollStage();
  152. }
  153. void cmQtAutoGeneratorRcc::PollStage()
  154. {
  155. switch (Stage_) {
  156. // -- Initialize
  157. case StageT::SETTINGS_READ:
  158. if (SettingsFileRead()) {
  159. SetStage(StageT::TEST_QRC_RCC_FILES);
  160. } else {
  161. SetStage(StageT::FINISH);
  162. }
  163. break;
  164. // -- Change detection
  165. case StageT::TEST_QRC_RCC_FILES:
  166. if (TestQrcRccFiles()) {
  167. SetStage(StageT::GENERATE);
  168. } else {
  169. SetStage(StageT::TEST_RESOURCES_READ);
  170. }
  171. break;
  172. case StageT::TEST_RESOURCES_READ:
  173. if (TestResourcesRead()) {
  174. SetStage(StageT::TEST_RESOURCES);
  175. }
  176. break;
  177. case StageT::TEST_RESOURCES:
  178. if (TestResources()) {
  179. SetStage(StageT::GENERATE);
  180. } else {
  181. SetStage(StageT::TEST_INFO_FILE);
  182. }
  183. break;
  184. case StageT::TEST_INFO_FILE:
  185. TestInfoFile();
  186. SetStage(StageT::GENERATE_WRAPPER);
  187. break;
  188. // -- Generation
  189. case StageT::GENERATE:
  190. GenerateParentDir();
  191. SetStage(StageT::GENERATE_RCC);
  192. break;
  193. case StageT::GENERATE_RCC:
  194. if (GenerateRcc()) {
  195. SetStage(StageT::GENERATE_WRAPPER);
  196. }
  197. break;
  198. case StageT::GENERATE_WRAPPER:
  199. GenerateWrapper();
  200. SetStage(StageT::SETTINGS_WRITE);
  201. break;
  202. // -- Finalize
  203. case StageT::SETTINGS_WRITE:
  204. SettingsFileWrite();
  205. SetStage(StageT::FINISH);
  206. break;
  207. case StageT::FINISH:
  208. // Clear all libuv handles
  209. UVRequest().reset();
  210. // Set highest END stage manually
  211. Stage_ = StageT::END;
  212. break;
  213. case StageT::END:
  214. break;
  215. }
  216. }
  217. void cmQtAutoGeneratorRcc::SetStage(StageT stage)
  218. {
  219. if (Error_) {
  220. stage = StageT::FINISH;
  221. }
  222. // Only allow to increase the stage
  223. if (Stage_ < stage) {
  224. Stage_ = stage;
  225. UVRequest().send();
  226. }
  227. }
  228. std::string cmQtAutoGeneratorRcc::MultiConfigOutput() const
  229. {
  230. static std::string const suffix = "_CMAKE_";
  231. std::string res;
  232. res += RccPathChecksum_;
  233. res += '/';
  234. res += AppendFilenameSuffix(RccFileName_, suffix);
  235. return res;
  236. }
  237. bool cmQtAutoGeneratorRcc::SettingsFileRead()
  238. {
  239. // Compose current settings strings
  240. {
  241. cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
  242. std::string const sep(" ~~~ ");
  243. {
  244. std::string str;
  245. str += RccExecutable_;
  246. str += sep;
  247. str += cmJoin(RccListOptions_, ";");
  248. str += sep;
  249. str += QrcFile_;
  250. str += sep;
  251. str += RccPathChecksum_;
  252. str += sep;
  253. str += RccFileName_;
  254. str += sep;
  255. str += cmJoin(Options_, ";");
  256. str += sep;
  257. str += cmJoin(Inputs_, ";");
  258. str += sep;
  259. SettingsString_ = crypt.HashString(str);
  260. }
  261. }
  262. // Make sure the settings file exists
  263. if (!FileSys().FileExists(SettingsFile_, true)) {
  264. // Touch the settings file to make sure it exists
  265. FileSys().Touch(SettingsFile_, true);
  266. }
  267. // Lock the lock file
  268. {
  269. // Make sure the lock file exists
  270. if (!FileSys().FileExists(LockFile_, true)) {
  271. if (!FileSys().Touch(LockFile_, true)) {
  272. Log().ErrorFile(GeneratorT::RCC, LockFile_,
  273. "Lock file creation failed");
  274. Error_ = true;
  275. return false;
  276. }
  277. }
  278. // Lock the lock file
  279. cmFileLockResult lockResult =
  280. LockFileLock_.Lock(LockFile_, static_cast<unsigned long>(-1));
  281. if (!lockResult.IsOk()) {
  282. Log().ErrorFile(GeneratorT::RCC, LockFile_,
  283. "File lock failed: " + lockResult.GetOutputMessage());
  284. Error_ = true;
  285. return false;
  286. }
  287. }
  288. // Read old settings
  289. {
  290. std::string content;
  291. if (FileSys().FileRead(content, SettingsFile_)) {
  292. SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc"));
  293. // In case any setting changed clear the old settings file.
  294. // This triggers a full rebuild on the next run if the current
  295. // build is aborted before writing the current settings in the end.
  296. if (SettingsChanged_) {
  297. FileSys().FileWrite(GeneratorT::RCC, SettingsFile_, "");
  298. }
  299. } else {
  300. SettingsChanged_ = true;
  301. }
  302. }
  303. return true;
  304. }
  305. void cmQtAutoGeneratorRcc::SettingsFileWrite()
  306. {
  307. // Only write if any setting changed
  308. if (SettingsChanged_) {
  309. if (Log().Verbose()) {
  310. Log().Info(GeneratorT::RCC,
  311. "Writing settings file " + Quoted(SettingsFile_));
  312. }
  313. // Write settings file
  314. std::string content = "rcc:";
  315. content += SettingsString_;
  316. content += '\n';
  317. if (!FileSys().FileWrite(GeneratorT::RCC, SettingsFile_, content)) {
  318. Log().ErrorFile(GeneratorT::RCC, SettingsFile_,
  319. "Settings file writing failed");
  320. // Remove old settings file to trigger a full rebuild on the next run
  321. FileSys().FileRemove(SettingsFile_);
  322. Error_ = true;
  323. }
  324. }
  325. // Unlock the lock file
  326. LockFileLock_.Release();
  327. }
  328. bool cmQtAutoGeneratorRcc::TestQrcRccFiles()
  329. {
  330. // Do basic checks if rcc generation is required
  331. // Test if the rcc output file exists
  332. if (!FileSys().FileExists(RccFileOutput_)) {
  333. if (Log().Verbose()) {
  334. std::string reason = "Generating ";
  335. reason += Quoted(RccFileOutput_);
  336. reason += " from its source file ";
  337. reason += Quoted(QrcFile_);
  338. reason += " because it doesn't exist";
  339. Log().Info(GeneratorT::RCC, reason);
  340. }
  341. Generate_ = true;
  342. return Generate_;
  343. }
  344. // Test if the settings changed
  345. if (SettingsChanged_) {
  346. if (Log().Verbose()) {
  347. std::string reason = "Generating ";
  348. reason += Quoted(RccFileOutput_);
  349. reason += " from ";
  350. reason += Quoted(QrcFile_);
  351. reason += " because the RCC settings changed";
  352. Log().Info(GeneratorT::RCC, reason);
  353. }
  354. Generate_ = true;
  355. return Generate_;
  356. }
  357. // Test if the rcc output file is older than the .qrc file
  358. {
  359. bool isOlder = false;
  360. {
  361. std::string error;
  362. isOlder = FileSys().FileIsOlderThan(RccFileOutput_, QrcFile_, &error);
  363. if (!error.empty()) {
  364. Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
  365. Error_ = true;
  366. }
  367. }
  368. if (isOlder) {
  369. if (Log().Verbose()) {
  370. std::string reason = "Generating ";
  371. reason += Quoted(RccFileOutput_);
  372. reason += " because it is older than ";
  373. reason += Quoted(QrcFile_);
  374. Log().Info(GeneratorT::RCC, reason);
  375. }
  376. Generate_ = true;
  377. }
  378. }
  379. return Generate_;
  380. }
  381. bool cmQtAutoGeneratorRcc::TestResourcesRead()
  382. {
  383. if (!Inputs_.empty()) {
  384. // Inputs are known already
  385. return true;
  386. }
  387. if (!RccListOptions_.empty()) {
  388. // Start a rcc list process and parse the output
  389. if (Process_) {
  390. // Process is running already
  391. if (Process_->IsFinished()) {
  392. // Process is finished
  393. if (!ProcessResult_.error()) {
  394. // Process success
  395. std::string parseError;
  396. if (!RccListParseOutput(ProcessResult_.StdOut, ProcessResult_.StdErr,
  397. Inputs_, parseError)) {
  398. Log().ErrorFile(GeneratorT::RCC, QrcFile_, parseError);
  399. Error_ = true;
  400. }
  401. } else {
  402. Log().ErrorFile(GeneratorT::RCC, QrcFile_,
  403. ProcessResult_.ErrorMessage);
  404. Error_ = true;
  405. }
  406. // Clean up
  407. Process_.reset();
  408. ProcessResult_.reset();
  409. } else {
  410. // Process is not finished, yet.
  411. return false;
  412. }
  413. } else {
  414. // Start a new process
  415. // rcc prints relative entry paths when started in the directory of the
  416. // qrc file with a pathless qrc file name argument.
  417. // This is important because on Windows absolute paths returned by rcc
  418. // might contain bad multibyte characters when the qrc file path
  419. // contains non-ASCII pcharacters.
  420. std::vector<std::string> cmd;
  421. cmd.push_back(RccExecutable_);
  422. cmd.insert(cmd.end(), RccListOptions_.begin(), RccListOptions_.end());
  423. cmd.push_back(QrcFileName_);
  424. // We're done here if the process fails to start
  425. return !StartProcess(QrcFileDir_, cmd, false);
  426. }
  427. } else {
  428. // rcc does not support the --list command.
  429. // Read the qrc file content and parse it.
  430. std::string qrcContent;
  431. if (FileSys().FileRead(GeneratorT::RCC, qrcContent, QrcFile_)) {
  432. RccListParseContent(qrcContent, Inputs_);
  433. }
  434. }
  435. if (!Inputs_.empty()) {
  436. // Convert relative paths to absolute paths
  437. RccListConvertFullPath(QrcFileDir_, Inputs_);
  438. }
  439. return true;
  440. }
  441. bool cmQtAutoGeneratorRcc::TestResources()
  442. {
  443. if (Inputs_.empty()) {
  444. return true;
  445. }
  446. {
  447. std::string error;
  448. for (std::string const& resFile : Inputs_) {
  449. // Check if the resource file exists
  450. if (!FileSys().FileExists(resFile)) {
  451. error = "Could not find the resource file\n ";
  452. error += Quoted(resFile);
  453. error += '\n';
  454. Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
  455. Error_ = true;
  456. break;
  457. }
  458. // Check if the resource file is newer than the build file
  459. if (FileSys().FileIsOlderThan(RccFileOutput_, resFile, &error)) {
  460. if (Log().Verbose()) {
  461. std::string reason = "Generating ";
  462. reason += Quoted(RccFileOutput_);
  463. reason += " from ";
  464. reason += Quoted(QrcFile_);
  465. reason += " because it is older than ";
  466. reason += Quoted(resFile);
  467. Log().Info(GeneratorT::RCC, reason);
  468. }
  469. Generate_ = true;
  470. break;
  471. }
  472. // Print error and break on demand
  473. if (!error.empty()) {
  474. Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
  475. Error_ = true;
  476. break;
  477. }
  478. }
  479. }
  480. return Generate_;
  481. }
  482. void cmQtAutoGeneratorRcc::TestInfoFile()
  483. {
  484. // Test if the rcc output file is older than the info file
  485. {
  486. bool isOlder = false;
  487. {
  488. std::string error;
  489. isOlder = FileSys().FileIsOlderThan(RccFileOutput_, InfoFile(), &error);
  490. if (!error.empty()) {
  491. Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
  492. Error_ = true;
  493. }
  494. }
  495. if (isOlder) {
  496. if (Log().Verbose()) {
  497. std::string reason = "Touching ";
  498. reason += Quoted(RccFileOutput_);
  499. reason += " because it is older than ";
  500. reason += Quoted(InfoFile());
  501. Log().Info(GeneratorT::RCC, reason);
  502. }
  503. // Touch build file
  504. FileSys().Touch(RccFileOutput_);
  505. BuildFileChanged_ = true;
  506. }
  507. }
  508. }
  509. void cmQtAutoGeneratorRcc::GenerateParentDir()
  510. {
  511. // Make sure the parent directory exists
  512. if (!FileSys().MakeParentDirectory(GeneratorT::RCC, RccFileOutput_)) {
  513. Error_ = true;
  514. }
  515. }
  516. /**
  517. * @return True when finished
  518. */
  519. bool cmQtAutoGeneratorRcc::GenerateRcc()
  520. {
  521. if (!Generate_) {
  522. // Nothing to do
  523. return true;
  524. }
  525. if (Process_) {
  526. // Process is running already
  527. if (Process_->IsFinished()) {
  528. // Process is finished
  529. if (!ProcessResult_.error()) {
  530. // Rcc process success
  531. // Print rcc output
  532. if (!ProcessResult_.StdOut.empty()) {
  533. Log().Info(GeneratorT::RCC, ProcessResult_.StdOut);
  534. }
  535. BuildFileChanged_ = true;
  536. } else {
  537. // Rcc process failed
  538. {
  539. std::string emsg = "The rcc process failed to compile\n ";
  540. emsg += Quoted(QrcFile_);
  541. emsg += "\ninto\n ";
  542. emsg += Quoted(RccFileOutput_);
  543. if (ProcessResult_.error()) {
  544. emsg += "\n";
  545. emsg += ProcessResult_.ErrorMessage;
  546. }
  547. Log().ErrorCommand(GeneratorT::RCC, emsg, Process_->Setup().Command,
  548. ProcessResult_.StdOut);
  549. }
  550. FileSys().FileRemove(RccFileOutput_);
  551. Error_ = true;
  552. }
  553. // Clean up
  554. Process_.reset();
  555. ProcessResult_.reset();
  556. } else {
  557. // Process is not finished, yet.
  558. return false;
  559. }
  560. } else {
  561. // Start a rcc process
  562. std::vector<std::string> cmd;
  563. cmd.push_back(RccExecutable_);
  564. cmd.insert(cmd.end(), Options_.begin(), Options_.end());
  565. cmd.push_back("-o");
  566. cmd.push_back(RccFileOutput_);
  567. cmd.push_back(QrcFile_);
  568. // We're done here if the process fails to start
  569. return !StartProcess(AutogenBuildDir_, cmd, true);
  570. }
  571. return true;
  572. }
  573. void cmQtAutoGeneratorRcc::GenerateWrapper()
  574. {
  575. // Generate a wrapper source file on demand
  576. if (IsMultiConfig()) {
  577. // Wrapper file content
  578. std::string content;
  579. content += "// This is an autogenerated configuration wrapper file.\n";
  580. content += "// Changes will be overwritten.\n";
  581. content += "#include <";
  582. content += MultiConfigOutput();
  583. content += ">\n";
  584. // Write content to file
  585. if (FileSys().FileDiffers(RccFilePublic_, content)) {
  586. // Write new wrapper file
  587. if (Log().Verbose()) {
  588. Log().Info(GeneratorT::RCC,
  589. "Generating RCC wrapper file " + RccFilePublic_);
  590. }
  591. if (!FileSys().FileWrite(GeneratorT::RCC, RccFilePublic_, content)) {
  592. Log().ErrorFile(GeneratorT::RCC, RccFilePublic_,
  593. "RCC wrapper file writing failed");
  594. Error_ = true;
  595. }
  596. } else if (BuildFileChanged_) {
  597. // Just touch the wrapper file
  598. if (Log().Verbose()) {
  599. Log().Info(GeneratorT::RCC,
  600. "Touching RCC wrapper file " + RccFilePublic_);
  601. }
  602. FileSys().Touch(RccFilePublic_);
  603. }
  604. }
  605. }
  606. bool cmQtAutoGeneratorRcc::StartProcess(
  607. std::string const& workingDirectory, std::vector<std::string> const& command,
  608. bool mergedOutput)
  609. {
  610. // Log command
  611. if (Log().Verbose()) {
  612. std::string msg = "Running command:\n";
  613. msg += QuotedCommand(command);
  614. msg += '\n';
  615. Log().Info(GeneratorT::RCC, msg);
  616. }
  617. // Create process handler
  618. Process_ = cm::make_unique<ReadOnlyProcessT>();
  619. Process_->setup(&ProcessResult_, mergedOutput, command, workingDirectory);
  620. // Start process
  621. if (!Process_->start(UVLoop(),
  622. std::bind(&cm::uv_async_ptr::send, &UVRequest()))) {
  623. Log().ErrorFile(GeneratorT::RCC, QrcFile_, ProcessResult_.ErrorMessage);
  624. Error_ = true;
  625. // Clean up
  626. Process_.reset();
  627. ProcessResult_.reset();
  628. return false;
  629. }
  630. return true;
  631. }