cmQtAutoGeneratorRcc.cxx 17 KB

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