cmQtAutoRcc.cxx 14 KB

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