cmSetCommand.cxx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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 "cmSetCommand.h"
  4. #include <algorithm>
  5. #include <cm/optional>
  6. #include <cm/string_view>
  7. #include <cmext/string_view>
  8. #include "cmArgumentParser.h"
  9. #include "cmArgumentParserTypes.h"
  10. #include "cmExecutionStatus.h"
  11. #include "cmList.h"
  12. #include "cmMakefile.h"
  13. #include "cmMessageType.h"
  14. #include "cmRange.h"
  15. #include "cmState.h"
  16. #include "cmStateTypes.h"
  17. #include "cmStringAlgorithms.h"
  18. #include "cmSystemTools.h"
  19. #include "cmValue.h"
  20. namespace {
  21. void setENV(std::string const& var, cm::string_view val)
  22. {
  23. #ifdef _WIN32
  24. if (val.empty()) {
  25. // FIXME(#27285): On Windows, PutEnv previously treated empty as unset.
  26. // KWSys was fixed, but we need to retain the behavior for compatibility.
  27. cmSystemTools::UnPutEnv(var);
  28. return;
  29. }
  30. #endif
  31. cmSystemTools::PutEnv(cmStrCat(var, '=', val));
  32. }
  33. }
  34. // cmSetCommand
  35. bool cmSetCommand(std::vector<std::string> const& args,
  36. cmExecutionStatus& status)
  37. {
  38. if (args.empty()) {
  39. status.SetError("called with incorrect number of arguments");
  40. return false;
  41. }
  42. auto const& variable = args[0]; // VAR is always first
  43. // watch for ENV{} signature
  44. if (cmHasLiteralPrefix(variable, "ENV{") && variable.size() > 5) {
  45. // what is the variable name
  46. auto const& varName = variable.substr(4, variable.size() - 5);
  47. // what is the current value if any
  48. std::string currValue;
  49. bool const currValueSet = cmSystemTools::GetEnv(varName, currValue);
  50. // will it be set to something, then set it
  51. if (args.size() > 1 && !args[1].empty()) {
  52. // but only if it is different from current value
  53. if (!currValueSet || currValue != args[1]) {
  54. setENV(varName, args[1]);
  55. }
  56. // if there's extra arguments, warn user
  57. // that they are ignored by this command.
  58. if (args.size() > 2) {
  59. std::string m = "Only the first value argument is used when setting "
  60. "an environment variable. Argument '" +
  61. args[2] + "' and later are unused.";
  62. status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, m);
  63. }
  64. return true;
  65. }
  66. // if it will be cleared, then clear it if it isn't already clear
  67. if (currValueSet) {
  68. setENV(varName, ""_s);
  69. }
  70. return true;
  71. }
  72. // watch for CACHE{} signature
  73. if (cmHasLiteralPrefix(variable, "CACHE{") && variable.size() > 7 &&
  74. cmHasSuffix(variable, '}')) {
  75. // what is the variable name
  76. auto const& varName = variable.substr(6, variable.size() - 7);
  77. // VALUE handling
  78. auto valueArg = std::find(args.cbegin() + 1, args.cend(), "VALUE");
  79. if (valueArg == args.cend()) {
  80. status.SetError("Required argument 'VALUE' is missing.");
  81. return false;
  82. }
  83. auto value = cmMakeRange(valueArg + 1, args.cend());
  84. // Handle options
  85. struct Arguments : public ArgumentParser::ParseResult
  86. {
  87. ArgumentParser::Continue validateTypeValue(cm::string_view type)
  88. {
  89. if (!cmState::StringToCacheEntryType(std::string{ type },
  90. this->Type)) {
  91. this->AddKeywordError("TYPE"_s,
  92. cmStrCat("Invalid value: ", type, '.'));
  93. }
  94. return ArgumentParser::Continue::No;
  95. }
  96. cmStateEnums::CacheEntryType Type = cmStateEnums::UNINITIALIZED;
  97. cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> Help;
  98. bool Force = false;
  99. };
  100. static auto const optionsParser =
  101. cmArgumentParser<Arguments>{}
  102. .Bind("TYPE"_s, &Arguments::validateTypeValue)
  103. .Bind("HELP"_s, &Arguments::Help)
  104. .Bind("FORCE"_s, &Arguments::Force);
  105. std::vector<std::string> unrecognizedArguments;
  106. auto parsedArgs = optionsParser.Parse(
  107. cmMakeRange(args.cbegin() + 1, valueArg), &unrecognizedArguments);
  108. if (!unrecognizedArguments.empty()) {
  109. status.SetError(cmStrCat("Called with unsupported argument(s): ",
  110. cmJoin(unrecognizedArguments, ", "_s), '.'));
  111. return false;
  112. }
  113. if (parsedArgs.MaybeReportError(status.GetMakefile())) {
  114. return false;
  115. }
  116. // see if this is already in the cache
  117. cmState* state = status.GetMakefile().GetState();
  118. cmValue existingValue = state->GetCacheEntryValue(varName);
  119. cmStateEnums::CacheEntryType existingType =
  120. state->GetCacheEntryType(varName);
  121. if (parsedArgs.Type == cmStateEnums::UNINITIALIZED) {
  122. parsedArgs.Type = existingType == cmStateEnums::UNINITIALIZED
  123. ? cmStateEnums::STRING
  124. : existingType;
  125. }
  126. std::string help = parsedArgs.Help
  127. ? cmJoin(*parsedArgs.Help, "")
  128. : *state->GetCacheEntryProperty(varName, "HELPSTRING");
  129. if (existingValue && existingType != cmStateEnums::UNINITIALIZED) {
  130. // if the set is trying to CACHE the value but the value
  131. // is already in the cache and the type is not internal
  132. // then leave now without setting any definitions in the cache
  133. // or the makefile
  134. if (parsedArgs.Type != cmStateEnums::INTERNAL && !parsedArgs.Force) {
  135. return true;
  136. }
  137. }
  138. status.GetMakefile().AddCacheDefinition(
  139. varName,
  140. valueArg == args.cend() ? *existingValue : cmList::to_string(value),
  141. help, parsedArgs.Type, parsedArgs.Force);
  142. return true;
  143. }
  144. // SET (VAR) // Removes the definition of VAR.
  145. if (args.size() == 1) {
  146. status.GetMakefile().RemoveDefinition(variable);
  147. return true;
  148. }
  149. // SET (VAR PARENT_SCOPE) // Removes the definition of VAR
  150. // in the parent scope.
  151. if (args.size() == 2 && args.back() == "PARENT_SCOPE") {
  152. status.GetMakefile().RaiseScope(variable, nullptr);
  153. return true;
  154. }
  155. // here are the remaining options
  156. // SET (VAR value )
  157. // SET (VAR value PARENT_SCOPE)
  158. // SET (VAR CACHE TYPE "doc String" [FORCE])
  159. // SET (VAR value CACHE TYPE "doc string" [FORCE])
  160. std::string value; // optional
  161. bool cache = false; // optional
  162. bool force = false; // optional
  163. bool parentScope = false;
  164. cmStateEnums::CacheEntryType type =
  165. cmStateEnums::STRING; // required if cache
  166. cmValue docstring; // required if cache
  167. unsigned int ignoreLastArgs = 0;
  168. // look for PARENT_SCOPE argument
  169. if (args.size() > 1 && args.back() == "PARENT_SCOPE") {
  170. parentScope = true;
  171. ignoreLastArgs++;
  172. } else {
  173. // look for FORCE argument
  174. if (args.size() > 4 && args.back() == "FORCE") {
  175. force = true;
  176. ignoreLastArgs++;
  177. }
  178. // check for cache signature
  179. if (args.size() > 3 &&
  180. args[args.size() - 3 - (force ? 1 : 0)] == "CACHE") {
  181. cache = true;
  182. ignoreLastArgs += 3;
  183. }
  184. }
  185. // collect any values into a single semi-colon separated value list
  186. value =
  187. cmList::to_string(cmMakeRange(args).advance(1).retreat(ignoreLastArgs));
  188. if (parentScope) {
  189. status.GetMakefile().RaiseScope(variable, value.c_str());
  190. return true;
  191. }
  192. // we should be nice and try to catch some simple screwups if the last or
  193. // next to last args are CACHE then they screwed up. If they used FORCE
  194. // without CACHE they screwed up
  195. if (args.back() == "CACHE") {
  196. status.SetError(
  197. "given invalid arguments for CACHE mode: missing type and docstring");
  198. return false;
  199. }
  200. if (args.size() > 1 && args[args.size() - 2] == "CACHE") {
  201. status.SetError(
  202. "given invalid arguments for CACHE mode: missing type or docstring");
  203. return false;
  204. }
  205. if (force && !cache) {
  206. status.SetError("given invalid arguments: FORCE specified without CACHE");
  207. return false;
  208. }
  209. if (cache) {
  210. std::string::size_type cacheStart = args.size() - 3 - (force ? 1 : 0);
  211. if (!cmState::StringToCacheEntryType(args[cacheStart + 1], type)) {
  212. std::string m = "implicitly converting '" + args[cacheStart + 1] +
  213. "' to 'STRING' type.";
  214. status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, m);
  215. // Setting this may not be required, since it's
  216. // initialized as a string. Keeping this here to
  217. // ensure that the type is actually converting to a string.
  218. type = cmStateEnums::STRING;
  219. }
  220. docstring = cmValue{ args[cacheStart + 2] };
  221. }
  222. // see if this is already in the cache
  223. cmState* state = status.GetMakefile().GetState();
  224. cmValue existingValue = state->GetCacheEntryValue(variable);
  225. if (existingValue &&
  226. (state->GetCacheEntryType(variable) != cmStateEnums::UNINITIALIZED)) {
  227. // if the set is trying to CACHE the value but the value
  228. // is already in the cache and the type is not internal
  229. // then leave now without setting any definitions in the cache
  230. // or the makefile
  231. if (cache && type != cmStateEnums::INTERNAL && !force) {
  232. return true;
  233. }
  234. }
  235. // if it is meant to be in the cache then define it in the cache
  236. if (cache) {
  237. status.GetMakefile().AddCacheDefinition(variable, cmValue{ value },
  238. docstring, type, force);
  239. } else {
  240. // add the definition
  241. status.GetMakefile().AddDefinition(variable, value);
  242. }
  243. return true;
  244. }