Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmakemain.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3
4 #include "cmConfigure.h" // IWYU pragma: keep
5
6 #include <algorithm>
7 #include <cassert>
8 #include <cctype>
9 #include <climits>
10 #include <cstdio>
11 #include <cstring>
12 #include <functional>
13 #include <iostream>
14 #include <sstream>
15 #include <string>
16 #include <utility>
17 #include <vector>
18
19 #include <cm/memory>
20 #include <cmext/algorithm>
21
22 #include <cm3p/uv.h>
23
24 #include "cmBuildOptions.h"
25 #include "cmCommandLineArgument.h"
26 #include "cmConsoleBuf.h"
27 #include "cmDocumentationEntry.h" // IWYU pragma: keep
28 #include "cmGlobalGenerator.h"
29 #include "cmMakefile.h"
30 #include "cmMessageMetadata.h"
31 #include "cmState.h"
32 #include "cmStateTypes.h"
33 #include "cmStringAlgorithms.h"
34 #include "cmSystemTools.h"
35 #include "cmValue.h"
36 #include "cmake.h"
37 #include "cmcmd.h"
38
39 #ifndef CMAKE_BOOTSTRAP
40 #  include "cmDocumentation.h"
41 #  include "cmDynamicLoader.h"
42 #endif
43
44 #include "cmsys/Encoding.hxx"
45 #include "cmsys/Terminal.h"
46
47 namespace {
48 #ifndef CMAKE_BOOTSTRAP
49 const char* cmDocumentationName[][2] = {
50   { nullptr, "  cmake - Cross-Platform Makefile Generator." },
51   { nullptr, nullptr }
52 };
53
54 const char* cmDocumentationUsage[][2] = {
55   { nullptr,
56     "  cmake [options] <path-to-source>\n"
57     "  cmake [options] <path-to-existing-build>\n"
58     "  cmake [options] -S <path-to-source> -B <path-to-build>" },
59   { nullptr,
60     "Specify a source directory to (re-)generate a build system for "
61     "it in the current working directory.  Specify an existing build "
62     "directory to re-generate its build system." },
63   { nullptr, nullptr }
64 };
65
66 const char* cmDocumentationUsageNote[][2] = {
67   { nullptr, "Run 'cmake --help' for more information." },
68   { nullptr, nullptr }
69 };
70
71 const char* cmDocumentationOptions[][2] = {
72   CMAKE_STANDARD_OPTIONS_TABLE,
73   { "--preset <preset>,--preset=<preset>", "Specify a configure preset." },
74   { "--list-presets[=<type>]", "List available presets." },
75   { "-E", "CMake command mode." },
76   { "-L[A][H]", "List non-advanced cached variables." },
77   { "--fresh",
78     "Configure a fresh build tree, removing any existing cache file." },
79   { "--build <dir>", "Build a CMake-generated project binary tree." },
80   { "--install <dir>", "Install a CMake-generated project binary tree." },
81   { "--open <dir>", "Open generated project in the associated application." },
82   { "-N", "View mode only." },
83   { "-P <file>", "Process script mode." },
84   { "--find-package", "Legacy pkg-config like mode.  Do not use." },
85   { "--graphviz=<file>",
86     "Generate graphviz of dependencies, see CMakeGraphVizOptions.cmake for "
87     "more." },
88   { "--system-information [file]", "Dump information about this system." },
89   { "--log-level=<ERROR|WARNING|NOTICE|STATUS|VERBOSE|DEBUG|TRACE>",
90     "Set the verbosity of messages from CMake files. "
91     "--loglevel is also accepted for backward compatibility reasons." },
92   { "--log-context", "Prepend log messages with context, if given" },
93   { "--debug-trycompile",
94     "Do not delete the try_compile build tree. Only "
95     "useful on one try_compile at a time." },
96   { "--debug-output", "Put cmake in a debug mode." },
97   { "--debug-find", "Put cmake find in a debug mode." },
98   { "--debug-find-pkg=<pkg-name>[,...]",
99     "Limit cmake debug-find to the comma-separated list of packages" },
100   { "--debug-find-var=<var-name>[,...]",
101     "Limit cmake debug-find to the comma-separated list of result variables" },
102   { "--trace", "Put cmake in trace mode." },
103   { "--trace-expand", "Put cmake in trace mode with variable expansion." },
104   { "--trace-format=<human|json-v1>", "Set the output format of the trace." },
105   { "--trace-source=<file>",
106     "Trace only this CMake file/module. Multiple options allowed." },
107   { "--trace-redirect=<file>",
108     "Redirect trace output to a file instead of stderr." },
109   { "--warn-uninitialized", "Warn about uninitialized values." },
110   { "--no-warn-unused-cli", "Don't warn about command line options." },
111   { "--check-system-vars",
112     "Find problems with variable usage in system files." },
113   { "--compile-no-warning-as-error",
114     "Ignore COMPILE_WARNING_AS_ERROR property and "
115     "CMAKE_COMPILE_WARNING_AS_ERROR variable." },
116 #  if !defined(CMAKE_BOOTSTRAP)
117   { "--profiling-format=<fmt>",
118     "Output data for profiling CMake scripts. Supported formats: "
119     "google-trace" },
120   { "--profiling-output=<file>",
121     "Select an output path for the profiling data enabled through "
122     "--profiling-format." },
123 #  endif
124   { nullptr, nullptr }
125 };
126
127 #endif
128
129 int do_command(int ac, char const* const* av,
130                std::unique_ptr<cmConsoleBuf> consoleBuf)
131 {
132   std::vector<std::string> args;
133   args.reserve(ac - 1);
134   args.emplace_back(av[0]);
135   cm::append(args, av + 2, av + ac);
136   return cmcmd::ExecuteCMakeCommand(args, std::move(consoleBuf));
137 }
138
139 cmMakefile* cmakemainGetMakefile(cmake* cm)
140 {
141   if (cm && cm->GetDebugOutput()) {
142     cmGlobalGenerator* gg = cm->GetGlobalGenerator();
143     if (gg) {
144       return gg->GetCurrentMakefile();
145     }
146   }
147   return nullptr;
148 }
149
150 std::string cmakemainGetStack(cmake* cm)
151 {
152   std::string msg;
153   cmMakefile* mf = cmakemainGetMakefile(cm);
154   if (mf) {
155     msg = mf->FormatListFileStack();
156     if (!msg.empty()) {
157       msg = "\n   Called from: " + msg;
158     }
159   }
160
161   return msg;
162 }
163
164 void cmakemainMessageCallback(const std::string& m,
165                               const cmMessageMetadata& md, cmake* cm)
166 {
167 #if defined(_WIN32)
168   // FIXME: On Windows we replace cerr's streambuf with a custom
169   // implementation that converts our internal UTF-8 encoding to the
170   // console's encoding.  It also does *not* replace LF with CRLF.
171   // Since stderr does not convert encoding and does convert LF, we
172   // cannot use it to print messages.  Another implementation will
173   // be needed to print colored messages on Windows.
174   static_cast<void>(md);
175   std::cerr << m << cmakemainGetStack(cm) << '\n' << std::flush;
176 #else
177   cmsysTerminal_cfprintf(md.desiredColor, stderr, "%s", m.c_str());
178   fflush(stderr); // stderr is buffered in some cases.
179   std::cerr << cmakemainGetStack(cm) << '\n' << std::flush;
180 #endif
181 }
182
183 void cmakemainProgressCallback(const std::string& m, float prog, cmake* cm)
184 {
185   cmMakefile* mf = cmakemainGetMakefile(cm);
186   std::string dir;
187   if (mf && cmHasLiteralPrefix(m, "Configuring") && (prog < 0)) {
188     dir = cmStrCat(' ', mf->GetCurrentSourceDirectory());
189   } else if (mf && cmHasLiteralPrefix(m, "Generating")) {
190     dir = cmStrCat(' ', mf->GetCurrentBinaryDirectory());
191   }
192
193   if ((prog < 0) || (!dir.empty())) {
194     std::cout << "-- " << m << dir << cmakemainGetStack(cm) << std::endl;
195   }
196 }
197
198 int do_cmake(int ac, char const* const* av)
199 {
200   if (cmSystemTools::GetCurrentWorkingDirectory().empty()) {
201     std::cerr << "Current working directory cannot be established."
202               << std::endl;
203     return 1;
204   }
205
206 #ifndef CMAKE_BOOTSTRAP
207   cmDocumentation doc;
208   doc.addCMakeStandardDocSections();
209   if (doc.CheckOptions(ac, av, "--")) {
210     // Construct and print requested documentation.
211     cmake hcm(cmake::RoleInternal, cmState::Unknown);
212     hcm.SetHomeDirectory("");
213     hcm.SetHomeOutputDirectory("");
214     hcm.AddCMakePaths();
215
216     // the command line args are processed here so that you can do
217     // -DCMAKE_MODULE_PATH=/some/path and have this value accessible here
218     std::vector<std::string> args(av, av + ac);
219     hcm.SetCacheArgs(args);
220
221     auto generators = hcm.GetGeneratorsDocumentation();
222
223     doc.SetName("cmake");
224     doc.SetSection("Name", cmDocumentationName);
225     doc.SetSection("Usage", cmDocumentationUsage);
226     if (ac == 1) {
227       doc.AppendSection("Usage", cmDocumentationUsageNote);
228     }
229     doc.AppendSection("Generators", generators);
230     doc.PrependSection("Options", cmDocumentationOptions);
231
232     return doc.PrintRequestedDocumentation(std::cout) ? 0 : 1;
233   }
234 #else
235   if (ac == 1) {
236     std::cout
237       << "Bootstrap CMake should not be used outside CMake build process."
238       << std::endl;
239     return 0;
240   }
241 #endif
242
243   bool wizard_mode = false;
244   bool sysinfo = false;
245   bool list_cached = false;
246   bool list_all_cached = false;
247   bool list_help = false;
248   bool view_only = false;
249   cmake::WorkingMode workingMode = cmake::NORMAL_MODE;
250   std::vector<std::string> parsedArgs;
251
252   using CommandArgument =
253     cmCommandLineArgument<bool(std::string const& value)>;
254   std::vector<CommandArgument> arguments = {
255     CommandArgument{
256       "-i", CommandArgument::Values::Zero,
257       [&wizard_mode](std::string const&) -> bool {
258         /* clang-format off */
259         std::cerr <<
260           "The \"cmake -i\" wizard mode is no longer supported.\n"
261           "Use the -D option to set cache values on the command line.\n"
262           "Use cmake-gui or ccmake for an interactive dialog.\n";
263         /* clang-format on */
264         wizard_mode = true;
265         return true;
266       } },
267     CommandArgument{ "--system-information", CommandArgument::Values::Zero,
268                      CommandArgument::setToTrue(sysinfo) },
269     CommandArgument{ "-N", CommandArgument::Values::Zero,
270                      CommandArgument::setToTrue(view_only) },
271     CommandArgument{ "-LAH", CommandArgument::Values::Zero,
272                      CommandArgument::setToTrue(list_all_cached, list_help) },
273     CommandArgument{ "-LA", CommandArgument::Values::Zero,
274                      CommandArgument::setToTrue(list_all_cached) },
275     CommandArgument{ "-LH", CommandArgument::Values::Zero,
276                      CommandArgument::setToTrue(list_cached, list_help) },
277     CommandArgument{ "-L", CommandArgument::Values::Zero,
278                      CommandArgument::setToTrue(list_cached) },
279     CommandArgument{ "-P", "No script specified for argument -P",
280                      CommandArgument::Values::One,
281                      CommandArgument::RequiresSeparator::No,
282                      [&](std::string const& value) -> bool {
283                        workingMode = cmake::SCRIPT_MODE;
284                        parsedArgs.emplace_back("-P");
285                        parsedArgs.push_back(value);
286                        return true;
287                      } },
288     CommandArgument{ "--find-package", CommandArgument::Values::Zero,
289                      [&](std::string const&) -> bool {
290                        workingMode = cmake::FIND_PACKAGE_MODE;
291                        parsedArgs.emplace_back("--find-package");
292                        return true;
293                      } },
294     CommandArgument{ "--list-presets", CommandArgument::Values::ZeroOrOne,
295                      [&](std::string const& value) -> bool {
296                        workingMode = cmake::HELP_MODE;
297                        parsedArgs.emplace_back("--list-presets");
298                        parsedArgs.emplace_back(value);
299                        return true;
300                      } },
301   };
302
303   std::vector<std::string> inputArgs;
304   inputArgs.reserve(ac);
305   cm::append(inputArgs, av, av + ac);
306
307   for (decltype(inputArgs.size()) i = 0; i < inputArgs.size(); ++i) {
308     std::string const& arg = inputArgs[i];
309     bool matched = false;
310     for (auto const& m : arguments) {
311       if (m.matches(arg)) {
312         matched = true;
313         if (m.parse(arg, i, inputArgs)) {
314           break;
315         }
316         return 1; // failed to parse
317       }
318     }
319     if (!matched) {
320       parsedArgs.emplace_back(av[i]);
321     }
322   }
323
324   if (wizard_mode) {
325     return 1;
326   }
327
328   if (sysinfo) {
329     cmake cm(cmake::RoleProject, cmState::Project);
330     cm.SetHomeDirectory("");
331     cm.SetHomeOutputDirectory("");
332     int ret = cm.GetSystemInformation(parsedArgs);
333     return ret;
334   }
335   cmake::Role const role =
336     workingMode == cmake::SCRIPT_MODE ? cmake::RoleScript : cmake::RoleProject;
337   cmState::Mode mode = cmState::Unknown;
338   switch (workingMode) {
339     case cmake::NORMAL_MODE:
340     case cmake::HELP_MODE:
341       mode = cmState::Project;
342       break;
343     case cmake::SCRIPT_MODE:
344       mode = cmState::Script;
345       break;
346     case cmake::FIND_PACKAGE_MODE:
347       mode = cmState::FindPackage;
348       break;
349   }
350   cmake cm(role, mode);
351   cm.SetHomeDirectory("");
352   cm.SetHomeOutputDirectory("");
353   cmSystemTools::SetMessageCallback(
354     [&cm](const std::string& msg, const cmMessageMetadata& md) {
355       cmakemainMessageCallback(msg, md, &cm);
356     });
357   cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
358     cmakemainProgressCallback(msg, prog, &cm);
359   });
360   cm.SetWorkingMode(workingMode);
361
362   int res = cm.Run(parsedArgs, view_only);
363   if (list_cached || list_all_cached) {
364     std::cout << "-- Cache values" << std::endl;
365     std::vector<std::string> keys = cm.GetState()->GetCacheEntryKeys();
366     for (std::string const& k : keys) {
367       cmStateEnums::CacheEntryType t = cm.GetState()->GetCacheEntryType(k);
368       if (t != cmStateEnums::INTERNAL && t != cmStateEnums::STATIC &&
369           t != cmStateEnums::UNINITIALIZED) {
370         cmValue advancedProp =
371           cm.GetState()->GetCacheEntryProperty(k, "ADVANCED");
372         if (list_all_cached || !advancedProp) {
373           if (list_help) {
374             cmValue help =
375               cm.GetState()->GetCacheEntryProperty(k, "HELPSTRING");
376             std::cout << "// " << (help ? *help : "") << std::endl;
377           }
378           std::cout << k << ":" << cmState::CacheEntryTypeToString(t) << "="
379                     << cm.GetState()->GetSafeCacheEntryValue(k) << std::endl;
380           if (list_help) {
381             std::cout << std::endl;
382           }
383         }
384       }
385     }
386   }
387
388   // Always return a non-negative value.  Windows tools do not always
389   // interpret negative return values as errors.
390   if (res != 0) {
391     return 1;
392   }
393   return 0;
394 }
395
396 #ifndef CMAKE_BOOTSTRAP
397 int extract_job_number(std::string const& command,
398                        std::string const& jobString)
399 {
400   int jobs = -1;
401   unsigned long numJobs = 0;
402   if (jobString.empty()) {
403     jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;
404   } else if (cmStrToULong(jobString, &numJobs)) {
405     if (numJobs == 0) {
406       std::cerr
407         << "The <jobs> value requires a positive integer argument.\n\n";
408     } else if (numJobs > INT_MAX) {
409       std::cerr << "The <jobs> value is too large.\n\n";
410     } else {
411       jobs = static_cast<int>(numJobs);
412     }
413   } else {
414     std::cerr << "'" << command << "' invalid number '" << jobString
415               << "' given.\n\n";
416   }
417   return jobs;
418 }
419 #endif
420
421 int do_build(int ac, char const* const* av)
422 {
423 #ifdef CMAKE_BOOTSTRAP
424   std::cerr << "This cmake does not support --build\n";
425   return -1;
426 #else
427   int jobs = cmake::NO_BUILD_PARALLEL_LEVEL;
428   std::vector<std::string> targets;
429   std::string config;
430   std::string dir;
431   std::vector<std::string> nativeOptions;
432   bool nativeOptionsPassed = false;
433   bool cleanFirst = false;
434   bool foundClean = false;
435   bool foundNonClean = false;
436   PackageResolveMode resolveMode = PackageResolveMode::Default;
437   bool verbose = cmSystemTools::HasEnv("VERBOSE");
438   std::string presetName;
439   bool listPresets = false;
440
441   auto jLambda = [&](std::string const& value) -> bool {
442     jobs = extract_job_number("-j", value);
443     if (jobs < 0) {
444       dir.clear();
445     }
446     return true;
447   };
448   auto parallelLambda = [&](std::string const& value) -> bool {
449     jobs = extract_job_number("--parallel", value);
450     if (jobs < 0) {
451       dir.clear();
452     }
453     return true;
454   };
455   auto targetLambda = [&](std::string const& value) -> bool {
456     if (!value.empty()) {
457       std::vector<std::string> values = cmExpandedList(value);
458       for (auto const& v : values) {
459         targets.emplace_back(v);
460         if (v == "clean") {
461           foundClean = true;
462         } else {
463           foundNonClean = true;
464         }
465       }
466       return true;
467     }
468     return false;
469   };
470   auto resolvePackagesLambda = [&](std::string const& value) -> bool {
471     std::string v = value;
472     std::transform(v.begin(), v.end(), v.begin(), ::tolower);
473
474     if (v == "on") {
475       resolveMode = PackageResolveMode::Force;
476     } else if (v == "only") {
477       resolveMode = PackageResolveMode::OnlyResolve;
478     } else if (v == "off") {
479       resolveMode = PackageResolveMode::Disable;
480     } else {
481       return false;
482     }
483
484     return true;
485   };
486   auto verboseLambda = [&](std::string const&) -> bool {
487     verbose = true;
488     return true;
489   };
490
491   using CommandArgument =
492     cmCommandLineArgument<bool(std::string const& value)>;
493
494   std::vector<CommandArgument> arguments = {
495     CommandArgument{ "--preset", CommandArgument::Values::One,
496                      CommandArgument::setToValue(presetName) },
497     CommandArgument{ "--list-presets", CommandArgument::Values::Zero,
498                      CommandArgument::setToTrue(listPresets) },
499     CommandArgument{ "-j", CommandArgument::Values::ZeroOrOne,
500                      CommandArgument::RequiresSeparator::No, jLambda },
501     CommandArgument{ "--parallel", CommandArgument::Values::ZeroOrOne,
502                      CommandArgument::RequiresSeparator::No, parallelLambda },
503     CommandArgument{ "-t", CommandArgument::Values::OneOrMore, targetLambda },
504     CommandArgument{ "--target", CommandArgument::Values::OneOrMore,
505                      targetLambda },
506     CommandArgument{ "--config", CommandArgument::Values::One,
507                      CommandArgument::setToValue(config) },
508     CommandArgument{ "--clean-first", CommandArgument::Values::Zero,
509                      CommandArgument::setToTrue(cleanFirst) },
510     CommandArgument{ "--resolve-package-references",
511                      CommandArgument::Values::One, resolvePackagesLambda },
512     CommandArgument{ "-v", CommandArgument::Values::Zero, verboseLambda },
513     CommandArgument{ "--verbose", CommandArgument::Values::Zero,
514                      verboseLambda },
515     /* legacy option no-op*/
516     CommandArgument{ "--use-stderr", CommandArgument::Values::Zero,
517                      [](std::string const&) -> bool { return true; } },
518     CommandArgument{ "--", CommandArgument::Values::Zero,
519                      CommandArgument::setToTrue(nativeOptionsPassed) },
520   };
521
522   if (ac >= 3) {
523     std::vector<std::string> inputArgs;
524
525     bool hasPreset = false;
526     for (int i = 2; i < ac; ++i) {
527       if (strcmp(av[i], "--list-presets") == 0 ||
528           cmHasLiteralPrefix(av[i], "--preset=") ||
529           strcmp(av[i], "--preset") == 0) {
530         hasPreset = true;
531         break;
532       }
533     }
534
535     if (hasPreset) {
536       inputArgs.reserve(ac - 2);
537       cm::append(inputArgs, av + 2, av + ac);
538     } else {
539       dir = cmSystemTools::CollapseFullPath(av[2]);
540
541       inputArgs.reserve(ac - 3);
542       cm::append(inputArgs, av + 3, av + ac);
543     }
544
545     decltype(inputArgs.size()) i = 0;
546     for (; i < inputArgs.size() && !nativeOptionsPassed; ++i) {
547
548       std::string const& arg = inputArgs[i];
549       bool matched = false;
550       bool parsed = false;
551       for (auto const& m : arguments) {
552         matched = m.matches(arg);
553         if (matched) {
554           parsed = m.parse(arg, i, inputArgs);
555           break;
556         }
557       }
558       if (!(matched && parsed)) {
559         dir.clear();
560         if (!matched) {
561           std::cerr << "Unknown argument " << arg << std::endl;
562         }
563         break;
564       }
565     }
566
567     if (nativeOptionsPassed) {
568       cm::append(nativeOptions, inputArgs.begin() + i, inputArgs.end());
569     }
570   }
571
572   if (foundClean && foundNonClean) {
573     std::cerr << "Error: Building 'clean' and other targets together "
574                  "is not supported."
575               << std::endl;
576     dir.clear();
577   }
578
579   if (jobs == cmake::NO_BUILD_PARALLEL_LEVEL) {
580     std::string parallel;
581     if (cmSystemTools::GetEnv("CMAKE_BUILD_PARALLEL_LEVEL", parallel)) {
582       if (parallel.empty()) {
583         jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;
584       } else {
585         unsigned long numJobs = 0;
586         if (cmStrToULong(parallel, &numJobs)) {
587           if (numJobs == 0) {
588             std::cerr << "The CMAKE_BUILD_PARALLEL_LEVEL environment variable "
589                          "requires a positive integer argument.\n\n";
590             dir.clear();
591           } else if (numJobs > INT_MAX) {
592             std::cerr << "The CMAKE_BUILD_PARALLEL_LEVEL environment variable "
593                          "is too large.\n\n";
594             dir.clear();
595           } else {
596             jobs = static_cast<int>(numJobs);
597           }
598         } else {
599           std::cerr << "'CMAKE_BUILD_PARALLEL_LEVEL' environment variable\n"
600                     << "invalid number '" << parallel << "' given.\n\n";
601           dir.clear();
602         }
603       }
604     }
605   }
606
607   if (dir.empty() && presetName.empty() && !listPresets) {
608     /* clang-format off */
609     std::cerr <<
610       "Usage: cmake --build <dir>            "
611       " [options] [-- [native-options]]\n"
612       "       cmake --build --preset <preset>"
613       " [options] [-- [native-options]]\n"
614       "Options:\n"
615       "  <dir>          = Project binary directory to be built.\n"
616       "  --preset <preset>, --preset=<preset>\n"
617       "                 = Specify a build preset.\n"
618       "  --list-presets[=<type>]\n"
619       "                 = List available build presets.\n"
620       "  --parallel [<jobs>], -j [<jobs>]\n"
621       "                 = Build in parallel using the given number of jobs. \n"
622       "                   If <jobs> is omitted the native build tool's \n"
623       "                   default number is used.\n"
624       "                   The CMAKE_BUILD_PARALLEL_LEVEL environment "
625       "variable\n"
626       "                   specifies a default parallel level when this "
627       "option\n"
628       "                   is not given.\n"
629       "  -t <tgt>..., --target <tgt>...\n"
630       "                 = Build <tgt> instead of default targets.\n"
631       "  --config <cfg> = For multi-configuration tools, choose <cfg>.\n"
632       "  --clean-first  = Build target 'clean' first, then build.\n"
633       "                   (To clean only, use --target 'clean'.)\n"
634       "  --resolve-package-references={on|only|off}\n"
635       "                 = Restore/resolve package references during build.\n"
636       "  -v, --verbose  = Enable verbose output - if supported - including\n"
637       "                   the build commands to be executed. \n"
638       "  --             = Pass remaining options to the native tool.\n"
639       ;
640     /* clang-format on */
641     return 1;
642   }
643
644   cmake cm(cmake::RoleInternal, cmState::Project);
645   cmSystemTools::SetMessageCallback(
646     [&cm](const std::string& msg, const cmMessageMetadata& md) {
647       cmakemainMessageCallback(msg, md, &cm);
648     });
649   cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
650     cmakemainProgressCallback(msg, prog, &cm);
651   });
652
653   cmBuildOptions buildOptions(cleanFirst, false, resolveMode);
654
655   return cm.Build(jobs, std::move(dir), std::move(targets), std::move(config),
656                   std::move(nativeOptions), buildOptions, verbose, presetName,
657                   listPresets);
658 #endif
659 }
660
661 bool parse_default_directory_permissions(const std::string& permissions,
662                                          std::string& parsedPermissionsVar)
663 {
664   std::vector<std::string> parsedPermissions;
665   enum Doing
666   {
667     DoingNone,
668     DoingOwner,
669     DoingGroup,
670     DoingWorld,
671     DoingOwnerAssignment,
672     DoingGroupAssignment,
673     DoingWorldAssignment,
674   };
675   Doing doing = DoingNone;
676
677   auto uniquePushBack = [&parsedPermissions](const std::string& e) {
678     if (std::find(parsedPermissions.begin(), parsedPermissions.end(), e) ==
679         parsedPermissions.end()) {
680       parsedPermissions.push_back(e);
681     }
682   };
683
684   for (auto const& e : permissions) {
685     switch (doing) {
686       case DoingNone:
687         if (e == 'u') {
688           doing = DoingOwner;
689         } else if (e == 'g') {
690           doing = DoingGroup;
691         } else if (e == 'o') {
692           doing = DoingWorld;
693         } else {
694           return false;
695         }
696         break;
697       case DoingOwner:
698         if (e == '=') {
699           doing = DoingOwnerAssignment;
700         } else {
701           return false;
702         }
703         break;
704       case DoingGroup:
705         if (e == '=') {
706           doing = DoingGroupAssignment;
707         } else {
708           return false;
709         }
710         break;
711       case DoingWorld:
712         if (e == '=') {
713           doing = DoingWorldAssignment;
714         } else {
715           return false;
716         }
717         break;
718       case DoingOwnerAssignment:
719         if (e == 'r') {
720           uniquePushBack("OWNER_READ");
721         } else if (e == 'w') {
722           uniquePushBack("OWNER_WRITE");
723         } else if (e == 'x') {
724           uniquePushBack("OWNER_EXECUTE");
725         } else if (e == ',') {
726           doing = DoingNone;
727         } else {
728           return false;
729         }
730         break;
731       case DoingGroupAssignment:
732         if (e == 'r') {
733           uniquePushBack("GROUP_READ");
734         } else if (e == 'w') {
735           uniquePushBack("GROUP_WRITE");
736         } else if (e == 'x') {
737           uniquePushBack("GROUP_EXECUTE");
738         } else if (e == ',') {
739           doing = DoingNone;
740         } else {
741           return false;
742         }
743         break;
744       case DoingWorldAssignment:
745         if (e == 'r') {
746           uniquePushBack("WORLD_READ");
747         } else if (e == 'w') {
748           uniquePushBack("WORLD_WRITE");
749         } else if (e == 'x') {
750           uniquePushBack("WORLD_EXECUTE");
751         } else if (e == ',') {
752           doing = DoingNone;
753         } else {
754           return false;
755         }
756         break;
757     }
758   }
759   if (doing != DoingOwnerAssignment && doing != DoingGroupAssignment &&
760       doing != DoingWorldAssignment) {
761     return false;
762   }
763
764   std::ostringstream oss;
765   for (auto i = 0u; i < parsedPermissions.size(); i++) {
766     if (i != 0) {
767       oss << ";";
768     }
769     oss << parsedPermissions[i];
770   }
771
772   parsedPermissionsVar = oss.str();
773   return true;
774 }
775
776 int do_install(int ac, char const* const* av)
777 {
778 #ifdef CMAKE_BOOTSTRAP
779   std::cerr << "This cmake does not support --install\n";
780   return -1;
781 #else
782   assert(1 < ac);
783
784   std::string config;
785   std::string component;
786   std::string defaultDirectoryPermissions;
787   std::string prefix;
788   std::string dir;
789   bool strip = false;
790   bool verbose = cmSystemTools::HasEnv("VERBOSE");
791
792   auto verboseLambda = [&](std::string const&) -> bool {
793     verbose = true;
794     return true;
795   };
796
797   using CommandArgument =
798     cmCommandLineArgument<bool(std::string const& value)>;
799
800   std::vector<CommandArgument> arguments = {
801     CommandArgument{ "--config", CommandArgument::Values::One,
802                      CommandArgument::setToValue(config) },
803     CommandArgument{ "--component", CommandArgument::Values::One,
804                      CommandArgument::setToValue(component) },
805     CommandArgument{
806       "--default-directory-permissions", CommandArgument::Values::One,
807       CommandArgument::setToValue(defaultDirectoryPermissions) },
808     CommandArgument{ "--prefix", CommandArgument::Values::One,
809                      CommandArgument::setToValue(prefix) },
810     CommandArgument{ "--strip", CommandArgument::Values::Zero,
811                      CommandArgument::setToTrue(strip) },
812     CommandArgument{ "-v", CommandArgument::Values::Zero, verboseLambda },
813     CommandArgument{ "--verbose", CommandArgument::Values::Zero,
814                      verboseLambda }
815   };
816
817   if (ac >= 3) {
818     dir = cmSystemTools::CollapseFullPath(av[2]);
819
820     std::vector<std::string> inputArgs;
821     inputArgs.reserve(ac - 3);
822     cm::append(inputArgs, av + 3, av + ac);
823     for (decltype(inputArgs.size()) i = 0; i < inputArgs.size(); ++i) {
824
825       std::string const& arg = inputArgs[i];
826       bool matched = false;
827       bool parsed = false;
828       for (auto const& m : arguments) {
829         matched = m.matches(arg);
830         if (matched) {
831           parsed = m.parse(arg, i, inputArgs);
832           break;
833         }
834       }
835       if (!(matched && parsed)) {
836         dir.clear();
837         if (!matched) {
838           std::cerr << "Unknown argument " << arg << std::endl;
839         }
840         break;
841       }
842     }
843   }
844
845   if (dir.empty()) {
846     /* clang-format off */
847     std::cerr <<
848       "Usage: cmake --install <dir> [options]\n"
849       "Options:\n"
850       "  <dir>              = Project binary directory to install.\n"
851       "  --config <cfg>     = For multi-configuration tools, choose <cfg>.\n"
852       "  --component <comp> = Component-based install. Only install <comp>.\n"
853       "  --default-directory-permissions <permission> \n"
854       "     Default install permission. Use default permission <permission>.\n"
855       "  --prefix <prefix>  = The installation prefix CMAKE_INSTALL_PREFIX.\n"
856       "  --strip            = Performing install/strip.\n"
857       "  -v --verbose       = Enable verbose output.\n"
858       ;
859     /* clang-format on */
860     return 1;
861   }
862
863   cmake cm(cmake::RoleScript, cmState::Script);
864
865   cmSystemTools::SetMessageCallback(
866     [&cm](const std::string& msg, const cmMessageMetadata& md) {
867       cmakemainMessageCallback(msg, md, &cm);
868     });
869   cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
870     cmakemainProgressCallback(msg, prog, &cm);
871   });
872   cm.SetHomeDirectory("");
873   cm.SetHomeOutputDirectory("");
874   cm.SetDebugOutputOn(verbose);
875   cm.SetWorkingMode(cmake::SCRIPT_MODE);
876
877   std::vector<std::string> args{ av[0] };
878
879   if (!prefix.empty()) {
880     args.emplace_back("-DCMAKE_INSTALL_PREFIX=" + prefix);
881   }
882
883   if (!component.empty()) {
884     args.emplace_back("-DCMAKE_INSTALL_COMPONENT=" + component);
885   }
886
887   if (strip) {
888     args.emplace_back("-DCMAKE_INSTALL_DO_STRIP=1");
889   }
890
891   if (!config.empty()) {
892     args.emplace_back("-DCMAKE_INSTALL_CONFIG_NAME=" + config);
893   }
894
895   if (!defaultDirectoryPermissions.empty()) {
896     std::string parsedPermissionsVar;
897     if (!parse_default_directory_permissions(defaultDirectoryPermissions,
898                                              parsedPermissionsVar)) {
899       std::cerr << "--default-directory-permissions is in incorrect format"
900                 << std::endl;
901       return 1;
902     }
903     args.emplace_back("-DCMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS=" +
904                       parsedPermissionsVar);
905   }
906
907   args.emplace_back("-P");
908   args.emplace_back(dir + "/cmake_install.cmake");
909
910   return cm.Run(args) ? 1 : 0;
911 #endif
912 }
913
914 int do_workflow(int ac, char const* const* av)
915 {
916 #ifdef CMAKE_BOOTSTRAP
917   std::cerr << "This cmake does not support --workflow\n";
918   return -1;
919 #else
920   using WorkflowListPresets = cmake::WorkflowListPresets;
921   using WorkflowFresh = cmake::WorkflowFresh;
922   std::string presetName;
923   auto listPresets = WorkflowListPresets::No;
924   auto fresh = WorkflowFresh::No;
925
926   using CommandArgument =
927     cmCommandLineArgument<bool(std::string const& value)>;
928
929   std::vector<CommandArgument> arguments = {
930     CommandArgument{ "--preset", CommandArgument::Values::One,
931                      CommandArgument::setToValue(presetName) },
932     CommandArgument{ "--list-presets", CommandArgument::Values::Zero,
933                      [&listPresets](const std::string&) -> bool {
934                        listPresets = WorkflowListPresets::Yes;
935                        return true;
936                      } },
937     CommandArgument{ "--fresh", CommandArgument::Values::Zero,
938                      [&fresh](const std::string&) -> bool {
939                        fresh = WorkflowFresh::Yes;
940                        return true;
941                      } },
942   };
943
944   std::vector<std::string> inputArgs;
945
946   inputArgs.reserve(ac - 2);
947   cm::append(inputArgs, av + 2, av + ac);
948
949   decltype(inputArgs.size()) i = 0;
950   for (; i < inputArgs.size(); ++i) {
951     std::string const& arg = inputArgs[i];
952     bool matched = false;
953     bool parsed = false;
954     for (auto const& m : arguments) {
955       matched = m.matches(arg);
956       if (matched) {
957         parsed = m.parse(arg, i, inputArgs);
958         break;
959       }
960     }
961     if (!(matched && parsed)) {
962       if (!matched) {
963         presetName.clear();
964         listPresets = WorkflowListPresets::No;
965         std::cerr << "Unknown argument " << arg << std::endl;
966       }
967       break;
968     }
969   }
970
971   if (presetName.empty() && listPresets == WorkflowListPresets::No) {
972     /* clang-format off */
973     std::cerr <<
974       "Usage: cmake --workflow [options]\n"
975       "Options:\n"
976       "  --preset <preset> = Workflow preset to execute.\n"
977       "  --list-presets    = List available workflow presets.\n"
978       "  --fresh           = Configure a fresh build tree, removing any "
979                             "existing cache file.\n"
980       ;
981     /* clang-format on */
982     return 1;
983   }
984
985   cmake cm(cmake::RoleInternal, cmState::Project);
986   cmSystemTools::SetMessageCallback(
987     [&cm](const std::string& msg, const cmMessageMetadata& md) {
988       cmakemainMessageCallback(msg, md, &cm);
989     });
990   cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
991     cmakemainProgressCallback(msg, prog, &cm);
992   });
993
994   return cm.Workflow(presetName, listPresets, fresh);
995 #endif
996 }
997
998 int do_open(int ac, char const* const* av)
999 {
1000 #ifdef CMAKE_BOOTSTRAP
1001   std::cerr << "This cmake does not support --open\n";
1002   return -1;
1003 #else
1004   std::string dir;
1005
1006   enum Doing
1007   {
1008     DoingNone,
1009     DoingDir,
1010   };
1011   Doing doing = DoingDir;
1012   for (int i = 2; i < ac; ++i) {
1013     switch (doing) {
1014       case DoingDir:
1015         dir = cmSystemTools::CollapseFullPath(av[i]);
1016         doing = DoingNone;
1017         break;
1018       default:
1019         std::cerr << "Unknown argument " << av[i] << std::endl;
1020         dir.clear();
1021         break;
1022     }
1023   }
1024   if (dir.empty()) {
1025     std::cerr << "Usage: cmake --open <dir>\n";
1026     return 1;
1027   }
1028
1029   cmake cm(cmake::RoleInternal, cmState::Unknown);
1030   cmSystemTools::SetMessageCallback(
1031     [&cm](const std::string& msg, const cmMessageMetadata& md) {
1032       cmakemainMessageCallback(msg, md, &cm);
1033     });
1034   cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
1035     cmakemainProgressCallback(msg, prog, &cm);
1036   });
1037   return cm.Open(dir, false) ? 0 : 1;
1038 #endif
1039 }
1040 } // namespace
1041
1042 int main(int ac, char const* const* av)
1043 {
1044   cmSystemTools::EnsureStdPipes();
1045
1046   // Replace streambuf so we can output Unicode to console
1047   auto consoleBuf = cm::make_unique<cmConsoleBuf>();
1048   consoleBuf->SetUTF8Pipes();
1049
1050   cmsys::Encoding::CommandLineArguments args =
1051     cmsys::Encoding::CommandLineArguments::Main(ac, av);
1052   ac = args.argc();
1053   av = args.argv();
1054
1055   cmSystemTools::InitializeLibUV();
1056   cmSystemTools::FindCMakeResources(av[0]);
1057   if (ac > 1) {
1058     if (strcmp(av[1], "--build") == 0) {
1059       return do_build(ac, av);
1060     }
1061     if (strcmp(av[1], "--install") == 0) {
1062       return do_install(ac, av);
1063     }
1064     if (strcmp(av[1], "--open") == 0) {
1065       return do_open(ac, av);
1066     }
1067     if (strcmp(av[1], "--workflow") == 0) {
1068       return do_workflow(ac, av);
1069     }
1070     if (strcmp(av[1], "-E") == 0) {
1071       return do_command(ac, av, std::move(consoleBuf));
1072     }
1073   }
1074   int ret = do_cmake(ac, av);
1075 #ifndef CMAKE_BOOTSTRAP
1076   cmDynamicLoader::FlushCache();
1077 #endif
1078   if (uv_loop_t* loop = uv_default_loop()) {
1079     uv_loop_close(loop);
1080   }
1081   return ret;
1082 }