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(
215 QString(kOptSwrast),
216 QCoreApplication::translate("main", "Force software OpenGL."));
217 (void) parser.addOption(swrastOpt);
218#endif
219
220#ifdef Q_OS_WIN
221 // --- Windows-only options ---
222 const QCommandLineOption quietWinAssertOpt(
223 QString(kOptNoWinAssertUI),
224 QCoreApplication::translate("main", "Disable Windows assert dialog boxes."));
225 (void) parser.addOption(quietWinAssertOpt);
226#endif
227
228 // --- Parse arguments ---
229 const QStringList normalizedArgs = normalizeArgs(QCoreApplication::arguments());
230 parser.process(normalizedArgs);
231
232 // --- Validate unknown options ---
233 out.unknownOptions = parser.unknownOptionNames();
234
235 // Check for platform-specific option errors on wrong platform
236 if (!out.unknownOptions.isEmpty()) {
237 // Check for test options in non-test build
238 if (out.unknownOptions.contains(QLatin1String("unittest")) ||
239 out.unknownOptions.contains(QLatin1String("unittest-stress")) ||
240 out.unknownOptions.contains(QLatin1String("unittest-output")) ||
241 out.unknownOptions.contains(QLatin1String("list-tests"))) {
242#ifndef QGC_UNITTEST_BUILD
243 out.statusCode = CommandLineParseResult::Status::Error;
244 out.errorString = QCoreApplication::translate("main",
245 "--unittest/--unittest-stress/--unittest-output/--list-tests options are only available in unittest builds.");
246 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
247 return out;
248#endif
249 }
250
251#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
252 // Mobile platforms don't support desktop options
253 if (out.unknownOptions.contains(QLatin1String("fake-mobile")) ||
254 out.unknownOptions.contains(QLatin1String("allow-multiple"))) {
255 out.statusCode = CommandLineParseResult::Status::Error;
256 out.errorString = QCoreApplication::translate("main",
257 "--fake-mobile/--allow-multiple are not supported on mobile platforms.");
258 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
259 return out;
260 }
261#endif
262
263#ifndef Q_OS_WIN
264 // Non-Windows platforms don't support Windows-specific options
265 if (out.unknownOptions.contains(QLatin1String("no-windows-assert-ui"))) {
266 out.statusCode = CommandLineParseResult::Status::Error;
267 out.errorString = QCoreApplication::translate("main",
268 "--no-windows-assert-ui is only supported on Windows.");
269 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
270 return out;
271 }
272#endif
273
274#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS)
275 // Non-Windows/macOS platforms don't support swrast
276 if (out.unknownOptions.contains(QLatin1String("swrast"))) {
277 out.statusCode = CommandLineParseResult::Status::Error;
278 out.errorString = QCoreApplication::translate("main",
279 "--swrast is only supported on Windows and macOS.");
280 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
281 return out;
282 }
283#endif
284
285 // Generic unknown options error
286 out.statusCode = CommandLineParseResult::Status::Error;
287 out.errorString = QCoreApplication::translate("main", "Unknown options: %1")
288 .arg(out.unknownOptions.join(QLatin1String(", ")));
289 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
290 return out;
291 }
292
293 // --- Validate positional arguments ---
294 out.positional = parser.positionalArguments();
295 if (!out.positional.isEmpty()) {
296 out.statusCode = CommandLineParseResult::Status::Error;
297 out.errorString = QCoreApplication::translate("main", "Unexpected positional arguments: %1")
298 .arg(out.positional.join(QLatin1String(", ")));
299 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
300 return out;
301 }
302
303 // --- Handle help/version requests ---
304 if (parser.isSet(helpOption)) {
305 out.statusCode = CommandLineParseResult::Status::HelpRequested;
306 out.helpText = parser.helpText();
307 return out;
308 }
309
310 if (parser.isSet(versionOption)) {
311 out.statusCode = CommandLineParseResult::Status::VersionRequested;
312 out.versionText = QCoreApplication::applicationVersion();
313 return out;
314 }
315
316 // --- Parse core options ---
317 if (parser.isSet(systemIdOpt)) {
318 const QString systemIdStr = parser.value(systemIdOpt);
319 bool ok = false;
320 const uint systemId = systemIdStr.toUInt(&ok);
321 if (!ok || (systemId < 1) || (systemId > 255)) {
322 out.statusCode = CommandLineParseResult::Status::Error;
323 out.errorString = QCoreApplication::translate("main", "Invalid System ID (must be 1-255): %1")
324 .arg(systemIdStr);
325 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
326 return out;
327 }
328 out.systemId = static_cast<quint8>(systemId);
329 qCDebug(QGCCommandLineParserLog) << "System ID:" << systemId;
330 }
331
332 out.clearSettingsOptions = parser.isSet(clearSettingsOpt);
333 out.clearCache = parser.isSet(clearCacheOpt);
334 if (parser.isSet(loggingOpt)) {
335 out.loggingOptions = parser.value(loggingOpt);
336 qCDebug(QGCCommandLineParserLog) << "Logging options:" << out.loggingOptions.value();
337 }
338 out.logOutput = parser.isSet(logOutputOpt);
339 out.simpleBootTest = parser.isSet(simpleBootOpt);
340
341#ifdef QGC_UNITTEST_BUILD
342 // --- Parse test options ---
343 if (parser.isSet(unittestOpt)) {
344 out.runningUnitTests = true;
345 const QStringList vals = parser.values(unittestOpt);
346 // Filter out empty values (from bare --unittest)
347 for (const QString& val : vals) {
348 if (!val.isEmpty()) {
349 out.unitTests.append(val);
350 }
351 }
352 qCDebug(QGCCommandLineParserLog) << "Unit tests:" << (out.unitTests.isEmpty() ? QStringLiteral("all") : out.unitTests.join(QLatin1String(", ")));
353 }
354
355 if (parser.isSet(unittestStressOpt)) {
356 out.runningUnitTests = true;
357 out.stressUnitTests = true;
358 const QString stress = parser.value(unittestStressOpt);
359 if (stress.isEmpty()) {
360 out.stressUnitTestsCount = 20;
361 } else {
362 bool ok = false;
363 const uint count = stress.toUInt(&ok);
364 if (!ok || (count == 0)) {
365 out.statusCode = CommandLineParseResult::Status::Error;
366 out.errorString = QCoreApplication::translate("main", "Invalid stress test count (must be > 0): %1")
367 .arg(stress);
368 qCWarning(QGCCommandLineParserLog) << out.errorString.value();
369 return out;
370 }
371 out.stressUnitTestsCount = count;
372 }
373 qCDebug(QGCCommandLineParserLog) << "Stress test iterations:" << out.stressUnitTestsCount;
374 }
375
376 if (parser.isSet(unittestOutputOpt)) {
377 out.unitTestOutput = parser.value(unittestOutputOpt);
378 qCDebug(QGCCommandLineParserLog) << "Test output file:" << out.unitTestOutput.value();
379 }
380
381 if (parser.isSet(unittestLabelOpt)) {
382 out.labelFilter = parser.value(unittestLabelOpt);
383 qCDebug(QGCCommandLineParserLog) << "Label filter:" << out.labelFilter.value();
384 }
385
386 out.listTests = parser.isSet(listTestsOpt);
387 if (out.listTests) {
388 qCDebug(QGCCommandLineParserLog) << "List tests requested";
389 }
390
391 out.onscreen = parser.isSet(onscreenOpt);
392#endif
393
394 // --- Parse desktop options ---
395#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
396 out.fakeMobile = parser.isSet(fakeMobileOpt);
397 out.allowMultiple = parser.isSet(allowMultipleOpt);
398#else
399 out.fakeMobile = false;
400 out.allowMultiple = false;
401#endif
402
403 // --- Parse graphics options ---
404#ifdef Q_OS_WIN
405 out.quietWindowsAsserts = parser.isSet(quietWinAssertOpt);
406#else
407 out.quietWindowsAsserts = false;
408#endif
409
410#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
411 out.useSwRast = parser.isSet(swrastOpt);
412#else
413 out.useSwRast = false;
414#endif
415
416 out.statusCode = CommandLineParseResult::Status::Ok;
417 qCDebug(QGCCommandLineParserLog) << "Command line parsing completed successfully";
418
419 return out;
420}
421
422CommandLineParseResult parse(int argc, char* argv[])
423{
424#ifdef QGC_UNITTEST_BUILD
425 overrideCommandLine(argc, argv);
426#endif
427 const QCoreApplication app(argc, argv);
428 return parseCommandLine();
429}
430
431std::optional<int> handleParseResult(const CommandLineParseResult& result)
432{
433 switch (result.statusCode) {
434 case CommandLineParseResult::Status::Error: {
435 const QString msg = result.errorString.value_or(QStringLiteral("Unknown error"));
436 QCommandLineParser::showMessageAndExit(QCommandLineParser::MessageType::Error, msg, 1);
437 Q_UNREACHABLE();
438 }
439 case CommandLineParseResult::Status::HelpRequested:
440 case CommandLineParseResult::Status::VersionRequested:
441 return 0;
442 case CommandLineParseResult::Status::Ok:
443 return std::nullopt;
444 }
445 Q_UNREACHABLE();
446}
447
449{
450#ifdef QGC_UNITTEST_BUILD
451 if (args.listTests) {
452 return AppMode::ListTests;
453 }
454 if (args.runningUnitTests) {
455 return AppMode::Test;
456 }
457#endif
458 if (args.simpleBootTest) {
459 return AppMode::BootTest;
460 }
461 return AppMode::Gui;
462}
463
464#ifdef QGC_UNITTEST_BUILD
465void overrideCommandLine(int& argc, char**& argv)
466{
467 constexpr bool enabled = false; // Set to true to enable
468 if constexpr (!enabled) {
469 return;
470 }
471
472 static char arg0[256];
473 static char arg1[] = "--unittest:FTPManagerTest";
474 static char arg2[] = "--logging:Vehicle.FTPManager";
475 static char* newArgv[] = { arg0, arg1, arg2, nullptr };
476
477 qstrncpy(arg0, argv[0], sizeof(arg0));
478 argc = 3;
479 argv = newArgv;
480}
481#endif
482
483} // 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