cmQtAutoGeneratorRcc.cxx 18 KB

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