[llvm] [llvm-cov] Migrate gcov to OptTable (PR #224955)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 21 02:04:37 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/224955
>From 5ad0307b7dbece4836ca2b44b558e782bb41dff1 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 20 Sep 2026 11:59:47 -0700
Subject: [PATCH] [llvm-cov] Migrate gcov to OptTable
llvm-cov gcov is the last user of cl::Grouping. Parse its options with
an OptTable that enables grouped short options, as the other
binutils-style tools do, so that cl::Grouping can be removed.
Spellings follow the other migrated tools: long options take `--` only,
so `-gcno` and `-gcda=` become `--gcno` and `--gcda=`. `--help` now
lists only gcov's options rather than every cl:: option linked into
llvm-cov, and an unknown option or a missing source file is reported
as `llvm-cov gcov: error: ...`.
LLM-aided
---
llvm/docs/CommandGuide/llvm-cov.md | 4 +-
llvm/test/tools/llvm-cov/gcov/basic.test | 14 +-
llvm/tools/llvm-cov/CMakeLists.txt | 7 +
llvm/tools/llvm-cov/GcovOpts.td | 51 ++++++
llvm/tools/llvm-cov/gcov.cpp | 168 ++++++++----------
.../gn/secondary/llvm/tools/llvm-cov/BUILD.gn | 9 +
.../llvm-project-overlay/llvm/BUILD.bazel | 11 ++
7 files changed, 157 insertions(+), 107 deletions(-)
create mode 100644 llvm/tools/llvm-cov/GcovOpts.td
diff --git a/llvm/docs/CommandGuide/llvm-cov.md b/llvm/docs/CommandGuide/llvm-cov.md
index 11508c00d2598..54ac3e05c8b36 100644
--- a/llvm/docs/CommandGuide/llvm-cov.md
+++ b/llvm/docs/CommandGuide/llvm-cov.md
@@ -107,7 +107,7 @@ an entire source file.
:::
:::{option} --help
-Display available options (--help-hidden for more).
+Display available options.
:::
:::{option} -l, --long-file-names
@@ -157,7 +157,7 @@ Include unconditional branches in the output for the --branch-probabilities
option.
:::
-:::{option} -version
+:::{option} --version
Display the version of llvm-cov.
:::
diff --git a/llvm/test/tools/llvm-cov/gcov/basic.test b/llvm/test/tools/llvm-cov/gcov/basic.test
index 7557739add8ba..9c849d4824a8b 100644
--- a/llvm/test/tools/llvm-cov/gcov/basic.test
+++ b/llvm/test/tools/llvm-cov/gcov/basic.test
@@ -57,11 +57,11 @@ RUN: llvm-cov gcov -l test_paths.cpp
RUN: ls test_paths.cpp##a.c.gcov
# Long file names and preserve paths.
-RUN: mkdir -p src && llvm-cov gcov -lp -gcno test_paths.gcno -gcda test_paths.gcda src/../test_paths.cpp
+RUN: mkdir -p src && llvm-cov gcov -lp --gcno test_paths.gcno --gcda test_paths.gcda src/../test_paths.cpp
RUN: ls src#^#test_paths.cpp##src#a.c.gcov
# Hash pathnames.
-RUN: llvm-cov gcov -x -gcno test_paths.gcno -gcda test_paths.gcda src/../test_paths.cpp
+RUN: llvm-cov gcov -x --gcno test_paths.gcno --gcda test_paths.gcda src/../test_paths.cpp
RUN: ls a.c##0c546a4dd99c1774b7b06e4fad16158c.gcov
# Function summaries. This changes stdout, but not the gcov files.
@@ -153,7 +153,7 @@ RUN: FileCheck --input-file=test.h.gcov %s --check-prefix=H-C
H-C: unconditional 0 taken 1
# Missing gcda file just gives 0 counts.
-RUN: llvm-cov gcov test.c -gcda=no_such_gcda_file | FileCheck %s --check-prefix=NO-GCDA
+RUN: llvm-cov gcov test.c --gcda=no_such_gcda_file | FileCheck %s --check-prefix=NO-GCDA
RUN: diff -ub test_no_gcda.cpp.gcov test.cpp.gcov
RUN: diff -ub test_no_gcda.h.gcov test.h.gcov
NO-GCDA: File 'test.cpp'
@@ -165,19 +165,19 @@ NO-GCDA-NEXT: Lines executed:0.00% of 1
NO-GCDA-NEXT: Creating 'test.h.gcov'
# Invalid gcno file.
-RUN: llvm-cov gcov test.c -gcno=test_read_fail.gcno
+RUN: llvm-cov gcov test.c --gcno=test_read_fail.gcno
# Not a .gcda file. Error but keep the .gcov output.
RUN: echo invalid > not.gcda
-RUN: llvm-cov gcov test.c -gcda=not.gcda 2> %t.err | FileCheck %s --check-prefix=NO-GCDA
+RUN: llvm-cov gcov test.c --gcda=not.gcda 2> %t.err | FileCheck %s --check-prefix=NO-GCDA
RUN: FileCheck %s --check-prefix=NOT-GCDA < %t.err
NOT-GCDA: not.gcda:not a gcov data file
# Bad file checksum on gcda.
-RUN: llvm-cov gcov test.c -gcda=test_file_checksum_fail.gcda 2> %t.err | FileCheck %s --check-prefix=NO-GCDA
+RUN: llvm-cov gcov test.c --gcda=test_file_checksum_fail.gcda 2> %t.err | FileCheck %s --check-prefix=NO-GCDA
# Bad function checksum on gcda
-RUN: llvm-cov gcov test.c -gcda=test_func_checksum_fail.gcda 2> %t.err | FileCheck %s --check-prefix=NO-GCDA
+RUN: llvm-cov gcov test.c --gcda=test_func_checksum_fail.gcda 2> %t.err | FileCheck %s --check-prefix=NO-GCDA
# Has arcs from exit blocks
RUN-DISABLED: llvm-cov gcov test_exit_block_arcs.c 2>&1 | FileCheck %s -check-prefix=EXIT_BLOCK_ARCS
diff --git a/llvm/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt
index 6602a2319cb57..41e826ac88a46 100644
--- a/llvm/tools/llvm-cov/CMakeLists.txt
+++ b/llvm/tools/llvm-cov/CMakeLists.txt
@@ -2,11 +2,16 @@ set(LLVM_LINK_COMPONENTS
Core
Support
Object
+ Option
Coverage
ProfileData
TargetParser
)
+set(LLVM_TARGET_DEFINITIONS GcovOpts.td)
+tablegen(LLVM GcovOpts.inc -gen-opt-parser-defs)
+add_public_tablegen_target(CovGcovOptsTableGen)
+
add_llvm_tool(llvm-cov
llvm-cov.cpp
gcov.cpp
@@ -20,6 +25,8 @@ add_llvm_tool(llvm-cov
SourceCoverageViewHTML.cpp
SourceCoverageViewText.cpp
TestingSupport.cpp
+ DEPENDS
+ CovGcovOptsTableGen
)
target_link_libraries(llvm-cov PRIVATE LLVMHTTP LLVMDebuginfod)
diff --git a/llvm/tools/llvm-cov/GcovOpts.td b/llvm/tools/llvm-cov/GcovOpts.td
new file mode 100644
index 0000000000000..fbf1123bb424d
--- /dev/null
+++ b/llvm/tools/llvm-cov/GcovOpts.td
@@ -0,0 +1,51 @@
+include "llvm/Option/OptParser.td"
+
+class F<string letter, string help> : Flag<["-"], letter>, HelpText<help>;
+class FF<string name, string help> : Flag<["--"], name>, HelpText<help>;
+
+multiclass Eq<string name, string help> {
+ def NAME #_EQ : Joined<["--"], name #"=">, HelpText<help>;
+ def : Separate<["--"], name>, Alias<!cast<Joined>(NAME #_EQ)>;
+}
+
+def all_blocks : FF<"all-blocks", "Display all basic blocks">;
+def branch_counts : FF<"branch-counts", "Display branch counts instead of percentages (requires -b)">;
+def branch_probabilities : FF<"branch-probabilities", "Display branch probabilities">;
+def demangled_names : FF<"demangled-names", "Demangle function names">;
+def dump_gcov : FF<"dump", "Dump the gcov file to stderr">;
+def function_summaries : FF<"function-summaries", "Show coverage for each function">;
+defm gcda : Eq<"gcda", "Override inferred gcda file">, MetaVarName<"<file>">;
+defm gcno : Eq<"gcno", "Override inferred gcno file">, MetaVarName<"<file>">;
+def hash_filenames : FF<"hash-filenames", "Hash long pathnames">;
+def help : FF<"help", "Display this help">;
+// Supported by gcov 4.9~8. gcov 9 (GCC r265587) removed --intermediate-format
+// and -i was changed to mean --json-format. We consider this format still
+// useful and support -i.
+def intermediate_format : FF<"intermediate-format", "Output .gcov in intermediate text format">;
+def long_file_names : FF<"long-file-names", "Prefix filenames with the main file">;
+def no_output : FF<"no-output", "Do not output any .gcov files">;
+defm object_directory : Eq<"object-directory", "Find objects in DIR or based on FILE's path">, MetaVarName<"<DIR>">;
+def : Joined<["--"], "object-file=">, Alias<object_directory_EQ>, HelpText<"Alias for --object-directory">, MetaVarName<"<FILE>">;
+def : Separate<["--"], "object-file">, Alias<object_directory_EQ>;
+def preserve_paths : FF<"preserve-paths", "Preserve path components">;
+def relative_only : FF<"relative-only", "Only dump files with relative paths or absolute paths with the prefix specified by -s">;
+defm source_prefix : Eq<"source-prefix", "Source prefix to elide">, MetaVarName<"<prefix>">;
+def stdout : FF<"stdout", "Print to stdout">;
+def unconditional_branches : FF<"unconditional-branches", "Display unconditional branch info (requires -b)">;
+def version : FF<"version", "Display the version">;
+
+def : F<"a", "Alias for --all-blocks">, Alias<all_blocks>;
+def : F<"b", "Alias for --branch-probabilities">, Alias<branch_probabilities>;
+def : F<"c", "Alias for --branch-counts">, Alias<branch_counts>;
+def : F<"f", "Alias for --function-summaries">, Alias<function_summaries>;
+def : F<"i", "Alias for --intermediate-format">, Alias<intermediate_format>;
+def : F<"l", "Alias for --long-file-names">, Alias<long_file_names>;
+def : F<"m", "Alias for --demangled-names">, Alias<demangled_names>;
+def : F<"n", "Alias for --no-output">, Alias<no_output>;
+def : JoinedOrSeparate<["-"], "o">, Alias<object_directory_EQ>, HelpText<"Alias for --object-directory">, MetaVarName<"<DIR|FILE>">;
+def : F<"p", "Alias for --preserve-paths">, Alias<preserve_paths>;
+def : F<"r", "Alias for --relative-only">, Alias<relative_only>;
+def : JoinedOrSeparate<["-"], "s">, Alias<source_prefix_EQ>, HelpText<"Alias for --source-prefix">, MetaVarName<"<prefix>">;
+def : F<"t", "Alias for --stdout">, Alias<stdout>;
+def : F<"u", "Alias for --unconditional-branches">, Alias<unconditional_branches>;
+def : F<"x", "Alias for --hash-filenames">, Alias<hash_filenames>;
diff --git a/llvm/tools/llvm-cov/gcov.cpp b/llvm/tools/llvm-cov/gcov.cpp
index 00ea12415b220..f500d2a3e8d5e 100644
--- a/llvm/tools/llvm-cov/gcov.cpp
+++ b/llvm/tools/llvm-cov/gcov.cpp
@@ -12,17 +12,42 @@
#include "llvm/ProfileData/GCOV.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/StringSaver.h"
+#include "llvm/Support/WithColor.h"
#include <system_error>
using namespace llvm;
+namespace {
+enum ID {
+ OPT_INVALID = 0, // This is not an option ID.
+#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
+#include "GcovOpts.inc"
+#undef OPTION
+};
+
+using namespace llvm::opt;
+#define OPTTABLE_CODE
+#include "GcovOpts.inc"
+
+class GcovOptTable : public opt::OptTable {
+public:
+ GcovOptTable() : OptTable(optionTables()) {
+ setGroupedShortOptions(true);
+ setDashDashParsing(true);
+ }
+};
+} // namespace
+
static void reportCoverage(StringRef SourceFile, StringRef ObjectDir,
- const std::string &InputGCNO,
- const std::string &InputGCDA, bool DumpGCOV,
- const GCOV::Options &Options) {
+ StringRef InputGCNO, StringRef InputGCDA,
+ bool DumpGCOV, const GCOV::Options &Options) {
SmallString<128> CoverageFileStem(ObjectDir);
if (CoverageFileStem.empty()) {
// If no directory was specified with -o, look next to the source file.
@@ -35,10 +60,10 @@ static void reportCoverage(StringRef SourceFile, StringRef ObjectDir,
// A file was given. Ignore the source file and look next to this file.
sys::path::replace_extension(CoverageFileStem, "");
- std::string GCNO =
- InputGCNO.empty() ? std::string(CoverageFileStem) + ".gcno" : InputGCNO;
- std::string GCDA =
- InputGCDA.empty() ? std::string(CoverageFileStem) + ".gcda" : InputGCDA;
+ std::string GCNO = InputGCNO.empty() ? std::string(CoverageFileStem) + ".gcno"
+ : InputGCNO.str();
+ std::string GCDA = InputGCDA.empty() ? std::string(CoverageFileStem) + ".gcda"
+ : InputGCDA.str();
GCOVFile GF;
// Open .gcda and .gcda without requiring a NUL terminator. The concurrent
@@ -81,97 +106,44 @@ static void reportCoverage(StringRef SourceFile, StringRef ObjectDir,
}
int gcovMain(int argc, const char *argv[]) {
- cl::list<std::string> SourceFiles(cl::Positional, cl::OneOrMore,
- cl::desc("SOURCEFILE"));
-
- cl::opt<bool> AllBlocks("a", cl::Grouping, cl::init(false),
- cl::desc("Display all basic blocks"));
- cl::alias AllBlocksA("all-blocks", cl::aliasopt(AllBlocks));
-
- cl::opt<bool> BranchProb("b", cl::Grouping, cl::init(false),
- cl::desc("Display branch probabilities"));
- cl::alias BranchProbA("branch-probabilities", cl::aliasopt(BranchProb));
-
- cl::opt<bool> BranchCount("c", cl::Grouping, cl::init(false),
- cl::desc("Display branch counts instead "
- "of percentages (requires -b)"));
- cl::alias BranchCountA("branch-counts", cl::aliasopt(BranchCount));
-
- cl::opt<bool> LongNames("l", cl::Grouping, cl::init(false),
- cl::desc("Prefix filenames with the main file"));
- cl::alias LongNamesA("long-file-names", cl::aliasopt(LongNames));
-
- cl::opt<bool> FuncSummary("f", cl::Grouping, cl::init(false),
- cl::desc("Show coverage for each function"));
- cl::alias FuncSummaryA("function-summaries", cl::aliasopt(FuncSummary));
-
- // Supported by gcov 4.9~8. gcov 9 (GCC r265587) removed --intermediate-format
- // and -i was changed to mean --json-format. We consider this format still
- // useful and support -i.
- cl::opt<bool> Intermediate(
- "intermediate-format", cl::init(false),
- cl::desc("Output .gcov in intermediate text format"));
- cl::alias IntermediateA("i", cl::desc("Alias for --intermediate-format"),
- cl::Grouping, cl::NotHidden,
- cl::aliasopt(Intermediate));
-
- cl::opt<bool> Demangle("demangled-names", cl::init(false),
- cl::desc("Demangle function names"));
- cl::alias DemangleA("m", cl::desc("Alias for --demangled-names"),
- cl::Grouping, cl::NotHidden, cl::aliasopt(Demangle));
-
- cl::opt<bool> NoOutput("n", cl::Grouping, cl::init(false),
- cl::desc("Do not output any .gcov files"));
- cl::alias NoOutputA("no-output", cl::aliasopt(NoOutput));
-
- cl::opt<std::string> ObjectDir(
- "o", cl::value_desc("DIR|FILE"), cl::init(""),
- cl::desc("Find objects in DIR or based on FILE's path"));
- cl::alias ObjectDirA("object-directory", cl::aliasopt(ObjectDir));
- cl::alias ObjectDirB("object-file", cl::aliasopt(ObjectDir));
-
- cl::opt<bool> PreservePaths("p", cl::Grouping, cl::init(false),
- cl::desc("Preserve path components"));
- cl::alias PreservePathsA("preserve-paths", cl::aliasopt(PreservePaths));
-
- cl::opt<bool> RelativeOnly(
- "r", cl::Grouping,
- cl::desc("Only dump files with relative paths or absolute paths with the "
- "prefix specified by -s"));
- cl::alias RelativeOnlyA("relative-only", cl::aliasopt(RelativeOnly));
- cl::opt<std::string> SourcePrefix("s", cl::desc("Source prefix to elide"));
- cl::alias SourcePrefixA("source-prefix", cl::aliasopt(SourcePrefix));
-
- cl::opt<bool> UseStdout("t", cl::Grouping, cl::init(false),
- cl::desc("Print to stdout"));
- cl::alias UseStdoutA("stdout", cl::aliasopt(UseStdout));
-
- cl::opt<bool> UncondBranch("u", cl::Grouping, cl::init(false),
- cl::desc("Display unconditional branch info "
- "(requires -b)"));
- cl::alias UncondBranchA("unconditional-branches", cl::aliasopt(UncondBranch));
-
- cl::opt<bool> HashFilenames("x", cl::Grouping, cl::init(false),
- cl::desc("Hash long pathnames"));
- cl::alias HashFilenamesA("hash-filenames", cl::aliasopt(HashFilenames));
-
-
- cl::OptionCategory DebugCat("Internal and debugging options");
- cl::opt<bool> DumpGCOV("dump", cl::init(false), cl::cat(DebugCat),
- cl::desc("Dump the gcov file to stderr"));
- cl::opt<std::string> InputGCNO("gcno", cl::cat(DebugCat), cl::init(""),
- cl::desc("Override inferred gcno file"));
- cl::opt<std::string> InputGCDA("gcda", cl::cat(DebugCat), cl::init(""),
- cl::desc("Override inferred gcda file"));
-
- cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
-
- GCOV::Options Options(AllBlocks, BranchProb, BranchCount, FuncSummary,
- PreservePaths, UncondBranch, Intermediate, LongNames,
- Demangle, NoOutput, RelativeOnly, UseStdout,
- HashFilenames, SourcePrefix);
-
- for (const auto &SourceFile : SourceFiles)
+ StringRef ToolName = sys::path::filename(argv[0]);
+ auto Error = [&](const Twine &Msg) {
+ WithColor::error(errs(), ToolName) << Msg << '\n';
+ exit(1);
+ };
+ BumpPtrAllocator A;
+ StringSaver Saver(A);
+ GcovOptTable Tbl;
+ opt::InputArgList Args =
+ Tbl.parseArgs(argc, const_cast<char **>(argv), OPT_UNKNOWN, Saver, Error);
+ if (Args.hasArg(OPT_help)) {
+ Tbl.printHelp(outs(), (ToolName + " [options] SOURCEFILE").str().c_str(),
+ "LLVM code coverage tool");
+ return 0;
+ }
+ if (Args.hasArg(OPT_version)) {
+ cl::PrintVersionMessage();
+ return 0;
+ }
+ std::vector<std::string> SourceFiles = Args.getAllArgValues(OPT_INPUT);
+ if (SourceFiles.empty())
+ Error("no source file specified");
+
+ GCOV::Options Options(
+ Args.hasArg(OPT_all_blocks), Args.hasArg(OPT_branch_probabilities),
+ Args.hasArg(OPT_branch_counts), Args.hasArg(OPT_function_summaries),
+ Args.hasArg(OPT_preserve_paths), Args.hasArg(OPT_unconditional_branches),
+ Args.hasArg(OPT_intermediate_format), Args.hasArg(OPT_long_file_names),
+ Args.hasArg(OPT_demangled_names), Args.hasArg(OPT_no_output),
+ Args.hasArg(OPT_relative_only), Args.hasArg(OPT_stdout),
+ Args.hasArg(OPT_hash_filenames),
+ Args.getLastArgValue(OPT_source_prefix_EQ).str());
+
+ StringRef ObjectDir = Args.getLastArgValue(OPT_object_directory_EQ);
+ StringRef InputGCNO = Args.getLastArgValue(OPT_gcno_EQ);
+ StringRef InputGCDA = Args.getLastArgValue(OPT_gcda_EQ);
+ bool DumpGCOV = Args.hasArg(OPT_dump_gcov);
+ for (const std::string &SourceFile : SourceFiles)
reportCoverage(SourceFile, ObjectDir, InputGCNO, InputGCDA, DumpGCOV,
Options);
return 0;
diff --git a/llvm/utils/gn/secondary/llvm/tools/llvm-cov/BUILD.gn b/llvm/utils/gn/secondary/llvm/tools/llvm-cov/BUILD.gn
index bf2493c1820a9..3fa2f04df93d4 100644
--- a/llvm/utils/gn/secondary/llvm/tools/llvm-cov/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/tools/llvm-cov/BUILD.gn
@@ -1,10 +1,19 @@
+import("//llvm/utils/TableGen/tablegen.gni")
+
+tablegen("GcovOpts") {
+ visibility = [ ":llvm-cov" ]
+ args = [ "-gen-opt-parser-defs" ]
+}
+
executable("llvm-cov") {
deps = [
+ ":GcovOpts",
"//llvm/include/llvm/Config:llvm-config",
"//llvm/lib/Debuginfod",
"//llvm/lib/HTTP",
"//llvm/lib/IR",
"//llvm/lib/Object",
+ "//llvm/lib/Option",
"//llvm/lib/ProfileData",
"//llvm/lib/ProfileData/Coverage",
"//llvm/lib/Support",
diff --git a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
index b7765075c72fb..fe23e55ade755 100644
--- a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
@@ -5244,6 +5244,15 @@ cc_binary(
],
)
+gentbl_cc_library(
+ name = "CovGcovOptsTableGen",
+ strip_include_prefix = "tools/llvm-cov",
+ tbl_outs = {"tools/llvm-cov/GcovOpts.inc": ["-gen-opt-parser-defs"]},
+ tblgen = ":llvm-tblgen",
+ td_file = "tools/llvm-cov/GcovOpts.td",
+ deps = [":OptParserTdFiles"],
+)
+
cc_binary(
name = "llvm-cov",
srcs = glob([
@@ -5253,11 +5262,13 @@ cc_binary(
copts = llvm_copts,
stamp = 0,
deps = [
+ ":CovGcovOptsTableGen",
":Coverage",
":Debuginfod",
":HTTP",
":Instrumentation",
":Object",
+ ":Option",
":ProfileData",
":Support",
":TargetParser",
More information about the llvm-commits
mailing list