cmListFileCache.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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. bool ParseFile();
  22. bool ParseFunction(const char* name, long line);
  23. bool AddArgument(cmListFileLexer_Token* token,
  24. cmListFileArgument::Delimiter delim);
  25. cmListFile* ListFile;
  26. cmMakefile* Makefile;
  27. const char* FileName;
  28. cmListFileLexer* Lexer;
  29. cmListFileFunction Function;
  30. enum
  31. {
  32. SeparationOkay,
  33. SeparationWarning,
  34. SeparationError
  35. } Separation;
  36. };
  37. cmListFileParser::cmListFileParser(cmListFile* lf, cmMakefile* mf,
  38. const char* filename)
  39. : ListFile(lf)
  40. , Makefile(mf)
  41. , FileName(filename)
  42. , Lexer(cmListFileLexer_New())
  43. {
  44. }
  45. cmListFileParser::~cmListFileParser()
  46. {
  47. cmListFileLexer_Delete(this->Lexer);
  48. }
  49. bool cmListFileParser::ParseFile()
  50. {
  51. // Open the file.
  52. cmListFileLexer_BOM bom;
  53. if (!cmListFileLexer_SetFileName(this->Lexer, this->FileName, &bom)) {
  54. cmSystemTools::Error("cmListFileCache: error can not open file ",
  55. this->FileName);
  56. return false;
  57. }
  58. // Verify the Byte-Order-Mark, if any.
  59. if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
  60. cmListFileLexer_SetFileName(this->Lexer, 0, 0);
  61. std::ostringstream m;
  62. m << "File\n " << this->FileName << "\n"
  63. << "starts with a Byte-Order-Mark that is not UTF-8.";
  64. this->Makefile->IssueMessage(cmake::FATAL_ERROR, m.str());
  65. return false;
  66. }
  67. // Use a simple recursive-descent parser to process the token
  68. // stream.
  69. bool haveNewline = true;
  70. while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
  71. if (token->type == cmListFileLexer_Token_Space) {
  72. } else if (token->type == cmListFileLexer_Token_Newline) {
  73. haveNewline = true;
  74. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  75. haveNewline = false;
  76. } else if (token->type == cmListFileLexer_Token_Identifier) {
  77. if (haveNewline) {
  78. haveNewline = false;
  79. if (this->ParseFunction(token->text, token->line)) {
  80. this->ListFile->Functions.push_back(this->Function);
  81. } else {
  82. return false;
  83. }
  84. } else {
  85. std::ostringstream error;
  86. error << "Error in cmake code at\n"
  87. << this->FileName << ":" << token->line << ":\n"
  88. << "Parse error. Expected a newline, got "
  89. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  90. << " with text \"" << token->text << "\".";
  91. cmSystemTools::Error(error.str().c_str());
  92. return false;
  93. }
  94. } else {
  95. std::ostringstream error;
  96. error << "Error in cmake code at\n"
  97. << this->FileName << ":" << token->line << ":\n"
  98. << "Parse error. Expected a command name, got "
  99. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  100. << " with text \"" << token->text << "\".";
  101. cmSystemTools::Error(error.str().c_str());
  102. return false;
  103. }
  104. }
  105. return true;
  106. }
  107. bool cmListFile::ParseFile(const char* filename, bool topLevel, cmMakefile* mf)
  108. {
  109. if (!cmSystemTools::FileExists(filename) ||
  110. cmSystemTools::FileIsDirectory(filename)) {
  111. return false;
  112. }
  113. bool parseError = false;
  114. {
  115. cmListFileParser parser(this, mf, filename);
  116. parseError = !parser.ParseFile();
  117. }
  118. // do we need a cmake_policy(VERSION call?
  119. if (topLevel) {
  120. bool hasVersion = false;
  121. // search for the right policy command
  122. for (std::vector<cmListFileFunction>::iterator i = this->Functions.begin();
  123. i != this->Functions.end(); ++i) {
  124. if (cmSystemTools::LowerCase(i->Name) == "cmake_minimum_required") {
  125. hasVersion = true;
  126. break;
  127. }
  128. }
  129. // if no policy command is found this is an error if they use any
  130. // non advanced functions or a lot of functions
  131. if (!hasVersion) {
  132. bool isProblem = true;
  133. if (this->Functions.size() < 30) {
  134. // the list of simple commands DO NOT ADD TO THIS LIST!!!!!
  135. // these commands must have backwards compatibility forever and
  136. // and that is a lot longer than your tiny mind can comprehend mortal
  137. std::set<std::string> allowedCommands;
  138. allowedCommands.insert("project");
  139. allowedCommands.insert("set");
  140. allowedCommands.insert("if");
  141. allowedCommands.insert("endif");
  142. allowedCommands.insert("else");
  143. allowedCommands.insert("elseif");
  144. allowedCommands.insert("add_executable");
  145. allowedCommands.insert("add_library");
  146. allowedCommands.insert("target_link_libraries");
  147. allowedCommands.insert("option");
  148. allowedCommands.insert("message");
  149. isProblem = false;
  150. for (std::vector<cmListFileFunction>::iterator i =
  151. this->Functions.begin();
  152. i != this->Functions.end(); ++i) {
  153. std::string name = cmSystemTools::LowerCase(i->Name);
  154. if (allowedCommands.find(name) == allowedCommands.end()) {
  155. isProblem = true;
  156. break;
  157. }
  158. }
  159. }
  160. if (isProblem) {
  161. // Tell the top level cmMakefile to diagnose
  162. // this violation of CMP0000.
  163. mf->SetCheckCMP0000(true);
  164. // Implicitly set the version for the user.
  165. mf->SetPolicyVersion("2.4");
  166. }
  167. }
  168. }
  169. if (topLevel) {
  170. bool hasProject = false;
  171. // search for a project command
  172. for (std::vector<cmListFileFunction>::iterator i = this->Functions.begin();
  173. i != this->Functions.end(); ++i) {
  174. if (cmSystemTools::LowerCase(i->Name) == "project") {
  175. hasProject = true;
  176. break;
  177. }
  178. }
  179. // if no project command is found, add one
  180. if (!hasProject) {
  181. cmListFileFunction project;
  182. project.Name = "PROJECT";
  183. cmListFileArgument prj("Project", cmListFileArgument::Unquoted, 0);
  184. project.Arguments.push_back(prj);
  185. this->Functions.insert(this->Functions.begin(), project);
  186. }
  187. }
  188. return !parseError;
  189. }
  190. bool cmListFileParser::ParseFunction(const char* name, long line)
  191. {
  192. // Ininitialize a new function call.
  193. this->Function = cmListFileFunction();
  194. this->Function.Name = name;
  195. this->Function.Line = line;
  196. // Command name has already been parsed. Read the left paren.
  197. cmListFileLexer_Token* token;
  198. while ((token = cmListFileLexer_Scan(this->Lexer)) &&
  199. token->type == cmListFileLexer_Token_Space) {
  200. }
  201. if (!token) {
  202. std::ostringstream error;
  203. /* clang-format off */
  204. error << "Error in cmake code at\n" << this->FileName << ":"
  205. << cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
  206. << "Parse error. Function missing opening \"(\".";
  207. /* clang-format on */
  208. cmSystemTools::Error(error.str().c_str());
  209. return false;
  210. }
  211. if (token->type != cmListFileLexer_Token_ParenLeft) {
  212. std::ostringstream error;
  213. error << "Error in cmake code at\n"
  214. << this->FileName << ":"
  215. << cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
  216. << "Parse error. Expected \"(\", got "
  217. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  218. << " with text \"" << token->text << "\".";
  219. cmSystemTools::Error(error.str().c_str());
  220. return false;
  221. }
  222. // Arguments.
  223. unsigned long lastLine;
  224. unsigned long parenDepth = 0;
  225. this->Separation = SeparationOkay;
  226. while ((lastLine = cmListFileLexer_GetCurrentLine(this->Lexer),
  227. token = cmListFileLexer_Scan(this->Lexer))) {
  228. if (token->type == cmListFileLexer_Token_Space ||
  229. token->type == cmListFileLexer_Token_Newline) {
  230. this->Separation = SeparationOkay;
  231. continue;
  232. }
  233. if (token->type == cmListFileLexer_Token_ParenLeft) {
  234. parenDepth++;
  235. this->Separation = SeparationOkay;
  236. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  237. return false;
  238. }
  239. } else if (token->type == cmListFileLexer_Token_ParenRight) {
  240. if (parenDepth == 0) {
  241. return true;
  242. }
  243. parenDepth--;
  244. this->Separation = SeparationOkay;
  245. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  246. return false;
  247. }
  248. this->Separation = SeparationWarning;
  249. } else if (token->type == cmListFileLexer_Token_Identifier ||
  250. token->type == cmListFileLexer_Token_ArgumentUnquoted) {
  251. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  252. return false;
  253. }
  254. this->Separation = SeparationWarning;
  255. } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
  256. if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
  257. return false;
  258. }
  259. this->Separation = SeparationWarning;
  260. } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
  261. if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
  262. return false;
  263. }
  264. this->Separation = SeparationError;
  265. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  266. this->Separation = SeparationError;
  267. } else {
  268. // Error.
  269. std::ostringstream error;
  270. error << "Error in cmake code at\n"
  271. << this->FileName << ":"
  272. << cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
  273. << "Parse error. Function missing ending \")\". "
  274. << "Instead found "
  275. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  276. << " with text \"" << token->text << "\".";
  277. cmSystemTools::Error(error.str().c_str());
  278. return false;
  279. }
  280. }
  281. std::ostringstream error;
  282. error << "Error in cmake code at\n"
  283. << this->FileName << ":" << lastLine << ":\n"
  284. << "Parse error. Function missing ending \")\". "
  285. << "End of file reached.";
  286. cmSystemTools::Error(error.str().c_str());
  287. return false;
  288. }
  289. bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
  290. cmListFileArgument::Delimiter delim)
  291. {
  292. cmListFileArgument a(token->text, delim, token->line);
  293. this->Function.Arguments.push_back(a);
  294. if (this->Separation == SeparationOkay) {
  295. return true;
  296. }
  297. bool isError = (this->Separation == SeparationError ||
  298. delim == cmListFileArgument::Bracket);
  299. std::ostringstream m;
  300. /* clang-format off */
  301. m << "Syntax " << (isError? "Error":"Warning") << " in cmake code at\n"
  302. << " " << this->FileName << ":" << token->line << ":"
  303. << token->column << "\n"
  304. << "Argument not separated from preceding token by whitespace.";
  305. /* clang-format on */
  306. if (isError) {
  307. this->Makefile->IssueMessage(cmake::FATAL_ERROR, m.str());
  308. return false;
  309. } else {
  310. this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, m.str());
  311. return true;
  312. }
  313. }
  314. struct cmListFileBacktrace::Entry : public cmListFileContext
  315. {
  316. Entry(cmListFileContext const& lfc, Entry* up)
  317. : cmListFileContext(lfc)
  318. , Up(up)
  319. , RefCount(0)
  320. {
  321. if (this->Up) {
  322. this->Up->Ref();
  323. }
  324. }
  325. ~Entry()
  326. {
  327. if (this->Up) {
  328. this->Up->Unref();
  329. }
  330. }
  331. void Ref() { ++this->RefCount; }
  332. void Unref()
  333. {
  334. if (--this->RefCount == 0) {
  335. delete this;
  336. }
  337. }
  338. Entry* Up;
  339. unsigned int RefCount;
  340. };
  341. cmListFileBacktrace::cmListFileBacktrace(cmState::Snapshot bottom, Entry* up,
  342. cmListFileContext const& lfc)
  343. : Bottom(bottom)
  344. , Cur(new Entry(lfc, up))
  345. {
  346. assert(this->Bottom.IsValid());
  347. this->Cur->Ref();
  348. }
  349. cmListFileBacktrace::cmListFileBacktrace(cmState::Snapshot bottom, Entry* cur)
  350. : Bottom(bottom)
  351. , Cur(cur)
  352. {
  353. if (this->Cur) {
  354. assert(this->Bottom.IsValid());
  355. this->Cur->Ref();
  356. }
  357. }
  358. cmListFileBacktrace::cmListFileBacktrace()
  359. : Bottom()
  360. , Cur(0)
  361. {
  362. }
  363. cmListFileBacktrace::cmListFileBacktrace(cmState::Snapshot snapshot)
  364. : Bottom(snapshot.GetCallStackBottom())
  365. , Cur(0)
  366. {
  367. }
  368. cmListFileBacktrace::cmListFileBacktrace(cmListFileBacktrace const& r)
  369. : Bottom(r.Bottom)
  370. , Cur(r.Cur)
  371. {
  372. if (this->Cur) {
  373. assert(this->Bottom.IsValid());
  374. this->Cur->Ref();
  375. }
  376. }
  377. cmListFileBacktrace& cmListFileBacktrace::operator=(
  378. cmListFileBacktrace const& r)
  379. {
  380. cmListFileBacktrace tmp(r);
  381. std::swap(this->Cur, tmp.Cur);
  382. std::swap(this->Bottom, tmp.Bottom);
  383. return *this;
  384. }
  385. cmListFileBacktrace::~cmListFileBacktrace()
  386. {
  387. if (this->Cur) {
  388. this->Cur->Unref();
  389. }
  390. }
  391. cmListFileBacktrace cmListFileBacktrace::Push(std::string const& file) const
  392. {
  393. // We are entering a file-level scope but have not yet reached
  394. // any specific line or command invocation within it. This context
  395. // is useful to print when it is at the top but otherwise can be
  396. // skipped during call stack printing.
  397. cmListFileContext lfc;
  398. lfc.FilePath = file;
  399. return cmListFileBacktrace(this->Bottom, this->Cur, lfc);
  400. }
  401. cmListFileBacktrace cmListFileBacktrace::Push(
  402. cmListFileContext const& lfc) const
  403. {
  404. return cmListFileBacktrace(this->Bottom, this->Cur, lfc);
  405. }
  406. cmListFileBacktrace cmListFileBacktrace::Pop() const
  407. {
  408. assert(this->Cur);
  409. return cmListFileBacktrace(this->Bottom, this->Cur->Up);
  410. }
  411. cmListFileContext const& cmListFileBacktrace::Top() const
  412. {
  413. if (this->Cur) {
  414. return *this->Cur;
  415. } else {
  416. static cmListFileContext const empty;
  417. return empty;
  418. }
  419. }
  420. void cmListFileBacktrace::PrintTitle(std::ostream& out) const
  421. {
  422. if (!this->Cur) {
  423. return;
  424. }
  425. cmOutputConverter converter(this->Bottom);
  426. cmListFileContext lfc = *this->Cur;
  427. if (!this->Bottom.GetState()->GetIsInTryCompile()) {
  428. lfc.FilePath = converter.Convert(lfc.FilePath, cmOutputConverter::HOME);
  429. }
  430. out << (lfc.Line ? " at " : " in ") << lfc;
  431. }
  432. void cmListFileBacktrace::PrintCallStack(std::ostream& out) const
  433. {
  434. if (!this->Cur || !this->Cur->Up) {
  435. return;
  436. }
  437. bool first = true;
  438. cmOutputConverter converter(this->Bottom);
  439. for (Entry* i = this->Cur->Up; i; i = i->Up) {
  440. if (i->Name.empty()) {
  441. // Skip this whole-file scope. When we get here we already will
  442. // have printed a more-specific context within the file.
  443. continue;
  444. }
  445. if (first) {
  446. first = false;
  447. out << "Call Stack (most recent call first):\n";
  448. }
  449. cmListFileContext lfc = *i;
  450. if (!this->Bottom.GetState()->GetIsInTryCompile()) {
  451. lfc.FilePath = converter.Convert(lfc.FilePath, cmOutputConverter::HOME);
  452. }
  453. out << " " << lfc << "\n";
  454. }
  455. }
  456. std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
  457. {
  458. os << lfc.FilePath;
  459. if (lfc.Line) {
  460. os << ":" << lfc.Line;
  461. if (!lfc.Name.empty()) {
  462. os << " (" << lfc.Name << ")";
  463. }
  464. }
  465. return os;
  466. }
  467. bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
  468. {
  469. if (lhs.Line != rhs.Line) {
  470. return lhs.Line < rhs.Line;
  471. }
  472. return lhs.FilePath < rhs.FilePath;
  473. }
  474. bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
  475. {
  476. return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
  477. }
  478. bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
  479. {
  480. return !(lhs == rhs);
  481. }