cmQtAutoGenerator.cxx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  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 "cmQtAutoGenerator.h"
  5. #include "cmsys/FStream.hxx"
  6. #include "cmAlgorithms.h"
  7. #include "cmGlobalGenerator.h"
  8. #include "cmMakefile.h"
  9. #include "cmStateDirectory.h"
  10. #include "cmStateSnapshot.h"
  11. #include "cmSystemTools.h"
  12. #include "cmake.h"
  13. #include <algorithm>
  14. // -- Class methods
  15. void cmQtAutoGenerator::Logger::SetVerbose(bool value)
  16. {
  17. Verbose_ = value;
  18. }
  19. void cmQtAutoGenerator::Logger::SetColorOutput(bool value)
  20. {
  21. ColorOutput_ = value;
  22. }
  23. std::string cmQtAutoGenerator::Logger::HeadLine(std::string const& title)
  24. {
  25. std::string head = title;
  26. head += '\n';
  27. head.append(head.size() - 1, '-');
  28. head += '\n';
  29. return head;
  30. }
  31. void cmQtAutoGenerator::Logger::Info(GeneratorT genType,
  32. std::string const& message)
  33. {
  34. std::string msg = GeneratorName(genType);
  35. msg += ": ";
  36. msg += message;
  37. if (msg.back() != '\n') {
  38. msg.push_back('\n');
  39. }
  40. {
  41. std::lock_guard<std::mutex> lock(Mutex_);
  42. cmSystemTools::Stdout(msg.c_str(), msg.size());
  43. }
  44. }
  45. void cmQtAutoGenerator::Logger::Warning(GeneratorT genType,
  46. std::string const& message)
  47. {
  48. std::string msg;
  49. if (message.find('\n') == std::string::npos) {
  50. // Single line message
  51. msg += GeneratorName(genType);
  52. msg += " warning: ";
  53. } else {
  54. // Multi line message
  55. msg += HeadLine(GeneratorName(genType) + " warning");
  56. }
  57. // Message
  58. msg += message;
  59. if (msg.back() != '\n') {
  60. msg.push_back('\n');
  61. }
  62. msg.push_back('\n');
  63. {
  64. std::lock_guard<std::mutex> lock(Mutex_);
  65. cmSystemTools::Stdout(msg.c_str(), msg.size());
  66. }
  67. }
  68. void cmQtAutoGenerator::Logger::WarningFile(GeneratorT genType,
  69. std::string const& filename,
  70. std::string const& message)
  71. {
  72. std::string msg = " ";
  73. msg += Quoted(filename);
  74. msg.push_back('\n');
  75. // Message
  76. msg += message;
  77. Warning(genType, msg);
  78. }
  79. void cmQtAutoGenerator::Logger::Error(GeneratorT genType,
  80. std::string const& message)
  81. {
  82. std::string msg;
  83. msg += HeadLine(GeneratorName(genType) + " error");
  84. // Message
  85. msg += message;
  86. if (msg.back() != '\n') {
  87. msg.push_back('\n');
  88. }
  89. msg.push_back('\n');
  90. {
  91. std::lock_guard<std::mutex> lock(Mutex_);
  92. cmSystemTools::Stderr(msg.c_str(), msg.size());
  93. }
  94. }
  95. void cmQtAutoGenerator::Logger::ErrorFile(GeneratorT genType,
  96. std::string const& filename,
  97. std::string const& message)
  98. {
  99. std::string emsg = " ";
  100. emsg += Quoted(filename);
  101. emsg += '\n';
  102. // Message
  103. emsg += message;
  104. Error(genType, emsg);
  105. }
  106. void cmQtAutoGenerator::Logger::ErrorCommand(
  107. GeneratorT genType, std::string const& message,
  108. std::vector<std::string> const& command, std::string const& output)
  109. {
  110. std::string msg;
  111. msg.push_back('\n');
  112. msg += HeadLine(GeneratorName(genType) + " subprocess error");
  113. msg += message;
  114. if (msg.back() != '\n') {
  115. msg.push_back('\n');
  116. }
  117. msg.push_back('\n');
  118. msg += HeadLine("Command");
  119. msg += QuotedCommand(command);
  120. if (msg.back() != '\n') {
  121. msg.push_back('\n');
  122. }
  123. msg.push_back('\n');
  124. msg += HeadLine("Output");
  125. msg += output;
  126. if (msg.back() != '\n') {
  127. msg.push_back('\n');
  128. }
  129. msg.push_back('\n');
  130. {
  131. std::lock_guard<std::mutex> lock(Mutex_);
  132. cmSystemTools::Stderr(msg.c_str(), msg.size());
  133. }
  134. }
  135. std::string cmQtAutoGenerator::FileSystem::RealPath(
  136. std::string const& filename)
  137. {
  138. std::lock_guard<std::mutex> lock(Mutex_);
  139. return cmSystemTools::GetRealPath(filename);
  140. }
  141. bool cmQtAutoGenerator::FileSystem::FileExists(std::string const& filename)
  142. {
  143. std::lock_guard<std::mutex> lock(Mutex_);
  144. return cmSystemTools::FileExists(filename);
  145. }
  146. bool cmQtAutoGenerator::FileSystem::FileIsOlderThan(
  147. std::string const& buildFile, std::string const& sourceFile,
  148. std::string* error)
  149. {
  150. bool res(false);
  151. int result = 0;
  152. {
  153. std::lock_guard<std::mutex> lock(Mutex_);
  154. res = cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result);
  155. }
  156. if (res) {
  157. res = (result < 0);
  158. } else {
  159. if (error != nullptr) {
  160. error->append(
  161. "File modification time comparison failed for the files\n ");
  162. error->append(Quoted(buildFile));
  163. error->append("\nand\n ");
  164. error->append(Quoted(sourceFile));
  165. }
  166. }
  167. return res;
  168. }
  169. bool cmQtAutoGenerator::FileSystem::FileRead(std::string& content,
  170. std::string const& filename,
  171. std::string* error)
  172. {
  173. bool success = false;
  174. {
  175. std::lock_guard<std::mutex> lock(Mutex_);
  176. if (cmSystemTools::FileExists(filename, true)) {
  177. std::size_t const length = cmSystemTools::FileLength(filename);
  178. cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
  179. if (ifs) {
  180. if (length > 0) {
  181. content.resize(length);
  182. ifs.read(&content.front(), content.size());
  183. if (ifs) {
  184. success = true;
  185. } else {
  186. content.clear();
  187. if (error != nullptr) {
  188. error->append("Reading from the file failed.");
  189. }
  190. }
  191. } else {
  192. // Readable but empty file
  193. content.clear();
  194. success = true;
  195. }
  196. } else if (error != nullptr) {
  197. error->append("Opening the file for reading failed.");
  198. }
  199. } else if (error != nullptr) {
  200. error->append(
  201. "The file does not exist, is not readable or is a directory.");
  202. }
  203. }
  204. return success;
  205. }
  206. bool cmQtAutoGenerator::FileSystem::FileRead(GeneratorT genType,
  207. std::string& content,
  208. std::string const& filename)
  209. {
  210. std::string error;
  211. if (!FileRead(content, filename, &error)) {
  212. Log()->ErrorFile(genType, filename, error);
  213. return false;
  214. }
  215. return true;
  216. }
  217. bool cmQtAutoGenerator::FileSystem::FileWrite(std::string const& filename,
  218. std::string const& content,
  219. std::string* error)
  220. {
  221. bool success = false;
  222. // Make sure the parent directory exists
  223. if (MakeParentDirectory(filename)) {
  224. std::lock_guard<std::mutex> lock(Mutex_);
  225. cmsys::ofstream outfile;
  226. outfile.open(filename.c_str(),
  227. (std::ios::out | std::ios::binary | std::ios::trunc));
  228. if (outfile) {
  229. outfile << content;
  230. // Check for write errors
  231. if (outfile.good()) {
  232. success = true;
  233. } else {
  234. if (error != nullptr) {
  235. error->assign("File writing failed");
  236. }
  237. }
  238. } else {
  239. if (error != nullptr) {
  240. error->assign("Opening file for writing failed");
  241. }
  242. }
  243. } else {
  244. if (error != nullptr) {
  245. error->assign("Could not create parent directory");
  246. }
  247. }
  248. return success;
  249. }
  250. bool cmQtAutoGenerator::FileSystem::FileWrite(GeneratorT genType,
  251. std::string const& filename,
  252. std::string const& content)
  253. {
  254. std::string error;
  255. if (!FileWrite(filename, content, &error)) {
  256. Log()->ErrorFile(genType, filename, error);
  257. return false;
  258. }
  259. return true;
  260. }
  261. bool cmQtAutoGenerator::FileSystem::FileDiffers(std::string const& filename,
  262. std::string const& content)
  263. {
  264. bool differs = true;
  265. {
  266. std::string oldContents;
  267. if (FileRead(oldContents, filename)) {
  268. differs = (oldContents != content);
  269. }
  270. }
  271. return differs;
  272. }
  273. bool cmQtAutoGenerator::FileSystem::FileRemove(std::string const& filename)
  274. {
  275. std::lock_guard<std::mutex> lock(Mutex_);
  276. return cmSystemTools::RemoveFile(filename);
  277. }
  278. bool cmQtAutoGenerator::FileSystem::Touch(std::string const& filename)
  279. {
  280. std::lock_guard<std::mutex> lock(Mutex_);
  281. return cmSystemTools::Touch(filename, false);
  282. }
  283. bool cmQtAutoGenerator::FileSystem::MakeDirectory(std::string const& dirname)
  284. {
  285. std::lock_guard<std::mutex> lock(Mutex_);
  286. return cmSystemTools::MakeDirectory(dirname);
  287. }
  288. bool cmQtAutoGenerator::FileSystem::MakeDirectory(GeneratorT genType,
  289. std::string const& dirname)
  290. {
  291. if (!MakeDirectory(dirname)) {
  292. Log()->ErrorFile(genType, dirname, "Could not create directory");
  293. return false;
  294. }
  295. return true;
  296. }
  297. bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
  298. std::string const& filename)
  299. {
  300. bool success = true;
  301. std::string const dirName = cmSystemTools::GetFilenamePath(filename);
  302. if (!dirName.empty()) {
  303. success = MakeDirectory(dirName);
  304. }
  305. return success;
  306. }
  307. bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
  308. GeneratorT genType, std::string const& filename)
  309. {
  310. if (!MakeParentDirectory(filename)) {
  311. Log()->ErrorFile(genType, filename, "Could not create parent directory");
  312. return false;
  313. }
  314. return true;
  315. }
  316. int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::init(uv_loop_t* uv_loop,
  317. ReadOnlyProcessT* process)
  318. {
  319. Process_ = process;
  320. Target_ = nullptr;
  321. return UVPipe_.init(*uv_loop, 0, this);
  322. }
  323. int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::startRead(std::string* target)
  324. {
  325. Target_ = target;
  326. return uv_read_start(uv_stream(), &PipeT::UVAlloc, &PipeT::UVData);
  327. }
  328. void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::reset()
  329. {
  330. Process_ = nullptr;
  331. Target_ = nullptr;
  332. UVPipe_.reset();
  333. Buffer_.clear();
  334. Buffer_.shrink_to_fit();
  335. }
  336. void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVAlloc(uv_handle_t* handle,
  337. size_t suggestedSize,
  338. uv_buf_t* buf)
  339. {
  340. auto& pipe = *reinterpret_cast<PipeT*>(handle->data);
  341. pipe.Buffer_.resize(suggestedSize);
  342. buf->base = &pipe.Buffer_.front();
  343. buf->len = pipe.Buffer_.size();
  344. }
  345. void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVData(uv_stream_t* stream,
  346. ssize_t nread,
  347. const uv_buf_t* buf)
  348. {
  349. auto& pipe = *reinterpret_cast<PipeT*>(stream->data);
  350. if (nread > 0) {
  351. // Append data to merged output
  352. if ((buf->base != nullptr) && (pipe.Target_ != nullptr)) {
  353. pipe.Target_->append(buf->base, nread);
  354. }
  355. } else if (nread < 0) {
  356. // EOF or error
  357. auto* proc = pipe.Process_;
  358. // Check it this an unusual error
  359. if (nread != UV_EOF) {
  360. if (!proc->Result()->error()) {
  361. proc->Result()->ErrorMessage =
  362. "libuv reading from pipe failed with error code ";
  363. proc->Result()->ErrorMessage += std::to_string(nread);
  364. }
  365. }
  366. // Clear libuv pipe handle and try to finish
  367. pipe.reset();
  368. proc->UVTryFinish();
  369. }
  370. }
  371. void cmQtAutoGenerator::ProcessResultT::reset()
  372. {
  373. ExitStatus = 0;
  374. TermSignal = 0;
  375. if (!StdOut.empty()) {
  376. StdOut.clear();
  377. StdOut.shrink_to_fit();
  378. }
  379. if (!StdErr.empty()) {
  380. StdErr.clear();
  381. StdErr.shrink_to_fit();
  382. }
  383. if (!ErrorMessage.empty()) {
  384. ErrorMessage.clear();
  385. ErrorMessage.shrink_to_fit();
  386. }
  387. }
  388. void cmQtAutoGenerator::ReadOnlyProcessT::setup(
  389. ProcessResultT* result, bool mergedOutput,
  390. std::vector<std::string> const& command, std::string const& workingDirectory)
  391. {
  392. Setup_.WorkingDirectory = workingDirectory;
  393. Setup_.Command = command;
  394. Setup_.Result = result;
  395. Setup_.MergedOutput = mergedOutput;
  396. }
  397. bool cmQtAutoGenerator::ReadOnlyProcessT::start(
  398. uv_loop_t* uv_loop, std::function<void()>&& finishedCallback)
  399. {
  400. if (IsStarted() || (Result() == nullptr)) {
  401. return false;
  402. }
  403. // Reset result before the start
  404. Result()->reset();
  405. // Fill command string pointers
  406. if (!Setup().Command.empty()) {
  407. CommandPtr_.reserve(Setup().Command.size() + 1);
  408. for (std::string const& arg : Setup().Command) {
  409. CommandPtr_.push_back(arg.c_str());
  410. }
  411. CommandPtr_.push_back(nullptr);
  412. } else {
  413. Result()->ErrorMessage = "Empty command";
  414. }
  415. if (!Result()->error()) {
  416. if (UVPipeOut_.init(uv_loop, this) != 0) {
  417. Result()->ErrorMessage = "libuv stdout pipe initialization failed";
  418. }
  419. }
  420. if (!Result()->error()) {
  421. if (UVPipeErr_.init(uv_loop, this) != 0) {
  422. Result()->ErrorMessage = "libuv stderr pipe initialization failed";
  423. }
  424. }
  425. if (!Result()->error()) {
  426. // -- Setup process stdio options
  427. // stdin
  428. UVOptionsStdIO_[0].flags = UV_IGNORE;
  429. UVOptionsStdIO_[0].data.stream = nullptr;
  430. // stdout
  431. UVOptionsStdIO_[1].flags =
  432. static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
  433. UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream();
  434. // stderr
  435. UVOptionsStdIO_[2].flags =
  436. static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
  437. UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream();
  438. // -- Setup process options
  439. std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0);
  440. UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit;
  441. UVOptions_.file = CommandPtr_[0];
  442. UVOptions_.args = const_cast<char**>(&CommandPtr_.front());
  443. UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
  444. UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
  445. UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
  446. UVOptions_.stdio = &UVOptionsStdIO_.front();
  447. // -- Spawn process
  448. if (UVProcess_.spawn(*uv_loop, UVOptions_, this) != 0) {
  449. Result()->ErrorMessage = "libuv process spawn failed";
  450. }
  451. }
  452. // -- Start reading from stdio streams
  453. if (!Result()->error()) {
  454. if (UVPipeOut_.startRead(&Result()->StdOut) != 0) {
  455. Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
  456. }
  457. }
  458. if (!Result()->error()) {
  459. if (UVPipeErr_.startRead(Setup_.MergedOutput ? &Result()->StdOut
  460. : &Result()->StdErr) != 0) {
  461. Result()->ErrorMessage = "libuv start reading from stderr pipe failed";
  462. }
  463. }
  464. if (!Result()->error()) {
  465. IsStarted_ = true;
  466. FinishedCallback_ = std::move(finishedCallback);
  467. } else {
  468. // Clear libuv handles and finish
  469. UVProcess_.reset();
  470. UVPipeOut_.reset();
  471. UVPipeErr_.reset();
  472. CommandPtr_.clear();
  473. }
  474. return IsStarted();
  475. }
  476. void cmQtAutoGenerator::ReadOnlyProcessT::UVExit(uv_process_t* handle,
  477. int64_t exitStatus,
  478. int termSignal)
  479. {
  480. auto& proc = *reinterpret_cast<ReadOnlyProcessT*>(handle->data);
  481. if (proc.IsStarted() && !proc.IsFinished()) {
  482. // Set error message on demand
  483. proc.Result()->ExitStatus = exitStatus;
  484. proc.Result()->TermSignal = termSignal;
  485. if (!proc.Result()->error()) {
  486. if (termSignal != 0) {
  487. proc.Result()->ErrorMessage = "Process was terminated by signal ";
  488. proc.Result()->ErrorMessage +=
  489. std::to_string(proc.Result()->TermSignal);
  490. } else if (exitStatus != 0) {
  491. proc.Result()->ErrorMessage = "Process failed with return value ";
  492. proc.Result()->ErrorMessage +=
  493. std::to_string(proc.Result()->ExitStatus);
  494. }
  495. }
  496. // Reset process handle and try to finish
  497. proc.UVProcess_.reset();
  498. proc.UVTryFinish();
  499. }
  500. }
  501. void cmQtAutoGenerator::ReadOnlyProcessT::UVTryFinish()
  502. {
  503. // There still might be data in the pipes after the process has finished.
  504. // Therefore check if the process is finished AND all pipes are closed
  505. // before signaling the worker thread to continue.
  506. if (UVProcess_.get() == nullptr) {
  507. if (UVPipeOut_.uv_pipe() == nullptr) {
  508. if (UVPipeErr_.uv_pipe() == nullptr) {
  509. IsFinished_ = true;
  510. FinishedCallback_();
  511. }
  512. }
  513. }
  514. }
  515. cmQtAutoGenerator::cmQtAutoGenerator()
  516. : FileSys_(&Logger_)
  517. {
  518. // Initialize logger
  519. Logger_.SetVerbose(cmSystemTools::HasEnv("VERBOSE"));
  520. {
  521. std::string colorEnv;
  522. cmSystemTools::GetEnv("COLOR", colorEnv);
  523. if (!colorEnv.empty()) {
  524. Logger_.SetColorOutput(cmSystemTools::IsOn(colorEnv.c_str()));
  525. } else {
  526. Logger_.SetColorOutput(true);
  527. }
  528. }
  529. // Initialize libuv loop
  530. uv_disable_stdio_inheritance();
  531. #ifdef CMAKE_UV_SIGNAL_HACK
  532. UVHackRAII_ = cm::make_unique<cmUVSignalHackRAII>();
  533. #endif
  534. UVLoop_ = cm::make_unique<uv_loop_t>();
  535. uv_loop_init(UVLoop());
  536. }
  537. cmQtAutoGenerator::~cmQtAutoGenerator()
  538. {
  539. // Close libuv loop
  540. uv_loop_close(UVLoop());
  541. }
  542. bool cmQtAutoGenerator::Run(std::string const& infoFile,
  543. std::string const& config)
  544. {
  545. // Info settings
  546. InfoFile_ = infoFile;
  547. cmSystemTools::ConvertToUnixSlashes(InfoFile_);
  548. InfoDir_ = cmSystemTools::GetFilenamePath(infoFile);
  549. InfoConfig_ = config;
  550. bool success = false;
  551. {
  552. cmake cm(cmake::RoleScript);
  553. cm.SetHomeOutputDirectory(InfoDir());
  554. cm.SetHomeDirectory(InfoDir());
  555. cm.GetCurrentSnapshot().SetDefaultDefinitions();
  556. cmGlobalGenerator gg(&cm);
  557. cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
  558. snapshot.GetDirectory().SetCurrentBinary(InfoDir());
  559. snapshot.GetDirectory().SetCurrentSource(InfoDir());
  560. auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
  561. // The OLD/WARN behavior for policy CMP0053 caused a speed regression.
  562. // https://gitlab.kitware.com/cmake/cmake/issues/17570
  563. makefile->SetPolicyVersion("3.9");
  564. gg.SetCurrentMakefile(makefile.get());
  565. success = this->Init(makefile.get());
  566. }
  567. if (success) {
  568. success = this->Process();
  569. }
  570. return success;
  571. }
  572. std::string cmQtAutoGenerator::SettingsFind(std::string const& content,
  573. const char* key)
  574. {
  575. std::string prefix(key);
  576. prefix += ':';
  577. std::string::size_type pos = content.find(prefix);
  578. if (pos != std::string::npos) {
  579. pos += prefix.size();
  580. if (pos < content.size()) {
  581. std::string::size_type posE = content.find('\n', pos);
  582. if ((posE != std::string::npos) && (posE != pos)) {
  583. return content.substr(pos, posE - pos);
  584. }
  585. }
  586. }
  587. return std::string();
  588. }