From c2ef63fd9787f06cf05878af26dd302be67576c2 Mon Sep 17 00:00:00 2001 From: Dave Evans Date: Tue, 29 Sep 2026 23:21:14 +0000 Subject: [PATCH] vulkaninfo: Error when multiple output formats are given --summary, --text, --html, --json and --vkconfig_output are mutually exclusive, but previously the last one silently won. parse_arguments now records each requested format in a set and fails with an error if more than one distinct format was given. Repeating the same format is still accepted. Adds CTest cases that run vulkaninfo with conflicting format pairs and check for the error message, and documents the restriction in the help text and vulkaninfo.md. --- tests/CMakeLists.txt | 25 +++++++++++++++++++++++++ vulkaninfo/vulkaninfo.cpp | 23 ++++++++++++++++++----- vulkaninfo/vulkaninfo.md | 2 ++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a1b7250fa..4a2e33a63 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -69,3 +69,28 @@ endif() include(GoogleTest) gtest_discover_tests(vulkan_tools_tests DISCOVERY_TIMEOUT 100) + +# vulkaninfo command line parsing: output format flags are mutually exclusive. +# Argument parsing happens before any Vulkan call, so these tests do not need a driver. +if (TARGET vulkaninfo) + set(VULKANINFO_CONFLICTING_FORMATS + "--html|--json" + "--json|--html" + "--text|--html" + "--summary|--text" + "--summary|-j" + "--json=0|--summary" + "--vkconfig_output|--html" + ) + # Pairs use '|' as the separator since CMake flattens nested ';' lists + foreach(FORMAT_PAIR_STRING IN LISTS VULKANINFO_CONFLICTING_FORMATS) + string(REPLACE "|" ";" FORMAT_PAIR "${FORMAT_PAIR_STRING}") + string(REPLACE "|" "_" TEST_SUFFIX "${FORMAT_PAIR_STRING}") + string(REGEX REPLACE "[-=]" "" TEST_SUFFIX "${TEST_SUFFIX}") + set(TEST_NAME vulkaninfo_rejects_conflicting_formats_${TEST_SUFFIX}) + add_test(NAME ${TEST_NAME} COMMAND $ ${FORMAT_PAIR}) + set_tests_properties(${TEST_NAME} PROPERTIES + PASS_REGULAR_EXPRESSION "Only one output format may be specified" + ) + endforeach() +endif() diff --git a/vulkaninfo/vulkaninfo.cpp b/vulkaninfo/vulkaninfo.cpp index df7ce022b..3d9fc5423 100644 --- a/vulkaninfo/vulkaninfo.cpp +++ b/vulkaninfo/vulkaninfo.cpp @@ -1249,6 +1249,8 @@ enum class OutputCategory { text, html, profile_json, vkconfig_output, summary } const char *help_message_body = "OPTIONS:\n" "[-h, --help] Print this help.\n" + "Note: --summary, --text, --html, --json, and --vkconfig_output select the output\n" + " format and are mutually exclusive; specify at most one.\n" "[--summary] Show a summary of the instance and GPU's on a system.\n" "[-o , --output ]\n" " Print output to a new file whose name is specified by filename.\n" @@ -1315,12 +1317,18 @@ struct ParsedResults { util::vulkaninfo_optional parse_arguments(int argc, char **argv, std::string executable_name) { ParsedResults results{}; results.default_filename = APP_SHORT_NAME ".txt"; + // Output formats are mutually exclusive, so record every format requested and reject more than one distinct format. + std::set requested_categories; + auto set_output_category = [&](OutputCategory category) { + results.output_category = category; + requested_categories.insert(category); + }; for (int i = 1; i < argc; ++i) { // A internal-use-only format for communication with the Vulkan Configurator tool // Usage "--vkconfig_output " // -o can be used to specify the filename instead if (0 == strcmp("--vkconfig_output", argv[i])) { - results.output_category = OutputCategory::vkconfig_output; + set_output_category(OutputCategory::vkconfig_output); results.print_to_file = true; results.default_filename = APP_SHORT_NAME ".json"; if (argc > (i + 1) && argv[i + 1][0] != '-') { @@ -1332,6 +1340,7 @@ util::vulkaninfo_optional parse_arguments(int argc, char **argv, ++i; } } else if (strncmp("--json", argv[i], 6) == 0 || strncmp(argv[i], "-j", 2) == 0) { + set_output_category(OutputCategory::profile_json); if (strlen(argv[i]) > 7 && strncmp("--json=", argv[i], 7) == 0) { results.selected_gpu = static_cast(strtol(argv[i] + 7, nullptr, 10)); results.has_selected_gpu = true; @@ -1340,16 +1349,15 @@ util::vulkaninfo_optional parse_arguments(int argc, char **argv, results.selected_gpu = static_cast(strtol(argv[i] + 3, nullptr, 10)); results.has_selected_gpu = true; } - results.output_category = OutputCategory::profile_json; results.default_filename = APP_SHORT_NAME ".json"; results.print_to_file = true; } else if (strcmp(argv[i], "--summary") == 0) { - results.output_category = OutputCategory::summary; + set_output_category(OutputCategory::summary); } else if (strcmp(argv[i], "--text") == 0) { - results.output_category = OutputCategory::text; + set_output_category(OutputCategory::text); results.default_filename = APP_SHORT_NAME ".txt"; } else if (strcmp(argv[i], "--html") == 0) { - results.output_category = OutputCategory::html; + set_output_category(OutputCategory::html); results.print_to_file = true; results.default_filename = APP_SHORT_NAME ".html"; } else if (strcmp(argv[i], "--show-all") == 0) { @@ -1382,6 +1390,11 @@ util::vulkaninfo_optional parse_arguments(int argc, char **argv, return {}; } } + if (requested_categories.size() > 1) { + std::cerr << "Error: --summary, --text, --html, --json, and --vkconfig_output cannot be combined. " + "Only one output format may be specified.\n"; + return {}; + } return results; } diff --git a/vulkaninfo/vulkaninfo.md b/vulkaninfo/vulkaninfo.md index 17b4a8b69..686d37dce 100644 --- a/vulkaninfo/vulkaninfo.md +++ b/vulkaninfo/vulkaninfo.md @@ -61,6 +61,8 @@ USAGE: OPTIONS: [-h, --help] Print this help. +Note: --summary, --text, --html, and --json select the output format and are + mutually exclusive; specify at most one. [--summary] Show a summary of the instance and GPU's on a system. [-o , --output ] Print output to a new file whose name is specified by filename.