cmQtAutoGeneratorRcc.cxx 19 KB

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