cmQtAutoRcc.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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 "cmQtAutoRcc.h"
  4. #include "cmQtAutoGen.h"
  5. #include <sstream>
  6. #include "cmAlgorithms.h"
  7. #include "cmCryptoHash.h"
  8. #include "cmDuration.h"
  9. #include "cmFileLockResult.h"
  10. #include "cmMakefile.h"
  11. #include "cmProcessOutput.h"
  12. #include "cmSystemTools.h"
  13. // -- Class methods
  14. cmQtAutoRcc::cmQtAutoRcc() = default;
  15. cmQtAutoRcc::~cmQtAutoRcc() = default;
  16. bool cmQtAutoRcc::Init(cmMakefile* makefile)
  17. {
  18. // -- Utility lambdas
  19. auto InfoGet = [makefile](std::string const& key) {
  20. return makefile->GetSafeDefinition(key);
  21. };
  22. auto InfoGetList =
  23. [makefile](std::string const& key) -> std::vector<std::string> {
  24. std::vector<std::string> list;
  25. cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
  26. return list;
  27. };
  28. auto InfoGetConfig = [makefile,
  29. this](std::string const& key) -> std::string {
  30. const char* valueConf = nullptr;
  31. {
  32. std::string keyConf = key;
  33. keyConf += '_';
  34. keyConf += InfoConfig();
  35. valueConf = makefile->GetDefinition(keyConf);
  36. }
  37. if (valueConf == nullptr) {
  38. return makefile->GetSafeDefinition(key);
  39. }
  40. return std::string(valueConf);
  41. };
  42. auto InfoGetConfigList =
  43. [&InfoGetConfig](std::string const& key) -> std::vector<std::string> {
  44. std::vector<std::string> list;
  45. cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
  46. return list;
  47. };
  48. auto LogInfoError = [this](std::string const& msg) -> bool {
  49. std::ostringstream err;
  50. err << "In " << Quoted(this->InfoFile()) << ":\n" << msg;
  51. this->Log().Error(GenT::RCC, err.str());
  52. return false;
  53. };
  54. // -- Read info file
  55. if (!makefile->ReadListFile(InfoFile())) {
  56. return LogInfoError("File processing failed.");
  57. }
  58. // - Configurations
  59. Logger_.RaiseVerbosity(InfoGet("ARCC_VERBOSITY"));
  60. MultiConfig_ = makefile->IsOn("ARCC_MULTI_CONFIG");
  61. // - Directories
  62. AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
  63. if (AutogenBuildDir_.empty()) {
  64. return LogInfoError("Build directory empty.");
  65. }
  66. IncludeDir_ = InfoGetConfig("ARCC_INCLUDE_DIR");
  67. if (IncludeDir_.empty()) {
  68. return LogInfoError("Include directory empty.");
  69. }
  70. // - Rcc executable
  71. RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
  72. if (!RccExecutableTime_.Load(RccExecutable_)) {
  73. std::string error = "The rcc executable ";
  74. error += Quoted(RccExecutable_);
  75. error += " does not exist.";
  76. return LogInfoError(error);
  77. }
  78. RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
  79. // - Job
  80. LockFile_ = InfoGet("ARCC_LOCK_FILE");
  81. QrcFile_ = InfoGet("ARCC_SOURCE");
  82. QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_);
  83. QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_);
  84. RccPathChecksum_ = InfoGet("ARCC_OUTPUT_CHECKSUM");
  85. RccFileName_ = InfoGet("ARCC_OUTPUT_NAME");
  86. Options_ = InfoGetConfigList("ARCC_OPTIONS");
  87. Inputs_ = InfoGetList("ARCC_INPUTS");
  88. // - Settings file
  89. SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
  90. // - Validity checks
  91. if (LockFile_.empty()) {
  92. return LogInfoError("Lock file name missing.");
  93. }
  94. if (SettingsFile_.empty()) {
  95. return LogInfoError("Settings file name missing.");
  96. }
  97. if (AutogenBuildDir_.empty()) {
  98. return LogInfoError("Autogen build directory missing.");
  99. }
  100. if (RccExecutable_.empty()) {
  101. return LogInfoError("rcc executable missing.");
  102. }
  103. if (QrcFile_.empty()) {
  104. return LogInfoError("rcc input file missing.");
  105. }
  106. if (RccFileName_.empty()) {
  107. return LogInfoError("rcc output file missing.");
  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 cmQtAutoRcc::Process()
  127. {
  128. if (!SettingsFileRead()) {
  129. return false;
  130. }
  131. // Test if the rcc output needs to be regenerated
  132. bool generate = false;
  133. if (!TestQrcRccFiles(generate)) {
  134. return false;
  135. }
  136. if (!generate && !TestResources(generate)) {
  137. return false;
  138. }
  139. // Generate on demand
  140. if (generate) {
  141. if (!GenerateRcc()) {
  142. return false;
  143. }
  144. } else {
  145. // Test if the info file is newer than the output file
  146. if (!TestInfoFile()) {
  147. return false;
  148. }
  149. }
  150. if (!GenerateWrapper()) {
  151. return false;
  152. }
  153. return SettingsFileWrite();
  154. }
  155. std::string cmQtAutoRcc::MultiConfigOutput() const
  156. {
  157. static std::string const suffix = "_CMAKE_";
  158. std::string res;
  159. res += RccPathChecksum_;
  160. res += '/';
  161. res += AppendFilenameSuffix(RccFileName_, suffix);
  162. return res;
  163. }
  164. bool cmQtAutoRcc::SettingsFileRead()
  165. {
  166. // Compose current settings strings
  167. {
  168. cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
  169. std::string const sep(" ~~~ ");
  170. {
  171. std::string str;
  172. str += RccExecutable_;
  173. str += sep;
  174. str += cmJoin(RccListOptions_, ";");
  175. str += sep;
  176. str += QrcFile_;
  177. str += sep;
  178. str += RccPathChecksum_;
  179. str += sep;
  180. str += RccFileName_;
  181. str += sep;
  182. str += cmJoin(Options_, ";");
  183. str += sep;
  184. str += cmJoin(Inputs_, ";");
  185. str += sep;
  186. SettingsString_ = crypt.HashString(str);
  187. }
  188. }
  189. // Make sure the settings file exists
  190. if (!cmSystemTools::FileExists(SettingsFile_, true)) {
  191. // Touch the settings file to make sure it exists
  192. if (!cmSystemTools::Touch(SettingsFile_, true)) {
  193. Log().ErrorFile(GenT::RCC, SettingsFile_,
  194. "Settings file creation failed.");
  195. return false;
  196. }
  197. }
  198. // Lock the lock file
  199. {
  200. // Make sure the lock file exists
  201. if (!cmSystemTools::FileExists(LockFile_, true)) {
  202. if (!cmSystemTools::Touch(LockFile_, true)) {
  203. Log().ErrorFile(GenT::RCC, LockFile_, "Lock file creation failed.");
  204. return false;
  205. }
  206. }
  207. // Lock the lock file
  208. cmFileLockResult lockResult =
  209. LockFileLock_.Lock(LockFile_, static_cast<unsigned long>(-1));
  210. if (!lockResult.IsOk()) {
  211. Log().ErrorFile(GenT::RCC, LockFile_,
  212. "File lock failed: " + lockResult.GetOutputMessage());
  213. return false;
  214. }
  215. }
  216. // Read old settings
  217. {
  218. std::string content;
  219. if (FileRead(content, SettingsFile_)) {
  220. SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc"));
  221. // In case any setting changed clear the old settings file.
  222. // This triggers a full rebuild on the next run if the current
  223. // build is aborted before writing the current settings in the end.
  224. if (SettingsChanged_) {
  225. std::string error;
  226. if (!FileWrite(SettingsFile_, "", &error)) {
  227. Log().ErrorFile(GenT::RCC, SettingsFile_,
  228. "Settings file clearing failed. " + error);
  229. return false;
  230. }
  231. }
  232. } else {
  233. SettingsChanged_ = true;
  234. }
  235. }
  236. return true;
  237. }
  238. bool cmQtAutoRcc::SettingsFileWrite()
  239. {
  240. // Only write if any setting changed
  241. if (SettingsChanged_) {
  242. if (Log().Verbose()) {
  243. Log().Info(GenT::RCC, "Writing settings file " + Quoted(SettingsFile_));
  244. }
  245. // Write settings file
  246. std::string content = "rcc:";
  247. content += SettingsString_;
  248. content += '\n';
  249. std::string error;
  250. if (!FileWrite(SettingsFile_, content, &error)) {
  251. Log().ErrorFile(GenT::RCC, SettingsFile_,
  252. "Settings file writing failed. " + error);
  253. // Remove old settings file to trigger a full rebuild on the next run
  254. cmSystemTools::RemoveFile(SettingsFile_);
  255. return false;
  256. }
  257. }
  258. // Unlock the lock file
  259. LockFileLock_.Release();
  260. return true;
  261. }
  262. /// Do basic checks if rcc generation is required
  263. bool cmQtAutoRcc::TestQrcRccFiles(bool& generate)
  264. {
  265. // Test if the rcc input file exists
  266. if (!QrcFileTime_.Load(QrcFile_)) {
  267. std::string error;
  268. error = "The resources file ";
  269. error += Quoted(QrcFile_);
  270. error += " does not exist";
  271. Log().ErrorFile(GenT::RCC, QrcFile_, error);
  272. return false;
  273. }
  274. // Test if the rcc output file exists
  275. if (!RccFileTime_.Load(RccFileOutput_)) {
  276. if (Log().Verbose()) {
  277. Reason = "Generating ";
  278. Reason += Quoted(RccFileOutput_);
  279. Reason += ", because it doesn't exist, from ";
  280. Reason += Quoted(QrcFile_);
  281. }
  282. generate = true;
  283. return true;
  284. }
  285. // Test if the settings changed
  286. if (SettingsChanged_) {
  287. if (Log().Verbose()) {
  288. Reason = "Generating ";
  289. Reason += Quoted(RccFileOutput_);
  290. Reason += ", because the rcc settings changed, from ";
  291. Reason += Quoted(QrcFile_);
  292. }
  293. generate = true;
  294. return true;
  295. }
  296. // Test if the rcc output file is older than the .qrc file
  297. if (RccFileTime_.Older(QrcFileTime_)) {
  298. if (Log().Verbose()) {
  299. Reason = "Generating ";
  300. Reason += Quoted(RccFileOutput_);
  301. Reason += ", because it is older than ";
  302. Reason += Quoted(QrcFile_);
  303. Reason += ", from ";
  304. Reason += Quoted(QrcFile_);
  305. }
  306. generate = true;
  307. return true;
  308. }
  309. // Test if the rcc output file is older than the rcc executable
  310. if (RccFileTime_.Older(RccExecutableTime_)) {
  311. if (Log().Verbose()) {
  312. Reason = "Generating ";
  313. Reason += Quoted(RccFileOutput_);
  314. Reason += ", because it is older than the rcc executable, from ";
  315. Reason += Quoted(QrcFile_);
  316. }
  317. generate = true;
  318. return true;
  319. }
  320. return true;
  321. }
  322. bool cmQtAutoRcc::TestResources(bool& generate)
  323. {
  324. // Read resource files list
  325. if (Inputs_.empty()) {
  326. std::string error;
  327. RccLister const lister(RccExecutable_, RccListOptions_);
  328. if (!lister.list(QrcFile_, Inputs_, error, Log().Verbose())) {
  329. Log().ErrorFile(GenT::RCC, QrcFile_, error);
  330. return false;
  331. }
  332. }
  333. // Check if any resource file is newer than the rcc output file
  334. for (std::string const& resFile : Inputs_) {
  335. // Check if the resource file exists
  336. cmFileTime fileTime;
  337. if (!fileTime.Load(resFile)) {
  338. std::string error;
  339. error = "Could not find the resource file\n ";
  340. error += Quoted(resFile);
  341. error += '\n';
  342. Log().ErrorFile(GenT::RCC, QrcFile_, error);
  343. return false;
  344. }
  345. // Check if the resource file is newer than the rcc output file
  346. if (RccFileTime_.Older(fileTime)) {
  347. if (Log().Verbose()) {
  348. Reason = "Generating ";
  349. Reason += Quoted(RccFileOutput_);
  350. Reason += ", because it is older than ";
  351. Reason += Quoted(resFile);
  352. Reason += ", from ";
  353. Reason += Quoted(QrcFile_);
  354. }
  355. generate = true;
  356. break;
  357. }
  358. }
  359. return true;
  360. }
  361. bool cmQtAutoRcc::TestInfoFile()
  362. {
  363. // Test if the rcc output file is older than the info file
  364. if (RccFileTime_.Older(InfoFileTime())) {
  365. if (Log().Verbose()) {
  366. std::string reason = "Touching ";
  367. reason += Quoted(RccFileOutput_);
  368. reason += " because it is older than ";
  369. reason += Quoted(InfoFile());
  370. Log().Info(GenT::RCC, reason);
  371. }
  372. // Touch build file
  373. if (!cmSystemTools::Touch(RccFileOutput_, false)) {
  374. Log().ErrorFile(GenT::RCC, RccFileOutput_, "Build file touch failed");
  375. return false;
  376. }
  377. BuildFileChanged_ = true;
  378. }
  379. return true;
  380. }
  381. bool cmQtAutoRcc::GenerateRcc()
  382. {
  383. // Make parent directory
  384. if (!MakeParentDirectory(RccFileOutput_)) {
  385. Log().ErrorFile(GenT::RCC, RccFileOutput_,
  386. "Could not create parent directory");
  387. return false;
  388. }
  389. // Compose rcc command
  390. std::vector<std::string> cmd;
  391. cmd.push_back(RccExecutable_);
  392. cmAppend(cmd, Options_);
  393. cmd.emplace_back("-o");
  394. cmd.push_back(RccFileOutput_);
  395. cmd.push_back(QrcFile_);
  396. // Log reason and command
  397. if (Log().Verbose()) {
  398. std::string msg = Reason;
  399. if (!msg.empty() && (msg.back() != '\n')) {
  400. msg += '\n';
  401. }
  402. msg += QuotedCommand(cmd);
  403. msg += '\n';
  404. Log().Info(GenT::RCC, msg);
  405. }
  406. std::string rccStdOut;
  407. std::string rccStdErr;
  408. int retVal = 0;
  409. bool result = cmSystemTools::RunSingleCommand(
  410. cmd, &rccStdOut, &rccStdErr, &retVal, AutogenBuildDir_.c_str(),
  411. cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto);
  412. if (!result || (retVal != 0)) {
  413. // rcc process failed
  414. {
  415. std::string err = "The rcc process failed to compile\n ";
  416. err += Quoted(QrcFile_);
  417. err += "\ninto\n ";
  418. err += Quoted(RccFileOutput_);
  419. Log().ErrorCommand(GenT::RCC, err, cmd, rccStdOut + rccStdErr);
  420. }
  421. cmSystemTools::RemoveFile(RccFileOutput_);
  422. return false;
  423. }
  424. // rcc process success
  425. // Print rcc output
  426. if (!rccStdOut.empty()) {
  427. Log().Info(GenT::RCC, rccStdOut);
  428. }
  429. BuildFileChanged_ = true;
  430. return true;
  431. }
  432. bool cmQtAutoRcc::GenerateWrapper()
  433. {
  434. // Generate a wrapper source file on demand
  435. if (IsMultiConfig()) {
  436. // Wrapper file content
  437. std::string content;
  438. content += "// This is an autogenerated configuration wrapper file.\n";
  439. content += "// Changes will be overwritten.\n";
  440. content += "#include <";
  441. content += MultiConfigOutput();
  442. content += ">\n";
  443. // Compare with existing file content
  444. bool fileDiffers = true;
  445. {
  446. std::string oldContents;
  447. if (FileRead(oldContents, RccFilePublic_)) {
  448. fileDiffers = (oldContents != content);
  449. }
  450. }
  451. if (fileDiffers) {
  452. // Write new wrapper file
  453. if (Log().Verbose()) {
  454. Log().Info(GenT::RCC, "Generating RCC wrapper file " + RccFilePublic_);
  455. }
  456. std::string error;
  457. if (!FileWrite(RccFilePublic_, content, &error)) {
  458. Log().ErrorFile(GenT::RCC, RccFilePublic_,
  459. "RCC wrapper file writing failed. " + error);
  460. return false;
  461. }
  462. } else if (BuildFileChanged_) {
  463. // Just touch the wrapper file
  464. if (Log().Verbose()) {
  465. Log().Info(GenT::RCC, "Touching RCC wrapper file " + RccFilePublic_);
  466. }
  467. if (!cmSystemTools::Touch(RccFilePublic_, false)) {
  468. Log().ErrorFile(GenT::RCC, RccFilePublic_,
  469. "RCC wrapper file touch failed.");
  470. return false;
  471. }
  472. }
  473. }
  474. return true;
  475. }