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