Extract C++ API Symbols with find-all-symbols 20
You will produce YAML records for symbols that find-all-symbols-20 finds while parsing selected C++ source files. The workflow uses a CMake-style compile_commands.json, writes results to a separate directory, and checks the records rather than assuming that a successful exit means useful output.
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
Allow about fifteen minutes. You need the clang-tools-20 package, a C++ source tree, and a compile command database. The installed command here is LLVM 20.1.8 from package version 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139. 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 command syntax is find-all-symbols-20 [options] <source0> [... <sourceN>]. It is a Clang tooling program, so it needs the same sort of compiler arguments that the source normally uses. Those arguments 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, then repeat this check. Do not run the analysis as root merely 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, but not the source tree's files. If the project already has a configured build, first look for the database:
$ find /path/to/project -name compile_commands.json -type f -print
/path/to/project/build/compile_commands.json
A compilation database contains the working directory, source path, and compiler command for each translation unit. It matters when the code depends on include directories, language standards, generated headers or preprocessor definitions. Running the tool without those flags can produce no records or misleading diagnostics.
The installed manual says that, without -p, 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 does not 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 includes the input basename and a generated suffix. Do not script against the exact suffix. Treat the output directory as disposable analysis data, not as 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
...
Records identify the symbol name, its namespace context, the declaration file, its kind, and counts observed during the run. In this example the input is widget.cpp, while the reported declaration is in the included public header. That is often the useful result when you are inventorying an API.
4. Pass several translation units
You can provide 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
The rm in this example targets only the disposable results directory. Check the path before running it: shell redirection and deletion mistakes can remove useful files. If the directory contains anything you need, choose a new empty directory instead. Recovery is simply to 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 generated YAML files. Do not assume one output file represents the whole project.
5. Use relative paths only when they resolve in the database
The manual permits absolute and relative source paths, but their matching rules are easy to trip over. An absolute path must point into the CMake source tree. A 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
If the command cannot find a compile command, first 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 solve 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 these 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, missing generated header or source path that is absent from the database. If analysis changes after adding an argument, record it alongside the result so another run can be reproduced.
7. Understand empty output and failed runs
A successful process exit is not a guarantee that the output directory contains a useful symbol record. A file can be empty when the selected source exposes no symbols that the tool records, while a path or compile-database problem may produce a diagnostic and 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
If the status is non-zero, keep the diagnostic and fix the database, path or source error before interpreting 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
find-all-symbols-20 --versionreports the expected LLVM 20.1.8 installation.- The selected source file is present in the compile database and is analysed with its recorded compiler context.
- Results are written to a fresh, separate directory and inspected as YAML records.
- Multiple input files and any extra compiler argument are recorded for repeatability.
- Empty output, stale output and non-zero exits are treated as results to investigate, not as an API inventory.