cmCMakeLanguageCommand.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmCMakeLanguageCommand.h"
  4. #include <algorithm>
  5. #include <array>
  6. #include <cstddef>
  7. #include <string>
  8. #include <utility>
  9. #include <cm/optional>
  10. #include <cm/string_view>
  11. #include <cmext/string_view>
  12. #include "cmArgumentParser.h"
  13. #include "cmArgumentParserTypes.h"
  14. #include "cmDependencyProvider.h"
  15. #include "cmExecutionStatus.h"
  16. #include "cmExperimental.h"
  17. #include "cmGlobalGenerator.h"
  18. #include "cmListFileCache.h"
  19. #include "cmMakefile.h"
  20. #include "cmMessageType.h" // IWYU pragma: keep
  21. #include "cmRange.h"
  22. #include "cmState.h"
  23. #include "cmStringAlgorithms.h"
  24. #include "cmSystemTools.h"
  25. #include "cmValue.h"
  26. #include "cmake.h"
  27. namespace {
  28. bool FatalError(cmExecutionStatus& status, std::string const& error)
  29. {
  30. status.SetError(error);
  31. cmSystemTools::SetFatalErrorOccurred();
  32. return false;
  33. }
  34. std::array<cm::static_string_view, 14> InvalidCommands{
  35. { // clang-format off
  36. "function"_s, "endfunction"_s,
  37. "macro"_s, "endmacro"_s,
  38. "if"_s, "elseif"_s, "else"_s, "endif"_s,
  39. "while"_s, "endwhile"_s,
  40. "foreach"_s, "endforeach"_s,
  41. "block"_s, "endblock"_s
  42. } // clang-format on
  43. };
  44. std::array<cm::static_string_view, 1> InvalidDeferCommands{
  45. {
  46. // clang-format off
  47. "return"_s,
  48. } // clang-format on
  49. };
  50. struct Defer
  51. {
  52. std::string Id;
  53. std::string IdVar;
  54. cmMakefile* Directory = nullptr;
  55. };
  56. bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
  57. std::string const& callCommand,
  58. size_t startArg, cm::optional<Defer> defer,
  59. cmExecutionStatus& status)
  60. {
  61. // ensure specified command is valid
  62. // start/end flow control commands are not allowed
  63. auto cmd = cmSystemTools::LowerCase(callCommand);
  64. if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
  65. InvalidCommands.cend()) {
  66. return FatalError(status,
  67. cmStrCat("invalid command specified: "_s, callCommand));
  68. }
  69. if (defer &&
  70. std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
  71. cmd) != InvalidDeferCommands.cend()) {
  72. return FatalError(status,
  73. cmStrCat("invalid command specified: "_s, callCommand));
  74. }
  75. cmMakefile& makefile = status.GetMakefile();
  76. cmListFileContext context = makefile.GetBacktrace().Top();
  77. std::vector<cmListFileArgument> funcArgs;
  78. funcArgs.reserve(args.size() - startArg);
  79. // The rest of the arguments are passed to the function call above
  80. for (size_t i = startArg; i < args.size(); ++i) {
  81. funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
  82. }
  83. cmListFileFunction func{ callCommand, context.Line, context.Line,
  84. std::move(funcArgs) };
  85. if (defer) {
  86. if (defer->Id.empty()) {
  87. defer->Id = makefile.NewDeferId();
  88. }
  89. if (!defer->IdVar.empty()) {
  90. makefile.AddDefinition(defer->IdVar, defer->Id);
  91. }
  92. cmMakefile* deferMakefile =
  93. defer->Directory ? defer->Directory : &makefile;
  94. if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
  95. return FatalError(
  96. status,
  97. cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s,
  98. deferMakefile->GetCurrentBinaryDirectory(),
  99. "\nat this time."_s));
  100. }
  101. return true;
  102. }
  103. return makefile.ExecuteCommand(func, status);
  104. }
  105. bool cmCMakeLanguageCommandDEFER(Defer const& defer,
  106. std::vector<std::string> const& args,
  107. size_t arg, cmExecutionStatus& status)
  108. {
  109. cmMakefile* deferMakefile =
  110. defer.Directory ? defer.Directory : &status.GetMakefile();
  111. if (args[arg] == "CANCEL_CALL"_s) {
  112. ++arg; // Consume CANCEL_CALL.
  113. auto ids = cmMakeRange(args).advance(arg);
  114. for (std::string const& id : ids) {
  115. if (id[0] >= 'A' && id[0] <= 'Z') {
  116. return FatalError(
  117. status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id));
  118. }
  119. if (!deferMakefile->DeferCancelCall(id)) {
  120. return FatalError(
  121. status,
  122. cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s,
  123. deferMakefile->GetCurrentBinaryDirectory(),
  124. "\nat this time."_s));
  125. }
  126. }
  127. return true;
  128. }
  129. if (args[arg] == "GET_CALL_IDS"_s) {
  130. ++arg; // Consume GET_CALL_IDS.
  131. if (arg == args.size()) {
  132. return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
  133. }
  134. std::string const& var = args[arg++];
  135. if (arg != args.size()) {
  136. return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
  137. }
  138. cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
  139. if (!ids) {
  140. return FatalError(
  141. status,
  142. cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s,
  143. deferMakefile->GetCurrentBinaryDirectory(),
  144. "\nat this time."_s));
  145. }
  146. status.GetMakefile().AddDefinition(var, *ids);
  147. return true;
  148. }
  149. if (args[arg] == "GET_CALL"_s) {
  150. ++arg; // Consume GET_CALL.
  151. if (arg == args.size()) {
  152. return FatalError(status, "DEFER GET_CALL missing id");
  153. }
  154. std::string const& id = args[arg++];
  155. if (arg == args.size()) {
  156. return FatalError(status, "DEFER GET_CALL missing output variable");
  157. }
  158. std::string const& var = args[arg++];
  159. if (arg != args.size()) {
  160. return FatalError(status, "DEFER GET_CALL given too many arguments");
  161. }
  162. if (id.empty()) {
  163. return FatalError(status, "DEFER GET_CALL id may not be empty");
  164. }
  165. if (id[0] >= 'A' && id[0] <= 'Z') {
  166. return FatalError(status,
  167. cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
  168. }
  169. cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
  170. if (!call) {
  171. return FatalError(
  172. status,
  173. cmStrCat("DEFER GET_CALL may not access directory:\n "_s,
  174. deferMakefile->GetCurrentBinaryDirectory(),
  175. "\nat this time."_s));
  176. }
  177. status.GetMakefile().AddDefinition(var, *call);
  178. return true;
  179. }
  180. return FatalError(status,
  181. cmStrCat("DEFER operation unknown: "_s, args[arg]));
  182. }
  183. bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
  184. cmExecutionStatus& status)
  185. {
  186. cmMakefile& makefile = status.GetMakefile();
  187. cmListFileContext context = makefile.GetBacktrace().Top();
  188. std::vector<std::string> expandedArgs;
  189. makefile.ExpandArguments(args, expandedArgs);
  190. if (expandedArgs.size() < 2) {
  191. return FatalError(status, "called with incorrect number of arguments");
  192. }
  193. if (expandedArgs[1] != "CODE") {
  194. auto code_iter =
  195. std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
  196. if (code_iter == expandedArgs.end()) {
  197. return FatalError(status, "called without CODE argument");
  198. }
  199. return FatalError(
  200. status,
  201. "called with unsupported arguments between EVAL and CODE arguments");
  202. }
  203. std::string const code =
  204. cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
  205. return makefile.ReadListFileAsString(
  206. code, cmStrCat(context.FilePath, ':', context.Line, ":EVAL"));
  207. }
  208. bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
  209. std::vector<std::string> const& args, cmExecutionStatus& status)
  210. {
  211. cmState* state = status.GetMakefile().GetState();
  212. if (!state->InTopLevelIncludes()) {
  213. return FatalError(
  214. status,
  215. "Dependency providers can only be set as part of the first call to "
  216. "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
  217. "can only be called while the first project() command processes files "
  218. "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
  219. }
  220. struct SetProviderArgs
  221. {
  222. std::string Command;
  223. ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
  224. };
  225. auto const ArgsParser =
  226. cmArgumentParser<SetProviderArgs>()
  227. .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
  228. .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
  229. std::vector<std::string> unparsed;
  230. auto parsedArgs = ArgsParser.Parse(args, &unparsed);
  231. if (!unparsed.empty()) {
  232. return FatalError(
  233. status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), '"'));
  234. }
  235. // We store the command that FetchContent_MakeAvailable() can call in a
  236. // global (but considered internal) property. If the provider doesn't
  237. // support this method, we set this property to an empty string instead.
  238. // This simplifies the logic in FetchContent_MakeAvailable() and doesn't
  239. // require us to define a new internal command or sub-command.
  240. std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
  241. if (parsedArgs.Command.empty()) {
  242. if (!parsedArgs.Methods.empty()) {
  243. return FatalError(status,
  244. "Must specify a non-empty command name when provider "
  245. "methods are given");
  246. }
  247. state->ClearDependencyProvider();
  248. state->SetGlobalProperty(fcmasProperty, "");
  249. return true;
  250. }
  251. cmState::Command command = state->GetCommand(parsedArgs.Command);
  252. if (!command) {
  253. return FatalError(status,
  254. cmStrCat("Command \"", parsedArgs.Command,
  255. "\" is not a defined command"));
  256. }
  257. if (parsedArgs.Methods.empty()) {
  258. return FatalError(status, "Must specify at least one provider method");
  259. }
  260. bool supportsFetchContentMakeAvailableSerial = false;
  261. std::vector<cmDependencyProvider::Method> methods;
  262. for (auto const& method : parsedArgs.Methods) {
  263. if (method == "FIND_PACKAGE") {
  264. methods.emplace_back(cmDependencyProvider::Method::FindPackage);
  265. } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
  266. supportsFetchContentMakeAvailableSerial = true;
  267. methods.emplace_back(
  268. cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
  269. } else {
  270. return FatalError(
  271. status,
  272. cmStrCat("Unknown dependency provider method \"", method, '"'));
  273. }
  274. }
  275. state->SetDependencyProvider({ parsedArgs.Command, methods });
  276. state->SetGlobalProperty(
  277. fcmasProperty,
  278. supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : "");
  279. return true;
  280. }
  281. bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(
  282. std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
  283. {
  284. cmMakefile& makefile = status.GetMakefile();
  285. std::vector<std::string> expandedArgs;
  286. makefile.ExpandArguments(args, expandedArgs);
  287. if (args.size() < 2 || expandedArgs.size() > 2) {
  288. return FatalError(
  289. status,
  290. "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument");
  291. }
  292. Message::LogLevel logLevel = makefile.GetCurrentLogLevel();
  293. std::string outputValue = cmake::LogLevelToString(logLevel);
  294. std::string const& outputVariable = expandedArgs[1];
  295. makefile.AddDefinition(outputVariable, outputValue);
  296. return true;
  297. }
  298. bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(
  299. std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
  300. {
  301. cmMakefile& makefile = status.GetMakefile();
  302. std::vector<std::string> expandedArgs;
  303. makefile.ExpandArguments(args, expandedArgs);
  304. if (expandedArgs.size() != 3) {
  305. return FatalError(status,
  306. "sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects "
  307. "exactly two arguments");
  308. }
  309. auto const& featureName = expandedArgs[1];
  310. auto const& variableName = expandedArgs[2];
  311. if (auto feature = cmExperimental::FeatureByName(featureName)) {
  312. if (cmExperimental::HasSupportEnabled(makefile, *feature)) {
  313. makefile.AddDefinition(variableName, "TRUE");
  314. } else {
  315. makefile.AddDefinition(variableName, "FALSE");
  316. }
  317. } else {
  318. return FatalError(status,
  319. cmStrCat("Experimental feature name \"", featureName,
  320. "\" does not exist."));
  321. }
  322. return true;
  323. }
  324. }
  325. bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
  326. cmExecutionStatus& status)
  327. {
  328. std::vector<std::string> expArgs;
  329. size_t rawArg = 0;
  330. size_t expArg = 0;
  331. // Helper to consume and expand one raw argument at a time.
  332. auto moreArgs = [&]() -> bool {
  333. while (expArg >= expArgs.size()) {
  334. if (rawArg >= args.size()) {
  335. return false;
  336. }
  337. std::vector<cmListFileArgument> tmpArg;
  338. tmpArg.emplace_back(args[rawArg++]);
  339. status.GetMakefile().ExpandArguments(tmpArg, expArgs);
  340. }
  341. return true;
  342. };
  343. auto finishArgs = [&]() {
  344. std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
  345. status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
  346. rawArg = args.size();
  347. };
  348. if (!moreArgs()) {
  349. return FatalError(status, "called with incorrect number of arguments");
  350. }
  351. if (expArgs[expArg] == "EXIT"_s) {
  352. ++expArg; // consume "EXIT".
  353. if (!moreArgs()) {
  354. return FatalError(status, "EXIT requires one argument");
  355. }
  356. auto workingMode =
  357. status.GetMakefile().GetCMakeInstance()->GetWorkingMode();
  358. if (workingMode != cmake::SCRIPT_MODE) {
  359. return FatalError(status, "EXIT can be used only in SCRIPT mode");
  360. }
  361. long retCode = 0;
  362. if (!cmStrToLong(expArgs[expArg], &retCode)) {
  363. return FatalError(status,
  364. cmStrCat("EXIT requires one integral argument, got \"",
  365. expArgs[expArg], '\"'));
  366. }
  367. if (workingMode == cmake::SCRIPT_MODE) {
  368. status.SetExitCode(static_cast<int>(retCode));
  369. }
  370. return true;
  371. }
  372. if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
  373. finishArgs();
  374. return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
  375. }
  376. cm::optional<Defer> maybeDefer;
  377. if (expArgs[expArg] == "DEFER"_s) {
  378. ++expArg; // Consume "DEFER".
  379. if (!moreArgs()) {
  380. return FatalError(status, "DEFER requires at least one argument");
  381. }
  382. Defer defer;
  383. // Process optional arguments.
  384. while (moreArgs()) {
  385. if (expArgs[expArg] == "CALL"_s) {
  386. break;
  387. }
  388. if (expArgs[expArg] == "CANCEL_CALL"_s ||
  389. expArgs[expArg] == "GET_CALL_IDS"_s ||
  390. expArgs[expArg] == "GET_CALL"_s) {
  391. if (!defer.Id.empty() || !defer.IdVar.empty()) {
  392. return FatalError(status,
  393. cmStrCat("DEFER "_s, expArgs[expArg],
  394. " does not accept ID or ID_VAR."_s));
  395. }
  396. finishArgs();
  397. return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
  398. }
  399. if (expArgs[expArg] == "DIRECTORY"_s) {
  400. ++expArg; // Consume "DIRECTORY".
  401. if (defer.Directory) {
  402. return FatalError(status,
  403. "DEFER given multiple DIRECTORY arguments");
  404. }
  405. if (!moreArgs()) {
  406. return FatalError(status, "DEFER DIRECTORY missing value");
  407. }
  408. std::string dir = expArgs[expArg++];
  409. if (dir.empty()) {
  410. return FatalError(status, "DEFER DIRECTORY may not be empty");
  411. }
  412. dir = cmSystemTools::CollapseFullPath(
  413. dir, status.GetMakefile().GetCurrentSourceDirectory());
  414. defer.Directory =
  415. status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
  416. if (!defer.Directory) {
  417. return FatalError(status,
  418. cmStrCat("DEFER DIRECTORY:\n "_s, dir,
  419. "\nis not known. "
  420. "It may not have been processed yet."_s));
  421. }
  422. } else if (expArgs[expArg] == "ID"_s) {
  423. ++expArg; // Consume "ID".
  424. if (!defer.Id.empty()) {
  425. return FatalError(status, "DEFER given multiple ID arguments");
  426. }
  427. if (!moreArgs()) {
  428. return FatalError(status, "DEFER ID missing value");
  429. }
  430. defer.Id = expArgs[expArg++];
  431. if (defer.Id.empty()) {
  432. return FatalError(status, "DEFER ID may not be empty");
  433. }
  434. if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
  435. return FatalError(status, "DEFER ID may not start in A-Z.");
  436. }
  437. } else if (expArgs[expArg] == "ID_VAR"_s) {
  438. ++expArg; // Consume "ID_VAR".
  439. if (!defer.IdVar.empty()) {
  440. return FatalError(status, "DEFER given multiple ID_VAR arguments");
  441. }
  442. if (!moreArgs()) {
  443. return FatalError(status, "DEFER ID_VAR missing variable name");
  444. }
  445. defer.IdVar = expArgs[expArg++];
  446. if (defer.IdVar.empty()) {
  447. return FatalError(status, "DEFER ID_VAR may not be empty");
  448. }
  449. } else {
  450. return FatalError(
  451. status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg]));
  452. }
  453. }
  454. if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
  455. return FatalError(status, "DEFER must be followed by a CALL argument");
  456. }
  457. maybeDefer = std::move(defer);
  458. }
  459. if (expArgs[expArg] == "CALL") {
  460. ++expArg; // Consume "CALL".
  461. // CALL requires a command name.
  462. if (!moreArgs()) {
  463. return FatalError(status, "CALL missing command name");
  464. }
  465. std::string const& callCommand = expArgs[expArg++];
  466. // CALL accepts no further expanded arguments.
  467. if (expArg != expArgs.size()) {
  468. return FatalError(status, "CALL command's arguments must be literal");
  469. }
  470. // Run the CALL.
  471. return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
  472. std::move(maybeDefer), status);
  473. }
  474. if (expArgs[expArg] == "EVAL") {
  475. return cmCMakeLanguageCommandEVAL(args, status);
  476. }
  477. if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
  478. return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
  479. }
  480. if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") {
  481. return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args,
  482. status);
  483. }
  484. if (expArgs[expArg] == "TRACE") {
  485. ++expArg; // Consume "TRACE".
  486. if (!moreArgs()) {
  487. return FatalError(status, "TRACE missing a boolean value");
  488. }
  489. bool const value = cmValue::IsOn(expArgs[expArg++]);
  490. bool expand = false;
  491. if (value && moreArgs()) {
  492. expand = (expArgs[expArg] == "EXPAND");
  493. if (!expand) {
  494. return FatalError(
  495. status,
  496. cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg]));
  497. }
  498. ++expArg;
  499. }
  500. if (moreArgs()) {
  501. return FatalError(
  502. status,
  503. cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments"));
  504. }
  505. cmMakefile& makefile = status.GetMakefile();
  506. if (value) {
  507. makefile.GetCMakeInstance()->PushTraceCmd(expand);
  508. return true;
  509. }
  510. return makefile.GetCMakeInstance()->PopTraceCmd() ||
  511. FatalError(status, "TRACE OFF request without a corresponding TRACE ON");
  512. }
  513. return FatalError(status, "called with unknown meta-operation");
  514. }