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 "cmSetPropertyCommand.h"
7 #include <unordered_set>
9 #include "cmExecutionStatus.h"
10 #include "cmGlobalGenerator.h"
11 #include "cmInstalledFile.h"
12 #include "cmListFileCache.h"
13 #include "cmMakefile.h"
14 #include "cmMessageType.h"
15 #include "cmPolicies.h"
16 #include "cmProperty.h"
18 #include "cmSourceFile.h"
19 #include "cmSourceFileLocation.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
29 bool HandleGlobalMode(cmExecutionStatus& status,
30 const std::set<std::string>& names,
31 const std::string& propertyName,
32 const std::string& propertyValue, bool appendAsString,
33 bool appendMode, bool remove);
34 bool HandleDirectoryMode(cmExecutionStatus& status,
35 const std::set<std::string>& names,
36 const std::string& propertyName,
37 const std::string& propertyValue, bool appendAsString,
38 bool appendMode, bool remove);
39 bool HandleTargetMode(cmExecutionStatus& status,
40 const std::set<std::string>& names,
41 const std::string& propertyName,
42 const std::string& propertyValue, bool appendAsString,
43 bool appendMode, bool remove);
44 bool HandleTarget(cmTarget* target, cmMakefile& makefile,
45 const std::string& propertyName,
46 const std::string& propertyValue, bool appendAsString,
47 bool appendMode, bool remove);
48 bool HandleSourceMode(cmExecutionStatus& status,
49 const std::set<std::string>& names,
50 const std::string& propertyName,
51 const std::string& propertyValue, bool appendAsString,
52 bool appendMode, bool remove,
53 const std::vector<cmMakefile*>& directory_makefiles,
54 bool source_file_paths_should_be_absolute);
55 bool HandleSource(cmSourceFile* sf, const std::string& propertyName,
56 const std::string& propertyValue, bool appendAsString,
57 bool appendMode, bool remove);
58 bool HandleTestMode(cmExecutionStatus& status, std::set<std::string>& names,
59 const std::string& propertyName,
60 const std::string& propertyValue, bool appendAsString,
61 bool appendMode, bool remove);
62 bool HandleTest(cmTest* test, const std::string& propertyName,
63 const std::string& propertyValue, bool appendAsString,
64 bool appendMode, bool remove);
65 bool HandleCacheMode(cmExecutionStatus& status,
66 const std::set<std::string>& names,
67 const std::string& propertyName,
68 const std::string& propertyValue, bool appendAsString,
69 bool appendMode, bool remove);
70 bool HandleCacheEntry(std::string const& cacheKey, const cmMakefile& makefile,
71 const std::string& propertyName,
72 const std::string& propertyValue, bool appendAsString,
73 bool appendMode, bool remove);
74 bool HandleInstallMode(cmExecutionStatus& status,
75 const std::set<std::string>& names,
76 const std::string& propertyName,
77 const std::string& propertyValue, bool appendAsString,
78 bool appendMode, bool remove);
79 bool HandleInstall(cmInstalledFile* file, cmMakefile& makefile,
80 const std::string& propertyName,
81 const std::string& propertyValue, bool appendAsString,
82 bool appendMode, bool remove);
85 namespace SetPropertyCommand {
86 bool HandleSourceFileDirectoryScopes(
87 cmExecutionStatus& status, std::vector<std::string>& source_file_directories,
88 std::vector<std::string>& source_file_target_directories,
89 std::vector<cmMakefile*>& directory_makefiles)
91 std::unordered_set<cmMakefile*> directory_makefiles_set;
93 cmMakefile* current_dir_mf = &status.GetMakefile();
94 if (!source_file_directories.empty()) {
95 for (const std::string& dir_path : source_file_directories) {
96 const std::string absolute_dir_path = cmSystemTools::CollapseFullPath(
97 dir_path, current_dir_mf->GetCurrentSourceDirectory());
99 status.GetMakefile().GetGlobalGenerator()->FindMakefile(
102 status.SetError(cmStrCat("given non-existent DIRECTORY ", dir_path));
105 if (directory_makefiles_set.find(dir_mf) ==
106 directory_makefiles_set.end()) {
107 directory_makefiles.push_back(dir_mf);
108 directory_makefiles_set.insert(dir_mf);
113 if (!source_file_target_directories.empty()) {
114 for (const std::string& target_name : source_file_target_directories) {
115 cmTarget* target = current_dir_mf->FindTargetToUse(target_name);
117 status.SetError(cmStrCat(
118 "given non-existent target for TARGET_DIRECTORY ", target_name));
121 cmValue target_source_dir = target->GetProperty("BINARY_DIR");
122 cmMakefile* target_dir_mf =
123 status.GetMakefile().GetGlobalGenerator()->FindMakefile(
126 if (directory_makefiles_set.find(target_dir_mf) ==
127 directory_makefiles_set.end()) {
128 directory_makefiles.push_back(target_dir_mf);
129 directory_makefiles_set.insert(target_dir_mf);
134 if (source_file_directories.empty() &&
135 source_file_target_directories.empty()) {
136 directory_makefiles.push_back(current_dir_mf);
141 bool HandleSourceFileDirectoryScopeValidation(
142 cmExecutionStatus& status, bool source_file_directory_option_enabled,
143 bool source_file_target_option_enabled,
144 std::vector<std::string>& source_file_directories,
145 std::vector<std::string>& source_file_target_directories)
147 // Validate source file directory scopes.
148 if (source_file_directory_option_enabled &&
149 source_file_directories.empty()) {
150 std::string errors = "called with incorrect number of arguments "
151 "no value provided to the DIRECTORY option";
152 status.SetError(errors);
155 if (source_file_target_option_enabled &&
156 source_file_target_directories.empty()) {
157 std::string errors = "called with incorrect number of arguments "
158 "no value provided to the TARGET_DIRECTORY option";
159 status.SetError(errors);
165 bool HandleAndValidateSourceFileDirectoryScopes(
166 cmExecutionStatus& status, bool source_file_directory_option_enabled,
167 bool source_file_target_option_enabled,
168 std::vector<std::string>& source_file_directories,
169 std::vector<std::string>& source_file_target_directories,
170 std::vector<cmMakefile*>& source_file_directory_makefiles)
172 bool scope_options_valid =
173 SetPropertyCommand::HandleSourceFileDirectoryScopeValidation(
174 status, source_file_directory_option_enabled,
175 source_file_target_option_enabled, source_file_directories,
176 source_file_target_directories);
177 if (!scope_options_valid) {
181 scope_options_valid = SetPropertyCommand::HandleSourceFileDirectoryScopes(
182 status, source_file_directories, source_file_target_directories,
183 source_file_directory_makefiles);
184 return scope_options_valid;
187 std::string MakeSourceFilePathAbsoluteIfNeeded(
188 cmExecutionStatus& status, const std::string& source_file_path,
192 return source_file_path;
194 std::string absolute_file_path = cmSystemTools::CollapseFullPath(
195 source_file_path, status.GetMakefile().GetCurrentSourceDirectory());
196 return absolute_file_path;
199 void MakeSourceFilePathsAbsoluteIfNeeded(
200 cmExecutionStatus& status,
201 std::vector<std::string>& source_files_absolute_paths,
202 std::vector<std::string>::const_iterator files_it_begin,
203 std::vector<std::string>::const_iterator files_it_end, const bool needed)
206 // Make the file paths absolute, so that relative source file paths are
207 // picked up relative to the command calling site, regardless of the
209 std::vector<std::string>::difference_type num_files =
210 files_it_end - files_it_begin;
211 source_files_absolute_paths.reserve(num_files);
214 source_files_absolute_paths.assign(files_it_begin, files_it_end);
218 for (; files_it_begin != files_it_end; ++files_it_begin) {
219 const std::string absolute_file_path =
220 MakeSourceFilePathAbsoluteIfNeeded(status, *files_it_begin, true);
221 source_files_absolute_paths.push_back(absolute_file_path);
225 bool HandleAndValidateSourceFilePropertyGENERATED(
226 cmSourceFile* sf, std::string const& propertyValue, PropertyOp op)
228 const auto& mf = *sf->GetLocation().GetMakefile();
229 auto policyStatus = mf.GetPolicyStatus(cmPolicies::CMP0118);
231 const bool policyWARN = policyStatus == cmPolicies::WARN;
232 const bool policyNEW = policyStatus != cmPolicies::OLD && !policyWARN;
235 if (!cmIsOn(propertyValue) && !cmIsOff(propertyValue)) {
237 MessageType::AUTHOR_WARNING,
238 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0118),
239 "\nAttempt to set property 'GENERATED' with the following "
240 "non-boolean value (which will be interpreted as \"0\"):\n",
242 "\nThat exact value will not be retrievable. A value of "
243 "\"0\" will be returned instead.\n"
244 "This will be an error under policy CMP0118.\n"));
246 if (cmIsOff(propertyValue)) {
248 MessageType::AUTHOR_WARNING,
249 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0118),
250 "\nUnsetting property 'GENERATED' will not be allowed under "
251 "policy CMP0118!\n"));
253 if (op == PropertyOp::Append || op == PropertyOp::AppendAsString) {
255 MessageType::AUTHOR_WARNING,
256 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0118),
257 "\nAppending to property 'GENERATED' will not be allowed "
258 "under policy CMP0118!\n"));
260 } else if (policyNEW) {
261 if (!cmIsOn(propertyValue) && !cmIsOff(propertyValue)) {
263 MessageType::AUTHOR_ERROR,
265 "Policy CMP0118 is set to NEW and the following non-boolean value "
266 "given for property 'GENERATED' is therefore not allowed:\n",
267 propertyValue, "\nReplace it with a boolean value!\n"));
270 if (cmIsOff(propertyValue)) {
272 MessageType::AUTHOR_ERROR,
273 "Unsetting the 'GENERATED' property is not allowed under CMP0118!\n");
276 if (op == PropertyOp::Append || op == PropertyOp::AppendAsString) {
277 mf.IssueMessage(MessageType::AUTHOR_ERROR,
278 "Policy CMP0118 is set to NEW and appending to the "
279 "'GENERATED' property is therefore not allowed. Only "
280 "setting it to \"1\" is allowed!\n");
287 // Do it the traditional way.
289 case PropertyOp::Append:
290 sf->AppendProperty("GENERATED", propertyValue, false);
292 case PropertyOp::AppendAsString:
293 sf->AppendProperty("GENERATED", propertyValue, true);
295 case PropertyOp::Remove:
296 sf->SetProperty("GENERATED", nullptr);
298 case PropertyOp::Set:
299 sf->SetProperty("GENERATED", propertyValue);
303 sf->MarkAsGenerated();
308 } // END: namespace SetPropertyCommand
310 bool cmSetPropertyCommand(std::vector<std::string> const& args,
311 cmExecutionStatus& status)
313 if (args.size() < 2) {
314 status.SetError("called with incorrect number of arguments");
318 // Get the scope on which to set the property.
319 std::string const& scopeName = args.front();
320 cmProperty::ScopeType scope;
321 if (scopeName == "GLOBAL") {
322 scope = cmProperty::GLOBAL;
323 } else if (scopeName == "DIRECTORY") {
324 scope = cmProperty::DIRECTORY;
325 } else if (scopeName == "TARGET") {
326 scope = cmProperty::TARGET;
327 } else if (scopeName == "SOURCE") {
328 scope = cmProperty::SOURCE_FILE;
329 } else if (scopeName == "TEST") {
330 scope = cmProperty::TEST;
331 } else if (scopeName == "CACHE") {
332 scope = cmProperty::CACHE;
333 } else if (scopeName == "INSTALL") {
334 scope = cmProperty::INSTALL;
336 status.SetError(cmStrCat("given invalid scope ", scopeName,
338 "Valid scopes are GLOBAL, DIRECTORY, "
339 "TARGET, SOURCE, TEST, CACHE, INSTALL."));
343 bool appendAsString = false;
344 bool appendMode = false;
346 std::set<std::string> names;
347 std::string propertyName;
348 std::string propertyValue;
350 std::vector<std::string> source_file_directories;
351 std::vector<std::string> source_file_target_directories;
352 bool source_file_directory_option_enabled = false;
353 bool source_file_target_option_enabled = false;
355 // Parse the rest of the arguments up to the values.
362 DoingSourceDirectory,
363 DoingSourceTargetDirectory
365 Doing doing = DoingNames;
366 const char* sep = "";
367 for (std::string const& arg : cmMakeRange(args).advance(1)) {
368 if (arg == "PROPERTY") {
369 doing = DoingProperty;
370 } else if (arg == "APPEND") {
374 appendAsString = false;
375 } else if (arg == "APPEND_STRING") {
379 appendAsString = true;
380 } else if (doing != DoingProperty && doing != DoingValues &&
381 scope == cmProperty::SOURCE_FILE && arg == "DIRECTORY") {
382 doing = DoingSourceDirectory;
383 source_file_directory_option_enabled = true;
384 } else if (doing != DoingProperty && doing != DoingValues &&
385 scope == cmProperty::SOURCE_FILE && arg == "TARGET_DIRECTORY") {
386 doing = DoingSourceTargetDirectory;
387 source_file_target_option_enabled = true;
388 } else if (doing == DoingNames) {
390 } else if (doing == DoingSourceDirectory) {
391 source_file_directories.push_back(arg);
392 } else if (doing == DoingSourceTargetDirectory) {
393 source_file_target_directories.push_back(arg);
394 } else if (doing == DoingProperty) {
397 } else if (doing == DoingValues) {
398 propertyValue += sep;
400 propertyValue += arg;
403 status.SetError(cmStrCat("given invalid argument \"", arg, "\"."));
408 // Make sure a property name was found.
409 if (propertyName.empty()) {
410 status.SetError("not given a PROPERTY <name> argument.");
414 std::vector<cmMakefile*> source_file_directory_makefiles;
415 bool file_scopes_handled =
416 SetPropertyCommand::HandleAndValidateSourceFileDirectoryScopes(
417 status, source_file_directory_option_enabled,
418 source_file_target_option_enabled, source_file_directories,
419 source_file_target_directories, source_file_directory_makefiles);
420 if (!file_scopes_handled) {
423 bool source_file_paths_should_be_absolute =
424 source_file_directory_option_enabled || source_file_target_option_enabled;
426 // Dispatch property setting.
428 case cmProperty::GLOBAL:
429 return HandleGlobalMode(status, names, propertyName, propertyValue,
430 appendAsString, appendMode, remove);
431 case cmProperty::DIRECTORY:
432 return HandleDirectoryMode(status, names, propertyName, propertyValue,
433 appendAsString, appendMode, remove);
434 case cmProperty::TARGET:
435 return HandleTargetMode(status, names, propertyName, propertyValue,
436 appendAsString, appendMode, remove);
437 case cmProperty::SOURCE_FILE:
438 return HandleSourceMode(status, names, propertyName, propertyValue,
439 appendAsString, appendMode, remove,
440 source_file_directory_makefiles,
441 source_file_paths_should_be_absolute);
442 case cmProperty::TEST:
443 return HandleTestMode(status, names, propertyName, propertyValue,
444 appendAsString, appendMode, remove);
445 case cmProperty::CACHE:
446 return HandleCacheMode(status, names, propertyName, propertyValue,
447 appendAsString, appendMode, remove);
448 case cmProperty::INSTALL:
449 return HandleInstallMode(status, names, propertyName, propertyValue,
450 appendAsString, appendMode, remove);
452 case cmProperty::VARIABLE:
453 case cmProperty::CACHED_VARIABLE:
454 break; // should never happen
459 namespace /* anonymous */ {
460 bool HandleGlobalMode(cmExecutionStatus& status,
461 const std::set<std::string>& names,
462 const std::string& propertyName,
463 const std::string& propertyValue, bool appendAsString,
464 bool appendMode, bool remove)
466 if (!names.empty()) {
467 status.SetError("given names for GLOBAL scope.");
471 // Set or append the property.
472 cmake* cm = status.GetMakefile().GetCMakeInstance();
474 cm->AppendProperty(propertyName, propertyValue, appendAsString);
477 cm->SetProperty(propertyName, nullptr);
479 cm->SetProperty(propertyName, propertyValue);
486 bool HandleDirectoryMode(cmExecutionStatus& status,
487 const std::set<std::string>& names,
488 const std::string& propertyName,
489 const std::string& propertyValue, bool appendAsString,
490 bool appendMode, bool remove)
492 if (names.size() > 1) {
493 status.SetError("allows at most one name for DIRECTORY scope.");
497 // Default to the current directory.
498 cmMakefile* mf = &status.GetMakefile();
500 // Lookup the directory if given.
501 if (!names.empty()) {
502 // Construct the directory name. Interpret relative paths with
503 // respect to the current directory.
504 std::string dir = cmSystemTools::CollapseFullPath(
505 *names.begin(), status.GetMakefile().GetCurrentSourceDirectory());
507 mf = status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
509 // Could not find the directory.
511 "DIRECTORY scope provided but requested directory was not found. "
512 "This could be because the directory argument was invalid or, "
513 "it is valid but has not been processed yet.");
518 // Set or append the property.
520 mf->AppendProperty(propertyName, propertyValue, appendAsString);
523 mf->SetProperty(propertyName, nullptr);
525 mf->SetProperty(propertyName, propertyValue);
532 bool HandleTargetMode(cmExecutionStatus& status,
533 const std::set<std::string>& names,
534 const std::string& propertyName,
535 const std::string& propertyValue, bool appendAsString,
536 bool appendMode, bool remove)
538 for (std::string const& name : names) {
539 if (status.GetMakefile().IsAlias(name)) {
540 status.SetError("can not be used on an ALIAS target.");
543 if (cmTarget* target = status.GetMakefile().FindTargetToUse(name)) {
544 // Handle the current target.
545 if (!HandleTarget(target, status.GetMakefile(), propertyName,
546 propertyValue, appendAsString, appendMode, remove)) {
550 status.SetError(cmStrCat("could not find TARGET ", name,
551 ". Perhaps it has not yet been created."));
558 bool HandleTarget(cmTarget* target, cmMakefile& makefile,
559 const std::string& propertyName,
560 const std::string& propertyValue, bool appendAsString,
561 bool appendMode, bool remove)
563 // Set or append the property.
565 target->AppendProperty(propertyName, propertyValue,
566 makefile.GetBacktrace(), appendAsString);
569 target->SetProperty(propertyName, nullptr);
571 target->SetProperty(propertyName, propertyValue);
575 // Check the resulting value.
576 target->CheckProperty(propertyName, &makefile);
581 bool HandleSourceMode(cmExecutionStatus& status,
582 const std::set<std::string>& names,
583 const std::string& propertyName,
584 const std::string& propertyValue, bool appendAsString,
585 bool appendMode, bool remove,
586 const std::vector<cmMakefile*>& directory_makefiles,
587 const bool source_file_paths_should_be_absolute)
589 std::vector<std::string> files_absolute;
590 std::vector<std::string> unique_files(names.begin(), names.end());
591 SetPropertyCommand::MakeSourceFilePathsAbsoluteIfNeeded(
592 status, files_absolute, unique_files.begin(), unique_files.end(),
593 source_file_paths_should_be_absolute);
595 for (auto* const mf : directory_makefiles) {
596 for (std::string const& name : files_absolute) {
597 // Get the source file.
598 if (cmSourceFile* sf = mf->GetOrCreateSource(name)) {
599 if (!HandleSource(sf, propertyName, propertyValue, appendAsString,
600 appendMode, remove)) {
604 status.SetError(cmStrCat(
605 "given SOURCE name that could not be found or created: ", name));
614 bool HandleSource(cmSourceFile* sf, const std::string& propertyName,
615 const std::string& propertyValue, bool appendAsString,
616 bool appendMode, bool remove)
618 // Special validation and handling of GENERATED flag?
619 if (propertyName == "GENERATED") {
620 SetPropertyCommand::PropertyOp op = (remove)
621 ? SetPropertyCommand::PropertyOp::Remove
623 ? SetPropertyCommand::PropertyOp::AppendAsString
624 : (appendMode) ? SetPropertyCommand::PropertyOp::Append
625 : SetPropertyCommand::PropertyOp::Set;
626 return SetPropertyCommand::HandleAndValidateSourceFilePropertyGENERATED(
627 sf, propertyValue, op);
630 // Set or append the property.
632 sf->AppendProperty(propertyName, propertyValue, appendAsString);
635 sf->SetProperty(propertyName, nullptr);
637 sf->SetProperty(propertyName, propertyValue);
643 bool HandleTestMode(cmExecutionStatus& status, std::set<std::string>& names,
644 const std::string& propertyName,
645 const std::string& propertyValue, bool appendAsString,
646 bool appendMode, bool remove)
648 // Look for tests with all names given.
649 std::set<std::string>::iterator next;
650 for (auto ni = names.begin(); ni != names.end(); ni = next) {
653 if (cmTest* test = status.GetMakefile().GetTest(*ni)) {
654 if (HandleTest(test, propertyName, propertyValue, appendAsString,
655 appendMode, remove)) {
663 // Names that are still left were not found.
664 if (!names.empty()) {
665 std::ostringstream e;
666 e << "given TEST names that do not exist:\n";
667 for (std::string const& name : names) {
668 e << " " << name << "\n";
670 status.SetError(e.str());
676 bool HandleTest(cmTest* test, const std::string& propertyName,
677 const std::string& propertyValue, bool appendAsString,
678 bool appendMode, bool remove)
680 // Set or append the property.
682 test->AppendProperty(propertyName, propertyValue, appendAsString);
685 test->SetProperty(propertyName, nullptr);
687 test->SetProperty(propertyName, propertyValue);
694 bool HandleCacheMode(cmExecutionStatus& status,
695 const std::set<std::string>& names,
696 const std::string& propertyName,
697 const std::string& propertyValue, bool appendAsString,
698 bool appendMode, bool remove)
700 if (propertyName == "ADVANCED") {
701 if (!remove && !cmIsOn(propertyValue) && !cmIsOff(propertyValue)) {
702 status.SetError(cmStrCat("given non-boolean value \"", propertyValue,
703 R"(" for CACHE property "ADVANCED". )"));
706 } else if (propertyName == "TYPE") {
707 if (!cmState::IsCacheEntryType(propertyValue)) {
709 cmStrCat("given invalid CACHE entry TYPE \"", propertyValue, "\""));
712 } else if (propertyName != "HELPSTRING" && propertyName != "STRINGS" &&
713 propertyName != "VALUE") {
715 cmStrCat("given invalid CACHE property ", propertyName,
717 "Settable CACHE properties are: "
718 "ADVANCED, HELPSTRING, STRINGS, TYPE, and VALUE."));
722 for (std::string const& name : names) {
723 // Get the source file.
724 cmake* cm = status.GetMakefile().GetCMakeInstance();
725 cmValue existingValue = cm->GetState()->GetCacheEntryValue(name);
727 if (!HandleCacheEntry(name, status.GetMakefile(), propertyName,
728 propertyValue, appendAsString, appendMode,
733 status.SetError(cmStrCat("could not find CACHE variable ", name,
734 ". Perhaps it has not yet been created."));
741 bool HandleCacheEntry(std::string const& cacheKey, const cmMakefile& makefile,
742 const std::string& propertyName,
743 const std::string& propertyValue, bool appendAsString,
744 bool appendMode, bool remove)
746 // Set or append the property.
747 cmState* state = makefile.GetState();
749 state->RemoveCacheEntryProperty(cacheKey, propertyName);
752 state->AppendCacheEntryProperty(cacheKey, propertyName, propertyValue,
755 state->SetCacheEntryProperty(cacheKey, propertyName, propertyValue);
761 bool HandleInstallMode(cmExecutionStatus& status,
762 const std::set<std::string>& names,
763 const std::string& propertyName,
764 const std::string& propertyValue, bool appendAsString,
765 bool appendMode, bool remove)
767 cmake* cm = status.GetMakefile().GetCMakeInstance();
769 for (std::string const& name : names) {
770 if (cmInstalledFile* file =
771 cm->GetOrCreateInstalledFile(&status.GetMakefile(), name)) {
772 if (!HandleInstall(file, status.GetMakefile(), propertyName,
773 propertyValue, appendAsString, appendMode, remove)) {
777 status.SetError(cmStrCat(
778 "given INSTALL name that could not be found or created: ", name));
785 bool HandleInstall(cmInstalledFile* file, cmMakefile& makefile,
786 const std::string& propertyName,
787 const std::string& propertyValue, bool appendAsString,
788 bool appendMode, bool remove)
790 // Set or append the property.
792 file->RemoveProperty(propertyName);
793 } else if (appendMode) {
794 file->AppendProperty(&makefile, propertyName, propertyValue,
797 file->SetProperty(&makefile, propertyName, propertyValue);