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 <csignal>
5 #include <cstdio>
6 #include <cstdlib>
7 #include <cstring>
8 #include <iostream>
9 #include <string>
10 #include <vector>
11 
12 #include <unistd.h>
13 
14 #include "cmsys/Encoding.hxx"
15 
16 #include "cmCursesColor.h"
17 #include "cmCursesForm.h"
18 #include "cmCursesMainForm.h"
19 #include "cmCursesStandardIncludes.h"
20 #include "cmDocumentation.h"
21 #include "cmDocumentationEntry.h" // IWYU pragma: keep
22 #include "cmMessageMetadata.h"
23 #include "cmState.h"
24 #include "cmStringAlgorithms.h"
25 #include "cmSystemTools.h"
26 #include "cmake.h"
27 
28 static const char* cmDocumentationName[][2] = {
29   { nullptr, "  ccmake - Curses Interface for CMake." },
30   { nullptr, nullptr }
31 };
32 
33 static const char* cmDocumentationUsage[][2] = {
34   { nullptr,
35     "  ccmake <path-to-source>\n"
36     "  ccmake <path-to-existing-build>" },
37   { nullptr,
38     "Specify a source directory to (re-)generate a build system for "
39     "it in the current working directory.  Specify an existing build "
40     "directory to re-generate its build system." },
41   { nullptr, nullptr }
42 };
43 
44 static const char* cmDocumentationUsageNote[][2] = {
45   { nullptr, "Run 'ccmake --help' for more information." },
46   { nullptr, nullptr }
47 };
48 
49 static const char* cmDocumentationOptions[][2] = {
50   CMAKE_STANDARD_OPTIONS_TABLE,
51   { nullptr, nullptr }
52 };
53 
54 cmCursesForm* cmCursesForm::CurrentForm = nullptr;
55 
56 extern "C" {
57 
onsig(int)58 void onsig(int /*unused*/)
59 {
60   if (cmCursesForm::CurrentForm) {
61     endwin();
62     if (initscr() == nullptr) {
63       static const char errmsg[] = "Error: ncurses initialization failed\n";
64       auto r = write(STDERR_FILENO, errmsg, sizeof(errmsg) - 1);
65       static_cast<void>(r);
66       exit(1);
67     }
68     noecho();             /* Echo off */
69     cbreak();             /* nl- or cr not needed */
70     keypad(stdscr, true); /* Use key symbols as KEY_DOWN */
71     refresh();
72     int x;
73     int y;
74     getmaxyx(stdscr, y, x);
75     cmCursesForm::CurrentForm->Render(1, 1, x, y);
76     cmCursesForm::CurrentForm->UpdateStatusBar();
77   }
78   signal(SIGWINCH, onsig);
79 }
80 }
81 
main(int argc,char const * const * argv)82 int main(int argc, char const* const* argv)
83 {
84   cmSystemTools::EnsureStdPipes();
85   cmsys::Encoding::CommandLineArguments encoding_args =
86     cmsys::Encoding::CommandLineArguments::Main(argc, argv);
87   argc = encoding_args.argc();
88   argv = encoding_args.argv();
89 
90   cmSystemTools::InitializeLibUV();
91   cmSystemTools::FindCMakeResources(argv[0]);
92   cmDocumentation doc;
93   doc.addCMakeStandardDocSections();
94   if (doc.CheckOptions(argc, argv)) {
95     cmake hcm(cmake::RoleInternal, cmState::Unknown);
96     hcm.SetHomeDirectory("");
97     hcm.SetHomeOutputDirectory("");
98     hcm.AddCMakePaths();
99     auto generators = hcm.GetGeneratorsDocumentation();
100     doc.SetName("ccmake");
101     doc.SetSection("Name", cmDocumentationName);
102     doc.SetSection("Usage", cmDocumentationUsage);
103     if (argc == 1) {
104       doc.AppendSection("Usage", cmDocumentationUsageNote);
105     }
106     doc.AppendSection("Generators", generators);
107     doc.PrependSection("Options", cmDocumentationOptions);
108     return doc.PrintRequestedDocumentation(std::cout) ? 0 : 1;
109   }
110 
111   bool debug = false;
112   unsigned int i;
113   int j;
114   std::vector<std::string> args;
115   for (j = 0; j < argc; ++j) {
116     if (strcmp(argv[j], "-debug") == 0) {
117       debug = true;
118     } else {
119       args.emplace_back(argv[j]);
120     }
121   }
122 
123   std::string cacheDir = cmSystemTools::GetCurrentWorkingDirectory();
124   for (i = 1; i < args.size(); ++i) {
125     std::string const& arg = args[i];
126     if (cmHasPrefix(arg, "-B")) {
127       cacheDir = arg.substr(2);
128     }
129   }
130 
131   cmSystemTools::DisableRunCommandOutput();
132 
133   if (debug) {
134     cmCursesForm::DebugStart();
135   }
136 
137   if (initscr() == nullptr) {
138     fprintf(stderr, "Error: ncurses initialization failed\n");
139     exit(1);
140   }
141   noecho();             /* Echo off */
142   cbreak();             /* nl- or cr not needed */
143   keypad(stdscr, true); /* Use key symbols as KEY_DOWN */
144   cmCursesColor::InitColors();
145 
146   signal(SIGWINCH, onsig);
147 
148   int x;
149   int y;
150   getmaxyx(stdscr, y, x);
151   if (x < cmCursesMainForm::MIN_WIDTH || y < cmCursesMainForm::MIN_HEIGHT) {
152     endwin();
153     std::cerr << "Window is too small. A size of at least "
154               << cmCursesMainForm::MIN_WIDTH << " x "
155               << cmCursesMainForm::MIN_HEIGHT << " is required to run ccmake."
156               << std::endl;
157     return 1;
158   }
159 
160   cmCursesMainForm* myform;
161 
162   myform = new cmCursesMainForm(args, x);
163   if (myform->LoadCache(cacheDir.c_str())) {
164     curses_clear();
165     touchwin(stdscr);
166     endwin();
167     delete myform;
168     std::cerr << "Error running cmake::LoadCache().  Aborting.\n";
169     return 1;
170   }
171 
172   /*
173    * The message is stored in a list by the form which will be
174    * joined by '\n' before display.
175    * Removing any trailing '\n' avoid extra empty lines in the final results
176    */
177   auto cleanMessage = [](const std::string& message) -> std::string {
178     auto msg = message;
179     if (!msg.empty() && msg.back() == '\n') {
180       msg.pop_back();
181     }
182     return msg;
183   };
184   cmSystemTools::SetMessageCallback(
185     [&](const std::string& message, const cmMessageMetadata& md) {
186       myform->AddError(cleanMessage(message), md.title);
187     });
188   cmSystemTools::SetStderrCallback([&](const std::string& message) {
189     myform->AddError(cleanMessage(message), "");
190   });
191   cmSystemTools::SetStdoutCallback([&](const std::string& message) {
192     myform->UpdateProgress(cleanMessage(message), -1);
193   });
194 
195   cmCursesForm::CurrentForm = myform;
196 
197   myform->InitializeUI();
198   if (myform->Configure(1) == 0) {
199     myform->Render(1, 1, x, y);
200     myform->HandleInput();
201   }
202 
203   // Need to clean-up better
204   curses_clear();
205   touchwin(stdscr);
206   endwin();
207   delete cmCursesForm::CurrentForm;
208   cmCursesForm::CurrentForm = nullptr;
209 
210   std::cout << std::endl << std::endl;
211 
212   return 0;
213 }
214