cmListFileCache.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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 "cmListFileCache.h"
  4. #include <cassert>
  5. #include <memory>
  6. #include <sstream>
  7. #include <utility>
  8. #ifdef _WIN32
  9. # include <cmsys/Encoding.hxx>
  10. #endif
  11. #include "cmListFileLexer.h"
  12. #include "cmMessageType.h"
  13. #include "cmMessenger.h"
  14. #include "cmState.h"
  15. #include "cmStringAlgorithms.h"
  16. #include "cmSystemTools.h"
  17. struct cmListFileParser
  18. {
  19. cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
  20. cmMessenger* messenger);
  21. ~cmListFileParser();
  22. cmListFileParser(const cmListFileParser&) = delete;
  23. cmListFileParser& operator=(const cmListFileParser&) = delete;
  24. void IssueFileOpenError(std::string const& text) const;
  25. void IssueError(std::string const& text) const;
  26. bool ParseFile(const char* filename);
  27. bool ParseString(const char* str, const char* virtual_filename);
  28. bool Parse();
  29. bool ParseFunction(const char* name, long line);
  30. bool AddArgument(cmListFileLexer_Token* token,
  31. cmListFileArgument::Delimiter delim);
  32. cm::optional<cmListFileContext> CheckNesting() const;
  33. cmListFile* ListFile;
  34. cmListFileBacktrace Backtrace;
  35. cmMessenger* Messenger;
  36. const char* FileName;
  37. cmListFileLexer* Lexer;
  38. std::string FunctionName;
  39. long FunctionLine;
  40. std::vector<cmListFileArgument> FunctionArguments;
  41. enum
  42. {
  43. SeparationOkay,
  44. SeparationWarning,
  45. SeparationError
  46. } Separation;
  47. };
  48. cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
  49. cmMessenger* messenger)
  50. : ListFile(lf)
  51. , Backtrace(std::move(lfbt))
  52. , Messenger(messenger)
  53. , FileName(nullptr)
  54. , Lexer(cmListFileLexer_New())
  55. {
  56. }
  57. cmListFileParser::~cmListFileParser()
  58. {
  59. cmListFileLexer_Delete(this->Lexer);
  60. }
  61. void cmListFileParser::IssueFileOpenError(const std::string& text) const
  62. {
  63. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text,
  64. this->Backtrace);
  65. }
  66. void cmListFileParser::IssueError(const std::string& text) const
  67. {
  68. cmListFileContext lfc;
  69. lfc.FilePath = this->FileName;
  70. lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer);
  71. cmListFileBacktrace lfbt = this->Backtrace;
  72. lfbt = lfbt.Push(lfc);
  73. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text, lfbt);
  74. cmSystemTools::SetFatalErrorOccured();
  75. }
  76. bool cmListFileParser::ParseFile(const char* filename)
  77. {
  78. this->FileName = filename;
  79. #ifdef _WIN32
  80. std::string expandedFileName = cmsys::Encoding::ToNarrow(
  81. cmSystemTools::ConvertToWindowsExtendedPath(filename));
  82. filename = expandedFileName.c_str();
  83. #endif
  84. // Open the file.
  85. cmListFileLexer_BOM bom;
  86. if (!cmListFileLexer_SetFileName(this->Lexer, filename, &bom)) {
  87. this->IssueFileOpenError("cmListFileCache: error can not open file.");
  88. return false;
  89. }
  90. if (bom == cmListFileLexer_BOM_Broken) {
  91. cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
  92. this->IssueFileOpenError("Error while reading Byte-Order-Mark. "
  93. "File not seekable?");
  94. return false;
  95. }
  96. // Verify the Byte-Order-Mark, if any.
  97. if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
  98. cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
  99. this->IssueFileOpenError(
  100. "File starts with a Byte-Order-Mark that is not UTF-8.");
  101. return false;
  102. }
  103. return this->Parse();
  104. }
  105. bool cmListFileParser::ParseString(const char* str,
  106. const char* virtual_filename)
  107. {
  108. this->FileName = virtual_filename;
  109. if (!cmListFileLexer_SetString(this->Lexer, str)) {
  110. this->IssueFileOpenError("cmListFileCache: cannot allocate buffer.");
  111. return false;
  112. }
  113. return this->Parse();
  114. }
  115. bool cmListFileParser::Parse()
  116. {
  117. // Use a simple recursive-descent parser to process the token
  118. // stream.
  119. bool haveNewline = true;
  120. while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
  121. if (token->type == cmListFileLexer_Token_Space) {
  122. } else if (token->type == cmListFileLexer_Token_Newline) {
  123. haveNewline = true;
  124. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  125. haveNewline = false;
  126. } else if (token->type == cmListFileLexer_Token_Identifier) {
  127. if (haveNewline) {
  128. haveNewline = false;
  129. if (this->ParseFunction(token->text, token->line)) {
  130. this->ListFile->Functions.emplace_back(
  131. std::move(this->FunctionName), this->FunctionLine,
  132. std::move(this->FunctionArguments));
  133. } else {
  134. return false;
  135. }
  136. } else {
  137. std::ostringstream error;
  138. error << "Parse error. Expected a newline, got "
  139. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  140. << " with text \"" << token->text << "\".";
  141. this->IssueError(error.str());
  142. return false;
  143. }
  144. } else {
  145. std::ostringstream error;
  146. error << "Parse error. Expected a command name, got "
  147. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  148. << " with text \"" << token->text << "\".";
  149. this->IssueError(error.str());
  150. return false;
  151. }
  152. }
  153. // Check if all functions are nested properly.
  154. if (auto badNesting = this->CheckNesting()) {
  155. this->Messenger->IssueMessage(
  156. MessageType::FATAL_ERROR,
  157. "Flow control statements are not properly nested.",
  158. this->Backtrace.Push(*badNesting));
  159. cmSystemTools::SetFatalErrorOccured();
  160. return false;
  161. }
  162. return true;
  163. }
  164. bool cmListFile::ParseFile(const char* filename, cmMessenger* messenger,
  165. cmListFileBacktrace const& lfbt)
  166. {
  167. if (!cmSystemTools::FileExists(filename) ||
  168. cmSystemTools::FileIsDirectory(filename)) {
  169. return false;
  170. }
  171. bool parseError = false;
  172. {
  173. cmListFileParser parser(this, lfbt, messenger);
  174. parseError = !parser.ParseFile(filename);
  175. }
  176. return !parseError;
  177. }
  178. bool cmListFile::ParseString(const char* str, const char* virtual_filename,
  179. cmMessenger* messenger,
  180. const cmListFileBacktrace& lfbt)
  181. {
  182. bool parseError = false;
  183. {
  184. cmListFileParser parser(this, lfbt, messenger);
  185. parseError = !parser.ParseString(str, virtual_filename);
  186. }
  187. return !parseError;
  188. }
  189. bool cmListFileParser::ParseFunction(const char* name, long line)
  190. {
  191. // Ininitialize a new function call.
  192. this->FunctionName = name;
  193. this->FunctionLine = line;
  194. // Command name has already been parsed. Read the left paren.
  195. cmListFileLexer_Token* token;
  196. while ((token = cmListFileLexer_Scan(this->Lexer)) &&
  197. token->type == cmListFileLexer_Token_Space) {
  198. }
  199. if (!token) {
  200. std::ostringstream error;
  201. /* clang-format off */
  202. error << "Unexpected end of file.\n"
  203. << "Parse error. Function missing opening \"(\".";
  204. /* clang-format on */
  205. this->IssueError(error.str());
  206. return false;
  207. }
  208. if (token->type != cmListFileLexer_Token_ParenLeft) {
  209. std::ostringstream error;
  210. error << "Parse error. Expected \"(\", got "
  211. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  212. << " with text \"" << token->text << "\".";
  213. this->IssueError(error.str());
  214. return false;
  215. }
  216. // Arguments.
  217. unsigned long parenDepth = 0;
  218. this->Separation = SeparationOkay;
  219. while ((token = cmListFileLexer_Scan(this->Lexer))) {
  220. if (token->type == cmListFileLexer_Token_Space ||
  221. token->type == cmListFileLexer_Token_Newline) {
  222. this->Separation = SeparationOkay;
  223. continue;
  224. }
  225. if (token->type == cmListFileLexer_Token_ParenLeft) {
  226. parenDepth++;
  227. this->Separation = SeparationOkay;
  228. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  229. return false;
  230. }
  231. } else if (token->type == cmListFileLexer_Token_ParenRight) {
  232. if (parenDepth == 0) {
  233. return true;
  234. }
  235. parenDepth--;
  236. this->Separation = SeparationOkay;
  237. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  238. return false;
  239. }
  240. this->Separation = SeparationWarning;
  241. } else if (token->type == cmListFileLexer_Token_Identifier ||
  242. token->type == cmListFileLexer_Token_ArgumentUnquoted) {
  243. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  244. return false;
  245. }
  246. this->Separation = SeparationWarning;
  247. } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
  248. if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
  249. return false;
  250. }
  251. this->Separation = SeparationWarning;
  252. } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
  253. if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
  254. return false;
  255. }
  256. this->Separation = SeparationError;
  257. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  258. this->Separation = SeparationError;
  259. } else {
  260. // Error.
  261. std::ostringstream error;
  262. error << "Parse error. Function missing ending \")\". "
  263. << "Instead found "
  264. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  265. << " with text \"" << token->text << "\".";
  266. this->IssueError(error.str());
  267. return false;
  268. }
  269. }
  270. std::ostringstream error;
  271. cmListFileContext lfc;
  272. lfc.FilePath = this->FileName;
  273. lfc.Line = line;
  274. cmListFileBacktrace lfbt = this->Backtrace;
  275. lfbt = lfbt.Push(lfc);
  276. error << "Parse error. Function missing ending \")\". "
  277. << "End of file reached.";
  278. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, error.str(), lfbt);
  279. return false;
  280. }
  281. bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
  282. cmListFileArgument::Delimiter delim)
  283. {
  284. this->FunctionArguments.emplace_back(token->text, delim, token->line);
  285. if (this->Separation == SeparationOkay) {
  286. return true;
  287. }
  288. bool isError = (this->Separation == SeparationError ||
  289. delim == cmListFileArgument::Bracket);
  290. std::ostringstream m;
  291. cmListFileContext lfc;
  292. lfc.FilePath = this->FileName;
  293. lfc.Line = token->line;
  294. cmListFileBacktrace lfbt = this->Backtrace;
  295. lfbt = lfbt.Push(lfc);
  296. m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
  297. << "column " << token->column << "\n"
  298. << "Argument not separated from preceding token by whitespace.";
  299. /* clang-format on */
  300. if (isError) {
  301. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, m.str(), lfbt);
  302. return false;
  303. }
  304. this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), lfbt);
  305. return true;
  306. }
  307. namespace {
  308. enum class NestingStateEnum
  309. {
  310. If,
  311. Else,
  312. While,
  313. Foreach,
  314. Function,
  315. Macro,
  316. };
  317. struct NestingState
  318. {
  319. NestingStateEnum State;
  320. cmListFileContext Context;
  321. };
  322. bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state)
  323. {
  324. return !stack.empty() && stack.back().State == state;
  325. }
  326. }
  327. cm::optional<cmListFileContext> cmListFileParser::CheckNesting() const
  328. {
  329. std::vector<NestingState> stack;
  330. for (auto const& func : this->ListFile->Functions) {
  331. auto const& name = func.LowerCaseName();
  332. if (name == "if") {
  333. stack.push_back({
  334. NestingStateEnum::If,
  335. cmListFileContext::FromCommandContext(func, this->FileName),
  336. });
  337. } else if (name == "elseif") {
  338. if (!TopIs(stack, NestingStateEnum::If)) {
  339. return cmListFileContext::FromCommandContext(func, this->FileName);
  340. }
  341. stack.back() = {
  342. NestingStateEnum::If,
  343. cmListFileContext::FromCommandContext(func, this->FileName),
  344. };
  345. } else if (name == "else") {
  346. if (!TopIs(stack, NestingStateEnum::If)) {
  347. return cmListFileContext::FromCommandContext(func, this->FileName);
  348. }
  349. stack.back() = {
  350. NestingStateEnum::Else,
  351. cmListFileContext::FromCommandContext(func, this->FileName),
  352. };
  353. } else if (name == "endif") {
  354. if (!TopIs(stack, NestingStateEnum::If) &&
  355. !TopIs(stack, NestingStateEnum::Else)) {
  356. return cmListFileContext::FromCommandContext(func, this->FileName);
  357. }
  358. stack.pop_back();
  359. } else if (name == "while") {
  360. stack.push_back({
  361. NestingStateEnum::While,
  362. cmListFileContext::FromCommandContext(func, this->FileName),
  363. });
  364. } else if (name == "endwhile") {
  365. if (!TopIs(stack, NestingStateEnum::While)) {
  366. return cmListFileContext::FromCommandContext(func, this->FileName);
  367. }
  368. stack.pop_back();
  369. } else if (name == "foreach") {
  370. stack.push_back({
  371. NestingStateEnum::Foreach,
  372. cmListFileContext::FromCommandContext(func, this->FileName),
  373. });
  374. } else if (name == "endforeach") {
  375. if (!TopIs(stack, NestingStateEnum::Foreach)) {
  376. return cmListFileContext::FromCommandContext(func, this->FileName);
  377. }
  378. stack.pop_back();
  379. } else if (name == "function") {
  380. stack.push_back({
  381. NestingStateEnum::Function,
  382. cmListFileContext::FromCommandContext(func, this->FileName),
  383. });
  384. } else if (name == "endfunction") {
  385. if (!TopIs(stack, NestingStateEnum::Function)) {
  386. return cmListFileContext::FromCommandContext(func, this->FileName);
  387. }
  388. stack.pop_back();
  389. } else if (name == "macro") {
  390. stack.push_back({
  391. NestingStateEnum::Macro,
  392. cmListFileContext::FromCommandContext(func, this->FileName),
  393. });
  394. } else if (name == "endmacro") {
  395. if (!TopIs(stack, NestingStateEnum::Macro)) {
  396. return cmListFileContext::FromCommandContext(func, this->FileName);
  397. }
  398. stack.pop_back();
  399. }
  400. }
  401. if (!stack.empty()) {
  402. return stack.back().Context;
  403. }
  404. return cm::nullopt;
  405. }
  406. // We hold either the bottom scope of a directory or a call/file context.
  407. // Discriminate these cases via the parent pointer.
  408. struct cmListFileBacktrace::Entry
  409. {
  410. Entry(cmStateSnapshot bottom)
  411. : Bottom(bottom)
  412. {
  413. }
  414. Entry(std::shared_ptr<Entry const> parent, cmListFileContext lfc)
  415. : Context(std::move(lfc))
  416. , Parent(std::move(parent))
  417. {
  418. }
  419. ~Entry()
  420. {
  421. if (this->Parent) {
  422. this->Context.~cmListFileContext();
  423. } else {
  424. this->Bottom.~cmStateSnapshot();
  425. }
  426. }
  427. bool IsBottom() const { return !this->Parent; }
  428. union
  429. {
  430. cmStateSnapshot Bottom;
  431. cmListFileContext Context;
  432. };
  433. std::shared_ptr<Entry const> Parent;
  434. };
  435. cmListFileBacktrace::cmListFileBacktrace(cmStateSnapshot const& snapshot)
  436. : TopEntry(std::make_shared<Entry const>(snapshot.GetCallStackBottom()))
  437. {
  438. }
  439. /* NOLINTNEXTLINE(performance-unnecessary-value-param) */
  440. cmListFileBacktrace::cmListFileBacktrace(std::shared_ptr<Entry const> parent,
  441. cmListFileContext const& lfc)
  442. : TopEntry(std::make_shared<Entry const>(std::move(parent), lfc))
  443. {
  444. }
  445. cmListFileBacktrace::cmListFileBacktrace(std::shared_ptr<Entry const> top)
  446. : TopEntry(std::move(top))
  447. {
  448. }
  449. cmStateSnapshot cmListFileBacktrace::GetBottom() const
  450. {
  451. cmStateSnapshot bottom;
  452. if (Entry const* cur = this->TopEntry.get()) {
  453. while (Entry const* parent = cur->Parent.get()) {
  454. cur = parent;
  455. }
  456. bottom = cur->Bottom;
  457. }
  458. return bottom;
  459. }
  460. cmListFileBacktrace cmListFileBacktrace::Push(std::string const& file) const
  461. {
  462. // We are entering a file-level scope but have not yet reached
  463. // any specific line or command invocation within it. This context
  464. // is useful to print when it is at the top but otherwise can be
  465. // skipped during call stack printing.
  466. cmListFileContext lfc;
  467. lfc.FilePath = file;
  468. return this->Push(lfc);
  469. }
  470. cmListFileBacktrace cmListFileBacktrace::Push(
  471. cmListFileContext const& lfc) const
  472. {
  473. assert(this->TopEntry);
  474. assert(!this->TopEntry->IsBottom() || this->TopEntry->Bottom.IsValid());
  475. return cmListFileBacktrace(this->TopEntry, lfc);
  476. }
  477. cmListFileBacktrace cmListFileBacktrace::Pop() const
  478. {
  479. assert(this->TopEntry);
  480. assert(!this->TopEntry->IsBottom());
  481. return cmListFileBacktrace(this->TopEntry->Parent);
  482. }
  483. cmListFileContext const& cmListFileBacktrace::Top() const
  484. {
  485. assert(this->TopEntry);
  486. assert(!this->TopEntry->IsBottom());
  487. return this->TopEntry->Context;
  488. }
  489. void cmListFileBacktrace::PrintTitle(std::ostream& out) const
  490. {
  491. // The title exists only if we have a call on top of the bottom.
  492. if (!this->TopEntry || this->TopEntry->IsBottom()) {
  493. return;
  494. }
  495. cmListFileContext lfc = this->TopEntry->Context;
  496. cmStateSnapshot bottom = this->GetBottom();
  497. if (bottom.GetState()->GetProjectKind() == cmState::ProjectKind::Normal) {
  498. lfc.FilePath = cmSystemTools::RelativeIfUnder(
  499. bottom.GetState()->GetSourceDirectory(), lfc.FilePath);
  500. }
  501. out << (lfc.Line ? " at " : " in ") << lfc;
  502. }
  503. void cmListFileBacktrace::PrintCallStack(std::ostream& out) const
  504. {
  505. // The call stack exists only if we have at least two calls on top
  506. // of the bottom.
  507. if (!this->TopEntry || this->TopEntry->IsBottom() ||
  508. this->TopEntry->Parent->IsBottom()) {
  509. return;
  510. }
  511. bool first = true;
  512. cmStateSnapshot bottom = this->GetBottom();
  513. for (Entry const* cur = this->TopEntry->Parent.get(); !cur->IsBottom();
  514. cur = cur->Parent.get()) {
  515. if (cur->Context.Name.empty() &&
  516. cur->Context.Line != cmListFileContext::DeferPlaceholderLine) {
  517. // Skip this whole-file scope. When we get here we already will
  518. // have printed a more-specific context within the file.
  519. continue;
  520. }
  521. if (first) {
  522. first = false;
  523. out << "Call Stack (most recent call first):\n";
  524. }
  525. cmListFileContext lfc = cur->Context;
  526. if (bottom.GetState()->GetProjectKind() == cmState::ProjectKind::Normal) {
  527. lfc.FilePath = cmSystemTools::RelativeIfUnder(
  528. bottom.GetState()->GetSourceDirectory(), lfc.FilePath);
  529. }
  530. out << " " << lfc << "\n";
  531. }
  532. }
  533. size_t cmListFileBacktrace::Depth() const
  534. {
  535. size_t depth = 0;
  536. if (Entry const* cur = this->TopEntry.get()) {
  537. for (; !cur->IsBottom(); cur = cur->Parent.get()) {
  538. ++depth;
  539. }
  540. }
  541. return depth;
  542. }
  543. bool cmListFileBacktrace::Empty() const
  544. {
  545. return !this->TopEntry || this->TopEntry->IsBottom();
  546. }
  547. std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
  548. {
  549. os << lfc.FilePath;
  550. if (lfc.Line > 0) {
  551. os << ":" << lfc.Line;
  552. if (!lfc.Name.empty()) {
  553. os << " (" << lfc.Name << ")";
  554. }
  555. } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
  556. os << ":DEFERRED";
  557. }
  558. return os;
  559. }
  560. bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
  561. {
  562. if (lhs.Line != rhs.Line) {
  563. return lhs.Line < rhs.Line;
  564. }
  565. return lhs.FilePath < rhs.FilePath;
  566. }
  567. bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
  568. {
  569. return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
  570. }
  571. bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
  572. {
  573. return !(lhs == rhs);
  574. }
  575. std::ostream& operator<<(std::ostream& os, BT<std::string> const& s)
  576. {
  577. return os << s.Value;
  578. }
  579. std::vector<BT<std::string>> ExpandListWithBacktrace(
  580. std::string const& list, cmListFileBacktrace const& bt)
  581. {
  582. std::vector<BT<std::string>> result;
  583. std::vector<std::string> tmp = cmExpandedList(list);
  584. result.reserve(tmp.size());
  585. for (std::string& i : tmp) {
  586. result.emplace_back(std::move(i), bt);
  587. }
  588. return result;
  589. }