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