cmListFileCache.cxx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*============================================================================
  2. CMake - Cross Platform Makefile Generator
  3. Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
  4. Distributed under the OSI-approved BSD License (the "License");
  5. see accompanying file Copyright.txt for details.
  6. This software is distributed WITHOUT ANY WARRANTY; without even the
  7. implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the License for more information.
  9. ============================================================================*/
  10. #include "cmListFileCache.h"
  11. #include "cmListFileLexer.h"
  12. #include "cmMakefile.h"
  13. #include "cmOutputConverter.h"
  14. #include "cmSystemTools.h"
  15. #include "cmVersion.h"
  16. #include <cmsys/RegularExpression.hxx>
  17. struct cmListFileParser
  18. {
  19. cmListFileParser(cmListFile* lf, cmMakefile* mf, const char* filename);
  20. ~cmListFileParser();
  21. void IssueFileOpenError(std::string const& text) const;
  22. void IssueError(std::string const& text) const;
  23. bool ParseFile();
  24. bool ParseFunction(const char* name, long line);
  25. bool AddArgument(cmListFileLexer_Token* token,
  26. cmListFileArgument::Delimiter delim);
  27. cmListFile* ListFile;
  28. cmMakefile* Makefile;
  29. cmListFileBacktrace Backtrace;
  30. const char* FileName;
  31. cmListFileLexer* Lexer;
  32. cmListFileFunction Function;
  33. enum
  34. {
  35. SeparationOkay,
  36. SeparationWarning,
  37. SeparationError
  38. } Separation;
  39. };
  40. cmListFileParser::cmListFileParser(cmListFile* lf, cmMakefile* mf,
  41. const char* filename)
  42. : ListFile(lf)
  43. , Makefile(mf)
  44. , Backtrace(mf->GetBacktrace())
  45. , FileName(filename)
  46. , Lexer(cmListFileLexer_New())
  47. {
  48. }
  49. cmListFileParser::~cmListFileParser()
  50. {
  51. cmListFileLexer_Delete(this->Lexer);
  52. }
  53. void cmListFileParser::IssueFileOpenError(const std::string& text) const
  54. {
  55. this->Makefile->IssueMessage(cmake::FATAL_ERROR, text);
  56. }
  57. void cmListFileParser::IssueError(const std::string& text) const
  58. {
  59. cmListFileContext lfc;
  60. lfc.FilePath = this->FileName;
  61. lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer);
  62. cmListFileBacktrace lfbt = this->Backtrace;
  63. lfbt = lfbt.Push(lfc);
  64. this->Makefile->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, text,
  65. lfbt);
  66. cmSystemTools::SetFatalErrorOccured();
  67. }
  68. bool cmListFileParser::ParseFile()
  69. {
  70. // Open the file.
  71. cmListFileLexer_BOM bom;
  72. if (!cmListFileLexer_SetFileName(this->Lexer, this->FileName, &bom)) {
  73. this->IssueFileOpenError("cmListFileCache: error can not open file.");
  74. return false;
  75. }
  76. // Verify the Byte-Order-Mark, if any.
  77. if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
  78. cmListFileLexer_SetFileName(this->Lexer, CM_NULLPTR, CM_NULLPTR);
  79. this->IssueFileOpenError(
  80. "File starts with a Byte-Order-Mark that is not UTF-8.");
  81. return false;
  82. }
  83. // Use a simple recursive-descent parser to process the token
  84. // stream.
  85. bool haveNewline = true;
  86. while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
  87. if (token->type == cmListFileLexer_Token_Space) {
  88. } else if (token->type == cmListFileLexer_Token_Newline) {
  89. haveNewline = true;
  90. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  91. haveNewline = false;
  92. } else if (token->type == cmListFileLexer_Token_Identifier) {
  93. if (haveNewline) {
  94. haveNewline = false;
  95. if (this->ParseFunction(token->text, token->line)) {
  96. this->ListFile->Functions.push_back(this->Function);
  97. } else {
  98. return false;
  99. }
  100. } else {
  101. std::ostringstream error;
  102. error << "Parse error. Expected a newline, got "
  103. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  104. << " with text \"" << token->text << "\".";
  105. this->IssueError(error.str());
  106. return false;
  107. }
  108. } else {
  109. std::ostringstream error;
  110. error << "Parse error. Expected a command name, got "
  111. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  112. << " with text \"" << token->text << "\".";
  113. this->IssueError(error.str());
  114. return false;
  115. }
  116. }
  117. return true;
  118. }
  119. bool cmListFile::ParseFile(const char* filename, cmMakefile* mf)
  120. {
  121. if (!cmSystemTools::FileExists(filename) ||
  122. cmSystemTools::FileIsDirectory(filename)) {
  123. return false;
  124. }
  125. bool parseError = false;
  126. {
  127. cmListFileParser parser(this, mf, filename);
  128. parseError = !parser.ParseFile();
  129. }
  130. return !parseError;
  131. }
  132. bool cmListFileParser::ParseFunction(const char* name, long line)
  133. {
  134. // Ininitialize a new function call.
  135. this->Function = cmListFileFunction();
  136. this->Function.Name = name;
  137. this->Function.Line = line;
  138. // Command name has already been parsed. Read the left paren.
  139. cmListFileLexer_Token* token;
  140. while ((token = cmListFileLexer_Scan(this->Lexer)) &&
  141. token->type == cmListFileLexer_Token_Space) {
  142. }
  143. if (!token) {
  144. std::ostringstream error;
  145. /* clang-format off */
  146. error << "Error in cmake code at\n" << this->FileName << ":"
  147. << cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
  148. << "Parse error. Function missing opening \"(\".";
  149. /* clang-format on */
  150. this->IssueError(error.str());
  151. return false;
  152. }
  153. if (token->type != cmListFileLexer_Token_ParenLeft) {
  154. std::ostringstream error;
  155. error << "Parse error. Expected \"(\", got "
  156. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  157. << " with text \"" << token->text << "\".";
  158. this->IssueError(error.str());
  159. return false;
  160. }
  161. // Arguments.
  162. unsigned long lastLine;
  163. unsigned long parenDepth = 0;
  164. this->Separation = SeparationOkay;
  165. while ((lastLine = cmListFileLexer_GetCurrentLine(this->Lexer),
  166. token = cmListFileLexer_Scan(this->Lexer))) {
  167. if (token->type == cmListFileLexer_Token_Space ||
  168. token->type == cmListFileLexer_Token_Newline) {
  169. this->Separation = SeparationOkay;
  170. continue;
  171. }
  172. if (token->type == cmListFileLexer_Token_ParenLeft) {
  173. parenDepth++;
  174. this->Separation = SeparationOkay;
  175. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  176. return false;
  177. }
  178. } else if (token->type == cmListFileLexer_Token_ParenRight) {
  179. if (parenDepth == 0) {
  180. return true;
  181. }
  182. parenDepth--;
  183. this->Separation = SeparationOkay;
  184. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  185. return false;
  186. }
  187. this->Separation = SeparationWarning;
  188. } else if (token->type == cmListFileLexer_Token_Identifier ||
  189. token->type == cmListFileLexer_Token_ArgumentUnquoted) {
  190. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  191. return false;
  192. }
  193. this->Separation = SeparationWarning;
  194. } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
  195. if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
  196. return false;
  197. }
  198. this->Separation = SeparationWarning;
  199. } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
  200. if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
  201. return false;
  202. }
  203. this->Separation = SeparationError;
  204. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  205. this->Separation = SeparationError;
  206. } else {
  207. // Error.
  208. std::ostringstream error;
  209. error << "Parse error. Function missing ending \")\". "
  210. << "Instead found "
  211. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  212. << " with text \"" << token->text << "\".";
  213. this->IssueError(error.str());
  214. return false;
  215. }
  216. }
  217. std::ostringstream error;
  218. cmListFileContext lfc;
  219. lfc.FilePath = this->FileName;
  220. lfc.Line = lastLine;
  221. cmListFileBacktrace lfbt = this->Backtrace;
  222. lfbt = lfbt.Push(lfc);
  223. error << "Parse error. Function missing ending \")\". "
  224. << "End of file reached.";
  225. this->Makefile->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR,
  226. error.str(), lfbt);
  227. return false;
  228. }
  229. bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
  230. cmListFileArgument::Delimiter delim)
  231. {
  232. cmListFileArgument a(token->text, delim, token->line);
  233. this->Function.Arguments.push_back(a);
  234. if (this->Separation == SeparationOkay) {
  235. return true;
  236. }
  237. bool isError = (this->Separation == SeparationError ||
  238. delim == cmListFileArgument::Bracket);
  239. std::ostringstream m;
  240. cmListFileContext lfc;
  241. lfc.FilePath = this->FileName;
  242. lfc.Line = token->line;
  243. cmListFileBacktrace lfbt = this->Backtrace;
  244. lfbt = lfbt.Push(lfc);
  245. m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
  246. << "column " << token->column << "\n"
  247. << "Argument not separated from preceding token by whitespace.";
  248. /* clang-format on */
  249. if (isError) {
  250. this->Makefile->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, m.str(), lfbt);
  251. return false;
  252. }
  253. this->Makefile->GetCMakeInstance()->IssueMessage(cmake::AUTHOR_WARNING, m.str(), lfbt);
  254. return true;
  255. }
  256. struct cmListFileBacktrace::Entry : public cmListFileContext
  257. {
  258. Entry(cmListFileContext const& lfc, Entry* up)
  259. : cmListFileContext(lfc)
  260. , Up(up)
  261. , RefCount(0)
  262. {
  263. if (this->Up) {
  264. this->Up->Ref();
  265. }
  266. }
  267. ~Entry()
  268. {
  269. if (this->Up) {
  270. this->Up->Unref();
  271. }
  272. }
  273. void Ref() { ++this->RefCount; }
  274. void Unref()
  275. {
  276. if (--this->RefCount == 0) {
  277. delete this;
  278. }
  279. }
  280. Entry* Up;
  281. unsigned int RefCount;
  282. };
  283. cmListFileBacktrace::cmListFileBacktrace(cmState::Snapshot bottom, Entry* up,
  284. cmListFileContext const& lfc)
  285. : Bottom(bottom)
  286. , Cur(new Entry(lfc, up))
  287. {
  288. assert(this->Bottom.IsValid());
  289. this->Cur->Ref();
  290. }
  291. cmListFileBacktrace::cmListFileBacktrace(cmState::Snapshot bottom, Entry* cur)
  292. : Bottom(bottom)
  293. , Cur(cur)
  294. {
  295. if (this->Cur) {
  296. assert(this->Bottom.IsValid());
  297. this->Cur->Ref();
  298. }
  299. }
  300. cmListFileBacktrace::cmListFileBacktrace()
  301. : Bottom()
  302. , Cur(CM_NULLPTR)
  303. {
  304. }
  305. cmListFileBacktrace::cmListFileBacktrace(cmState::Snapshot snapshot)
  306. : Bottom(snapshot.GetCallStackBottom())
  307. , Cur(CM_NULLPTR)
  308. {
  309. }
  310. cmListFileBacktrace::cmListFileBacktrace(cmListFileBacktrace const& r)
  311. : Bottom(r.Bottom)
  312. , Cur(r.Cur)
  313. {
  314. if (this->Cur) {
  315. assert(this->Bottom.IsValid());
  316. this->Cur->Ref();
  317. }
  318. }
  319. cmListFileBacktrace& cmListFileBacktrace::operator=(
  320. cmListFileBacktrace const& r)
  321. {
  322. cmListFileBacktrace tmp(r);
  323. std::swap(this->Cur, tmp.Cur);
  324. std::swap(this->Bottom, tmp.Bottom);
  325. return *this;
  326. }
  327. cmListFileBacktrace::~cmListFileBacktrace()
  328. {
  329. if (this->Cur) {
  330. this->Cur->Unref();
  331. }
  332. }
  333. cmListFileBacktrace cmListFileBacktrace::Push(std::string const& file) const
  334. {
  335. // We are entering a file-level scope but have not yet reached
  336. // any specific line or command invocation within it. This context
  337. // is useful to print when it is at the top but otherwise can be
  338. // skipped during call stack printing.
  339. cmListFileContext lfc;
  340. lfc.FilePath = file;
  341. return cmListFileBacktrace(this->Bottom, this->Cur, lfc);
  342. }
  343. cmListFileBacktrace cmListFileBacktrace::Push(
  344. cmListFileContext const& lfc) const
  345. {
  346. return cmListFileBacktrace(this->Bottom, this->Cur, lfc);
  347. }
  348. cmListFileBacktrace cmListFileBacktrace::Pop() const
  349. {
  350. assert(this->Cur);
  351. return cmListFileBacktrace(this->Bottom, this->Cur->Up);
  352. }
  353. cmListFileContext const& cmListFileBacktrace::Top() const
  354. {
  355. if (this->Cur) {
  356. return *this->Cur;
  357. }
  358. static cmListFileContext const empty;
  359. return empty;
  360. }
  361. void cmListFileBacktrace::PrintTitle(std::ostream& out) const
  362. {
  363. if (!this->Cur) {
  364. return;
  365. }
  366. cmOutputConverter converter(this->Bottom);
  367. cmListFileContext lfc = *this->Cur;
  368. if (!this->Bottom.GetState()->GetIsInTryCompile()) {
  369. lfc.FilePath = converter.Convert(lfc.FilePath, cmOutputConverter::HOME);
  370. }
  371. out << (lfc.Line ? " at " : " in ") << lfc;
  372. }
  373. void cmListFileBacktrace::PrintCallStack(std::ostream& out) const
  374. {
  375. if (!this->Cur || !this->Cur->Up) {
  376. return;
  377. }
  378. bool first = true;
  379. cmOutputConverter converter(this->Bottom);
  380. for (Entry* i = this->Cur->Up; i; i = i->Up) {
  381. if (i->Name.empty()) {
  382. // Skip this whole-file scope. When we get here we already will
  383. // have printed a more-specific context within the file.
  384. continue;
  385. }
  386. if (first) {
  387. first = false;
  388. out << "Call Stack (most recent call first):\n";
  389. }
  390. cmListFileContext lfc = *i;
  391. if (!this->Bottom.GetState()->GetIsInTryCompile()) {
  392. lfc.FilePath = converter.Convert(lfc.FilePath, cmOutputConverter::HOME);
  393. }
  394. out << " " << lfc << "\n";
  395. }
  396. }
  397. std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
  398. {
  399. os << lfc.FilePath;
  400. if (lfc.Line) {
  401. os << ":" << lfc.Line;
  402. if (!lfc.Name.empty()) {
  403. os << " (" << lfc.Name << ")";
  404. }
  405. }
  406. return os;
  407. }
  408. bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
  409. {
  410. if (lhs.Line != rhs.Line) {
  411. return lhs.Line < rhs.Line;
  412. }
  413. return lhs.FilePath < rhs.FilePath;
  414. }
  415. bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
  416. {
  417. return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
  418. }
  419. bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
  420. {
  421. return !(lhs == rhs);
  422. }