cmCMakeLanguageCommand.cxx 17 KB

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