cmQtAutoGeneratorRcc.cxx 19 KB

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