QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCCommandLineParser.cc
Go to the documentation of this file.
2#include "qgc_version.h"
3
4#include <QtCore/QCommandLineOption>
5#include <QtCore/QCoreApplication>
6
8
9QGC_LOGGING_CATEGORY(QGCCommandLineParserLog, "Utilities.QGCCommandLineParser")
10
11namespace QGCCommandLineParser {
12
13// ============================================================================
14// Option Name Constants
15// ============================================================================
16
17namespace {
18
19// --- Core options ---
20constexpr QLatin1StringView kOptSystemId = QLatin1StringView("system-id");
21constexpr QLatin1StringView kOptClearSettings = QLatin1StringView("clear-settings");
22constexpr QLatin1StringView kOptClearCache = QLatin1StringView("clear-cache");
23constexpr QLatin1StringView kOptLogging = QLatin1StringView("logging");
24constexpr QLatin1StringView kOptLogOutput = QLatin1StringView("log-output");
25constexpr QLatin1StringView kOptSimpleBoot = QLatin1StringView("simple-boot-test");
26
27#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
28// --- Desktop-only options ---
29constexpr QLatin1StringView kOptFakeMobile = QLatin1StringView("fake-mobile");
30constexpr QLatin1StringView kOptAllowMultiple = QLatin1StringView("allow-multiple");
31#endif
32
33#ifdef QGC_UNITTEST_BUILD
34// --- Test options ---
35constexpr QLatin1StringView kOptUnittest = QLatin1StringView("unittest");
36constexpr QLatin1StringView kOptUnittestStress = QLatin1StringView("unittest-stress");
37constexpr QLatin1StringView kOptUnittestOutput = QLatin1StringView("unittest-output");
38constexpr QLatin1StringView kOptUnittestLabel = QLatin1StringView("label");
39constexpr QLatin1StringView kOptListTests = QLatin1StringView("list-tests");
40constexpr QLatin1StringView kOptOnscreen = QLatin1StringView("onscreen");
41#endif
42
43#ifdef Q_OS_WIN
44// --- Windows-only options ---
45constexpr QLatin1StringView kOptNoWinAssertUI = QLatin1StringView("no-windows-assert-ui");
46#endif
47
48#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
49// --- Windows/macOS options ---
50constexpr QLatin1StringView kOptSwrast = QLatin1StringView("swrast");
51#endif
52
53} // anonymous namespace
54
55// ============================================================================
56// Argument Normalization
57// ============================================================================
58
62#ifdef QGC_UNITTEST_BUILD
63QStringList normalizeArgs(const QStringList &args)
64#else
65static QStringList normalizeArgs(const QStringList &args)
66#endif
67{
68 QStringList out;
69 out.reserve(args.size() + 4);
70
71 for (qsizetype i = 0; i < args.size(); ++i) {
72 const QString &arg = args.at(i);
73#ifdef QGC_UNITTEST_BUILD
74 if (arg == QLatin1String("--unittest")) {
75 const bool valueFollows = ((i + 1) < args.size()) && !args.at(i + 1).startsWith(QLatin1Char('-'));
76 out << arg;
77 if (!valueFollows) {
78 out << QString();
79 }
80 continue;
81 }
82#endif
83
84 // Convert --option:value to --option value
85 if (arg.startsWith(QLatin1String("--")) && arg.contains(QLatin1Char(':'))) {
86 const qsizetype idx = arg.indexOf(QLatin1Char(':'));
87 const QString opt = arg.left(idx);
88 const QString val = arg.mid(idx + 1);
89 out << opt;
90 if (!val.isEmpty()) {
91 out << val;
92 }
93#ifdef QGC_UNITTEST_BUILD
94 else if (opt == QLatin1String("--unittest")) {
95 out << QString();
96 }
97#endif
98 } else {
99 out << arg;
100 }
101 }
102
103 qCDebug(QGCCommandLineParserLog) << "Normalized arguments:" << out;
104 return out;
105}
106
107// ============================================================================
108// Command Line Parsing
109// ============================================================================
110
112{
113 // Set application info (needed for --help and --version)
114 QCoreApplication::setApplicationName(QLatin1String(QGC_APP_NAME));
115 QCoreApplication::setApplicationVersion(QLatin1String(QGC_APP_VERSION_STR));
116
118 out.parser = std::make_unique<QCommandLineParser>();
119
120 QCommandLineParser& parser = *out.parser;
121 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
122 parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions);
123 parser.setApplicationDescription(QStringLiteral(QGC_APP_DESCRIPTION));
124
125 // --- Standard options ---
126 const QCommandLineOption helpOption = parser.addHelpOption();
127 const QCommandLineOption versionOption = parser.addVersionOption();
128
129 // --- Core options (always available) ---
130 const QCommandLineOption systemIdOpt(
131 QString(kOptSystemId),
132 QCoreApplication::translate("main", "MAVLink GCS system id."),
133 QCoreApplication::translate("main", "id"));
134 (void) parser.addOption(systemIdOpt);
135
136 const QCommandLineOption clearSettingsOpt(
137 QString(kOptClearSettings),
138 QCoreApplication::translate("main", "Clear stored application settings."));
139 (void) parser.addOption(clearSettingsOpt);
140
141 const QCommandLineOption clearCacheOpt(
142 QString(kOptClearCache),
143 QCoreApplication::translate("main", "Clear parameter and airframe caches."));
144 (void) parser.addOption(clearCacheOpt);
145
146 const QCommandLineOption loggingOpt(
147 QString(kOptLogging),
148 QCoreApplication::translate("main", "Enable logging with optional rules string."),
149 QCoreApplication::translate("main", "rules"));
150 (void) parser.addOption(loggingOpt);
151
152 const QCommandLineOption logOutputOpt(
153 QString(kOptLogOutput),
154 QCoreApplication::translate("main", "Log to console."));
155 (void) parser.addOption(logOutputOpt);
156
157 const QCommandLineOption simpleBootOpt(
158 QString(kOptSimpleBoot),
159 QCoreApplication::translate("main", "Initialize subsystems and exit."));
160 (void) parser.addOption(simpleBootOpt);
161
162#ifdef QGC_UNITTEST_BUILD
163 // --- Test options (only in test builds) ---
164 const QCommandLineOption unittestOpt(
165 QString(kOptUnittest),
166 QCoreApplication::translate("main", "Run unit tests (optional filter value)."),
167 QCoreApplication::translate("main", "filter"));
168 (void) parser.addOption(unittestOpt);
169
170 const QCommandLineOption unittestStressOpt(
171 QString(kOptUnittestStress),
172 QCoreApplication::translate("main", "Stress unit tests."),
173 QCoreApplication::translate("main", "count"));
174 (void) parser.addOption(unittestStressOpt);
175
176 const QCommandLineOption unittestOutputOpt(
177 QString(kOptUnittestOutput),
178 QCoreApplication::translate("main", "Output test results to file (JUnit XML format)."),
179 QCoreApplication::translate("main", "file"));
180 (void) parser.addOption(unittestOutputOpt);
181
182 const QCommandLineOption unittestLabelOpt(
183 QString(kOptUnittestLabel),
184 QCoreApplication::translate("main", "Filter tests by label (unit, integration, vehicle, missionmanager, etc.)."),
185 QCoreApplication::translate("main", "labels"));
186 (void) parser.addOption(unittestLabelOpt);
187
188 const QCommandLineOption listTestsOpt(
189 QString(kOptListTests),
190 QCoreApplication::translate("main", "List available unit tests and exit."));
191 (void) parser.addOption(listTestsOpt);
192
193 const QCommandLineOption onscreenOpt(
194 QString(kOptOnscreen),
195 QCoreApplication::translate("main", "Show test windows on screen instead of running offscreen."));
196 (void) parser.addOption(onscreenOpt);
197#endif
198
199#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
200 // --- Desktop-only options ---
201 const QCommandLineOption fakeMobileOpt(
202 QString(kOptFakeMobile),
203 QCoreApplication::translate("main", "Run with mobile-style UI."));
204 (void) parser.addOption(fakeMobileOpt);
205
206 const QCommandLineOption allowMultipleOpt(
207 QString(kOptAllowMultiple),
208 QCoreApplication::translate("main", "Bypass single-instance guard."));
209 (void) parser.addOption(allowMultipleOpt);
210#endif
211
212#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
213 // --- Windows/macOS options ---
214 const QCommandLineOption swrastOpt(QString(kOptSwrast),
215 QCoreApplication::translate("main", "Force software rendering."));
216 (void) parser.addOption(swrastOpt);
217#endif
218
219#ifdef Q_OS_WIN
220 // --- Windows-only options ---
221 const QCommandLineOption quietWinAssertOpt(
222 QString(kOptNoWinAssertUI),
223 QCoreApplication::translate("main", "Disable Windows assert dialog boxes."));
224 (void) parser.addOption(quietWinAssertOpt);
225#endif
226
227 // --- Parse arguments ---
228 const QStringList normalizedArgs = normalizeArgs(QCoreApplication::arguments());
229 parser.process(normalizedArgs);
230
231 // --- Validate unknown options ---
232 out.unknownOptions = parser.unknownOptionNames();
233
234 // Check for platform-specific option errors on wrong platform
235 if (!out.unknownOptions.isEmpty()) {
236 // Check for test options in non-test build
237 if (out.unknownOptions.contains(QLatin1String("unittest")) ||
238 out.unknownOptions.contains(QLatin1String("unittest-stress")) ||
239 out.unknownOptions.contains(QLatin1String("unittest-output")) ||
240 out.unknownOptions.contains(QLatin1String("list-tests"))) {
241#ifndef QGC_UNITTEST_BUILD
242 out.statusCode = CommandLineParseResult::Status::Error;
243 out.errorString = QCoreApplication::translate("main",
244 "--unittest/--unittest-stress/--unittest-output/--list-tests options are only available in unittest builds.");
245 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
246 return out;
247#endif
248 }
249
250#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
251 // Mobile platforms don't support desktop options
252 if (out.unknownOptions.contains(QLatin1String("fake-mobile")) ||
253 out.unknownOptions.contains(QLatin1String("allow-multiple"))) {
254 out.statusCode = CommandLineParseResult::Status::Error;
255 out.errorString = QCoreApplication::translate("main",
256 "--fake-mobile/--allow-multiple are not supported on mobile platforms.");
257 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
258 return out;
259 }
260#endif
261
262#ifndef Q_OS_WIN
263 // Non-Windows platforms don't support Windows-specific options
264 if (out.unknownOptions.contains(QLatin1String("no-windows-assert-ui"))) {
265 out.statusCode = CommandLineParseResult::Status::Error;
266 out.errorString = QCoreApplication::translate("main",
267 "--no-windows-assert-ui is only supported on Windows.");
268 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
269 return out;
270 }
271#endif
272
273#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS)
274 // Non-Windows/macOS platforms don't support swrast
275 if (out.unknownOptions.contains(QLatin1String("swrast"))) {
276 out.statusCode = CommandLineParseResult::Status::Error;
277 out.errorString = QCoreApplication::translate("main",
278 "--swrast is only supported on Windows and macOS.");
279 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
280 return out;
281 }
282#endif
283
284 // Generic unknown options error
285 out.statusCode = CommandLineParseResult::Status::Error;
286 out.errorString = QCoreApplication::translate("main", "Unknown options: %1")
287 .arg(out.unknownOptions.join(QLatin1String(", ")));
288 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
289 return out;
290 }
291
292 // --- Validate positional arguments ---
293 out.positional = parser.positionalArguments();
294 if (!out.positional.isEmpty()) {
295 out.statusCode = CommandLineParseResult::Status::Error;
296 out.errorString = QCoreApplication::translate("main", "Unexpected positional arguments: %1")
297 .arg(out.positional.join(QLatin1String(", ")));
298 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
299 return out;
300 }
301
302 // --- Handle help/version requests ---
303 if (parser.isSet(helpOption)) {
304 out.statusCode = CommandLineParseResult::Status::HelpRequested;
305 out.helpText = parser.helpText();
306 return out;
307 }
308
309 if (parser.isSet(versionOption)) {
310 out.statusCode = CommandLineParseResult::Status::VersionRequested;
311 out.versionText = QCoreApplication::applicationVersion();
312 return out;
313 }
314
315 // --- Parse core options ---
316 if (parser.isSet(systemIdOpt)) {
317 const QString systemIdStr = parser.value(systemIdOpt);
318 bool ok = false;
319 const uint systemId = systemIdStr.toUInt(&ok);
320 if (!ok || (systemId < 1) || (systemId > 255)) {
321 out.statusCode = CommandLineParseResult::Status::Error;
322 out.errorString = QCoreApplication::translate("main", "Invalid System ID (must be 1-255): %1")
323 .arg(systemIdStr);
324 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
325 return out;
326 }
327 out.systemId = static_cast<quint8>(systemId);
328 qCDebug(QGCCommandLineParserLog) << "System ID:" << systemId;
329 }
330
331 out.clearSettingsOptions = parser.isSet(clearSettingsOpt);
332 out.clearCache = parser.isSet(clearCacheOpt);
333 if (parser.isSet(loggingOpt)) {
334 out.loggingOptions = parser.value(loggingOpt);
335 qCDebug(QGCCommandLineParserLog) << "Logging options:" << out.loggingOptions.value();
336 }
337 out.logOutput = parser.isSet(logOutputOpt);
338 out.simpleBootTest = parser.isSet(simpleBootOpt);
339
340#ifdef QGC_UNITTEST_BUILD
341 // --- Parse test options ---
342 if (parser.isSet(unittestOpt)) {
343 out.runningUnitTests = true;
344 const QStringList vals = parser.values(unittestOpt);
345 // Filter out empty values (from bare --unittest)
346 for (const QString& val : vals) {
347 if (!val.isEmpty()) {
348 out.unitTests.append(val);
349 }
350 }
351 qCDebug(QGCCommandLineParserLog) << "Unit tests:" << (out.unitTests.isEmpty() ? QStringLiteral("all") : out.unitTests.join(QLatin1String(", ")));
352 }
353
354 if (parser.isSet(unittestStressOpt)) {
355 out.runningUnitTests = true;
356 out.stressUnitTests = true;
357 const QString stress = parser.value(unittestStressOpt);
358 if (stress.isEmpty()) {
359 out.stressUnitTestsCount = 20;
360 } else {
361 bool ok = false;
362 const uint count = stress.toUInt(&ok);
363 if (!ok || (count == 0)) {
364 out.statusCode = CommandLineParseResult::Status::Error;
365 out.errorString = QCoreApplication::translate("main", "Invalid stress test count (must be > 0): %1")
366 .arg(stress);
367 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
368 return out;
369 }
370 out.stressUnitTestsCount = count;
371 }
372 qCDebug(QGCCommandLineParserLog) << "Stress test iterations:" << out.stressUnitTestsCount;
373 }
374
375 if (parser.isSet(unittestOutputOpt)) {
376 out.unitTestOutput = parser.value(unittestOutputOpt);
377 qCDebug(QGCCommandLineParserLog) << "Test output file:" << out.unitTestOutput.value();
378 }
379
380 if (parser.isSet(unittestLabelOpt)) {
381 out.labelFilter = parser.value(unittestLabelOpt);
382 qCDebug(QGCCommandLineParserLog) << "Label filter:" << out.labelFilter.value();
383 }
384
385 out.listTests = parser.isSet(listTestsOpt);
386 if (out.listTests) {
387 qCDebug(QGCCommandLineParserLog) << "List tests requested";
388 }
389
390 out.onscreen = parser.isSet(onscreenOpt);
391#endif
392
393 // --- Parse desktop options ---
394#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
395 out.fakeMobile = parser.isSet(fakeMobileOpt);
396 out.allowMultiple = parser.isSet(allowMultipleOpt);
397#else
398 out.fakeMobile = false;
399 out.allowMultiple = false;
400#endif
401
402 // --- Parse graphics options ---
403#ifdef Q_OS_WIN
404 out.quietWindowsAsserts = parser.isSet(quietWinAssertOpt);
405#else
406 out.quietWindowsAsserts = false;
407#endif
408
409#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
410 out.useSwRast = parser.isSet(swrastOpt);
411#else
412 out.useSwRast = false;
413#endif
414
415 out.statusCode = CommandLineParseResult::Status::Ok;
416 qCDebug(QGCCommandLineParserLog) << "Command line parsing completed successfully";
417
418 return out;
419}
420
421CommandLineParseResult parse(int argc, char* argv[])
422{
423#ifdef QGC_UNITTEST_BUILD
424 overrideCommandLine(argc, argv);
425#endif
426 const QCoreApplication app(argc, argv);
427 return parseCommandLine();
428}
429
430std::optional<int> handleParseResult(const CommandLineParseResult& result)
431{
432 switch (result.statusCode) {
433 case CommandLineParseResult::Status::Error: {
434 const QString msg = result.errorString.value_or(QStringLiteral("Unknown error"));
435 QCommandLineParser::showMessageAndExit(QCommandLineParser::MessageType::Error, msg, 1);
436 Q_UNREACHABLE();
437 }
438 case CommandLineParseResult::Status::HelpRequested:
439 case CommandLineParseResult::Status::VersionRequested:
440 return 0;
441 case CommandLineParseResult::Status::Ok:
442 return std::nullopt;
443 }
444 Q_UNREACHABLE();
445}
446
448{
449#ifdef QGC_UNITTEST_BUILD
450 if (args.listTests) {
451 return AppMode::ListTests;
452 }
453 if (args.runningUnitTests) {
454 return AppMode::Test;
455 }
456#endif
457 if (args.simpleBootTest) {
458 return AppMode::BootTest;
459 }
460 return AppMode::Gui;
461}
462
463#ifdef QGC_UNITTEST_BUILD
464void overrideCommandLine(int& argc, char**& argv)
465{
466 constexpr bool enabled = false; // Set to true to enable
467 if constexpr (!enabled) {
468 return;
469 }
470
471 static char arg0[256];
472 static char arg1[] = "--unittest:FTPManagerTest";
473 static char arg2[] = "--logging:Vehicle.FTPManager";
474 static char* newArgv[] = { arg0, arg1, arg2, nullptr };
475
476 qstrncpy(arg0, argv[0], sizeof(arg0));
477 argc = 3;
478 argv = newArgv;
479}
480#endif
481
482} // namespace QGCCommandLineParser
#define QGC_LOGGING_CATEGORY(name, categoryStr)
CommandLineParseResult parse(int argc, char *argv[])
Parse command-line arguments with automatic QCoreApplication management.
AppMode determineAppMode(const CommandLineParseResult &args)
Determine the application mode from parsed arguments.
AppMode
Application execution mode.
CommandLineParseResult parseCommandLine()
Parse command-line arguments (requires existing QCoreApplication)
static QStringList normalizeArgs(const QStringList &args)
Normalizes command-line arguments Converts colon-separated syntax (–option:value) to standard format ...
std::optional< int > handleParseResult(const CommandLineParseResult &result)
Handle early exit conditions (help, version, error)
Result of parsing command-line arguments.
std::unique_ptr< QCommandLineParser > parser
bool listTests
List available tests and exit.
enum QGCCommandLineParser::CommandLineParseResult::Status statusCode