Inventory C++ API Symbols with find-all-symbols-20
You need a list of every class and function a C++ library exposes, and grepping headers will not get you there. find-all-symbols-20 parses your sources with their real compiler flags and writes YAML records for the symbols it finds.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Check the installed tool
- 2. Generate or locate a compilation database
- 3. Analyse one source file into a new directory
- 4. Pass several translation units
- 5. Use relative paths only when they resolve in the database
- 6. Add a compiler argument only when the database needs it
- 7. Understand empty output and failed runs
The workflow uses a CMake-style compile_commands.json, writes results to a separate directory, and checks the records instead of assuming a clean exit means useful output. Allow about fifteen minutes.
- You need: the
clang-tools-20package, a C++ source tree and a compile command database. - Installed version: LLVM 20.1.8, package version
1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139. - Privileges: the examples only read source and write a new results directory, so they normally need no elevated privileges.
1. Check the installed tool
Confirm the binary and version before relying on its output:
$ command -v find-all-symbols-20
/usr/bin/find-all-symbols-20
$ find-all-symbols-20 --version
Ubuntu LLVM version 20.1.8
Optimized build.
The syntax is find-all-symbols-20 [options] <source0> [... <sourceN>]. It is a Clang tooling program, so it needs the same compiler arguments the source normally builds with. Those come from the compilation database, not from the source file name.
Checkpoint
The version output should identify LLVM 20. If the command is missing, install the package through your normal distribution process and repeat the check. Do not run the analysis as root to work around a missing package.
2. Generate or locate a compilation database
For a CMake project, configure a build directory with export enabled:
$ cmake -S /path/to/project -B /path/to/project/build \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
$ test -r /path/to/project/build/compile_commands.json && echo ready
ready
This changes the build directory, not the source tree's files. If the project already has a configured build, look for the database first:
$ find /path/to/project -name compile_commands.json -type f -print
/path/to/project/build/compile_commands.json
The database holds the working directory, source path and compiler command for each translation unit. It matters when code depends on include directories, language standards, generated headers or preprocessor definitions. Without those flags you can get no records or misleading diagnostics.
Tip
Without -p, the installed manual says the program searches parent directories of the first input for compile_commands.json. Make the build path explicit in scripts so a change of working directory cannot silently select a different database.
3. Analyse one source file into a new directory
Choose a source path that appears in the database and write results somewhere dedicated:
$ mkdir -p /tmp/find-all-symbols-results
$ find-all-symbols-20 \
-p /path/to/project/build \
--output-dir=/tmp/find-all-symbols-results \
/path/to/project/src/widget.cpp
/tmp/find-all-symbols-results/widget.cpp-5ac549.yaml
The output filename is the input basename plus a generated suffix, so do not script against the exact suffix. Treat the output directory as disposable analysis data, not a replacement for source control.
Checkpoint
List the directory and inspect the first record.
$ find /tmp/find-all-symbols-results -maxdepth 1 -type f -print
/tmp/find-all-symbols-results/widget.cpp-5ac549.yaml
$ sed -n '1,80p' /tmp/find-all-symbols-results/widget.cpp-5ac549.yaml
---
Name: Widget
Contexts:
- ContextType: Namespace
ContextName: demo
FilePath: '/path/to/project/include/api.h'
Type: Class
Seen: 1
Used: 1
...
A record gives the symbol name, its namespace context, the declaration file, its kind and the counts seen during the run. Here the input is widget.cpp but the declaration is reported in the included public header. That is usually exactly what you want when inventorying an API.
4. Pass several translation units
You can give more than one source path in a single invocation:
$ rm -f /tmp/find-all-symbols-results/*
$ find-all-symbols-20 \
-p /path/to/project/build \
--output-dir=/tmp/find-all-symbols-results \
/path/to/project/src/widget.cpp \
/path/to/project/src/gadget.cpp
$ find /tmp/find-all-symbols-results -maxdepth 1 -type f -print
/tmp/find-all-symbols-results/widget.cpp-5ac549.yaml
/tmp/find-all-symbols-results/gadget.cpp-d435a8.yaml
Warning
The rm here targets only the disposable results directory. Check the path before running it, because a slip in a deletion command can remove useful files. If the directory holds anything you need, pick a new empty directory instead.
Recovery
Rerun the analysis, provided the source and database still exist.
For a repeatable job, create a fresh run directory under a known temporary or build location, record the command and database path, then consume all the generated YAML files. One output file does not represent the whole project.
5. Use relative paths only when they resolve in the database
The manual permits absolute and relative source paths, but the matching rules are easy to trip over:
- Absolute path. Must point into the CMake source tree.
- Relative path. Must be below the current working directory and must match a suffix of a path in the database.
From the project root, this is usually clearer:
$ cd /path/to/project
$ find-all-symbols-20 \
-p build \
--output-dir=/tmp/find-all-symbols-results \
src/widget.cpp
Recovery
If the command cannot find a compile command, inspect the database and compare the recorded file paths with the path you supplied. Then use an absolute path into the source tree, or change to the directory that makes the relative path a suffix. Do not fix a path mismatch by adding random compiler flags with --extra-arg.
6. Add a compiler argument only when the database needs it
--extra-arg=<string> appends one argument to each compiler command. --extra-arg-before=<string> prepends one. Use them for a controlled, analysis-only adjustment, such as selecting a known language mode:
$ find-all-symbols-20 \
-p /path/to/project/build \
--extra-arg=-DAPI_SCAN=1 \
--output-dir=/tmp/find-all-symbols-results \
/path/to/project/src/widget.cpp
Keep the database as the source of truth for normal include paths and definitions. An extra argument does not repair a wrong compiler command, a missing generated header or a source path absent from the database.
Tip
If the analysis changes after you add an argument, record it alongside the result so another run can reproduce it.
7. Understand empty output and failed runs
A successful exit does not guarantee the output directory holds a useful record. A file can be empty when the source exposes no symbols the tool records, while a path or compile-database problem may print a diagnostic and leave no usable result. Check both the exit status and the files:
$ find-all-symbols-20 -p /path/to/project/build \
--output-dir=/tmp/find-all-symbols-results \
/path/to/project/src/widget.cpp
$ status=$?
$ printf 'exit status: %s\n' "$status"
exit status: 0
$ find /tmp/find-all-symbols-results -maxdepth 1 -type f -size +0c -print
/tmp/find-all-symbols-results/widget.cpp-5ac549.yaml
Warning
If the status is non-zero, keep the diagnostic and fix the database, path or source error before you interpret any old files in the output directory. A stale YAML file can make a failed run look successful, which is why a fresh directory is safer for automation.
Done means
- Right version.
find-all-symbols-20 --versionreports the expected LLVM 20.1.8 installation. - Database match. The selected source file is in the compile database and is analysed with its recorded compiler context.
- Clean output. Results went to a fresh, separate directory and you inspected them as YAML records.
- Repeatable. Multiple input files and any extra compiler argument are recorded.
- Suspicious of silence. Empty output, stale output and non-zero exits are treated as things to investigate, not as an API inventory.