Appearance
Parser Backends
Flude can read C++ sources through three interchangeable backends. They produce the same intermediate model, so everything downstream (renderers, coverage gates, diffs) works the same whichever one you pick. The backend is chosen by the parser_backend key (global config, SDK config or doc-config; the usual cascade applies) and defaults to doxygen.
parser_backend | How it reads the code | Needs | Speed | Best for |
|---|---|---|---|---|
doxygen | Runs Doxygen and reads its XML | the Doxygen binary | fast, results cached | projects whose public API is plain declarations |
native | Compiles each file with libclang | a working build: compile_commands.json or a .vcxproj | slowest (minutes to tens of minutes) | the most exact result, macro-heavy code that you can build |
syntactic | tree-sitter plus the pcpp preprocessor, no compiler | nothing but the sources (a build file only improves the result) | fast (seconds to a minute) | code you cannot or do not want to build, large trees, quick feedback |
syntactic is C and C++ only (collector.language of cpp); any other language raises an error at start-up.
🧭 Choosing a backend
- No build available, or a build that takes hours — use
syntactic. It expands the project's own macros with a real preprocessor, so entities generated by property/factory macros are found, which Doxygen does not do by default. - A working
compile_commands.jsonor.vcxproj, and exactness matters more than time — usenative. It sees the program the compiler sees. - Plain declarations, no macro tricks —
doxygenis enough.
On a large real-world codebase (microsoft/terminal, 388 files, 3.8 MB, 4 CPUs) native takes about 15 minutes and 3 GB of memory and syntactic about 35 seconds. Against Doxygen the syntactic result has full recall; the entities it adds are the macro-generated ones Doxygen cannot see (see What the numbers mean below).
⚙️ Syntactic backend settings
All keys live under collector in the doc-config. Paths are resolved relative to the doc-config's own directory.
file_patterns,exclude,exclude_patterns,min_visibility— which files are parsed and which members are reported, with the same meaning as for the other backends.min_visibilitydefaults toprotected: private members are not reported.include_paths— directories the preprocessor searches to resolve#include. Following the include graph is what lets the backend see macros defined in headers, and it is also about 93% of the run time, so this is the main speed/accuracy lever (see Profiles). An include that cannot be found is skipped, never an error.macros— extra definitions,NAME,NAME=VALUEorNAME(args)=VALUE. An empty value (EXPORT_API=) removes an annotation macro from the code before parsing.expand_as_defined— names of project macros whose real expansion should be used. For this backend they are appended tomacros.vcxproj_path,vcxproj_configuration,vcxproj_platform,bazel_build_path,bazel_target_name,cmake_lists_path— read macros (and, for thethoroughprofile or whenbuild_file_include_dirsis enabled, include directories) from an existing build file, statically, without running the build. Withoutbuild_file_include_dirsonly what the file states plainly is read: a.vcxproj's own settings, aCMakeLists.txt's top-levelinclude_directories()andadd_definitions(), a Bazel target'sincludes,definesandcopts.build_file_include_dirs— boolean, defaultfalse. Turns on the extended reading of the build files above and adds the include directories they declare, only those that exist and lie inside the project tree (below the deepest common ancestor of the build file's directory and the configuredinclude_paths).balancedis otherwise unchanged,fastignores the key, andthoroughalready adds those directories (it does not turn on the extended reading by itself). Directories fromcompile_commands.jsonare not covered by it. The extended reading adds:.vcxproj—<Import>ed.propsfiles are followed (up to 8 levels, only files inside the project tree), simpleConditions are evaluated (==,!=,Exists(),And,Or), and the properties$(MSBuildThisFileDirectory),$(ProjectDir),$(Configuration),$(Platform), those set in the files read, and$(SolutionDir)(the directory of the nearest.slnabove the project) are resolved. An implicitDirectory.Build.propsis read too.- CMake — commands at the top level of the file (not inside
if,foreach,functionormacro):set()variables,include_directories,add_definitions,add_compile_definitions,target_include_directories,target_compile_definitionsand the-Dand-Iitems oftarget_compile_options. The definitions and directories of all targets in the file are merged (there is no target selector).$<BUILD_INTERFACE:…>is unwrapped; other generator expressions are ignored. - Bazel — a relative
-Iincoptsis resolved from the workspace root (the nearest directory above theBUILDfile that holdsWORKSPACEorMODULE.bazel).
Limits: no file outside the project tree is ever read, and project-specific CMake helper macros and conditional blocks are not followed, so for some projects the extended reading finds nothing more (on nlohmann/json, googletest, protobuf and Abseil the
CMakeLists.txtyields no include directories or macros). Then listinclude_pathsandmacrosyourself. Measured on microsoft/terminal in October 2026: with onlyvcxproj_path,build_file_include_dirs: trueand the repository root asinclude_paths, the backend finds the same 7,032 entities as a hand-written configuration (nine include directories and the macros listed by hand); without the key it finds 6,119.compile_commands_path— acompile_commands.json. Each file gets the-Dflags of its own entry; headers get the union.compile_commands_macros_excludelists macro names to drop from that union (for example ABI-test switches that are never part of one real build).target_triple— set to a Windows target (x86_64-pc-windows-msvc, amingwtriple) or give a.vcxproj, and the backend also defines the Windows SDK function spellings (STDMETHODIMP,STDMETHODCALLTYPE,STDMETHOD,IFACEMETHODand friends) and ignores calling-convention keywords and__declspec(uuid(...))class attributes, which the parser cannot read. A Linux or macOS project is unaffected.precompiled_header— a file name, a list of names, orfalse. A header parsed on its own does not see the macros its project's precompiled header brings in, so its macro-generated members stay unexpanded. With this key each header starts from the macro state of the nearestpch.hup the directory tree, and each source file whose first real line is#include "pch.h"is seeded the same way instead of re-reading the chain. When the key is absent it is read from the.vcxproj's<PrecompiledHeaderFile>; with no.vcxprojit is off.max_workers— number of worker processes for the preprocessing step. The default follows the container's CPU quota (cgroup-aware), capped at 8;1runs everything in one process. The result is identical for any value; only the time changes.profile—fast,balanced(default) orthorough, see below.
⏱️ Profiles
A profile states how much of the include graph the backend follows. It changes nothing else: max_workers and the macro settings are untouched. It takes precedence over include_paths and precompiled_header (fast ignores them, thorough extends them), because otherwise it would do nothing for a project that already lists its include paths.
balanced(default) — the include paths, precompiled header and macros exactly as configured (withbuild_file_include_dirsenabled, also the include directories and macros the build files declare, see above).fast— follows no#includeat all:include_pathsandprecompiled_headerare ignored. Use it for a quick structural scan, a size estimate or a smoke test.thorough—balancedplus the include directories the build itself declares: the-I,-isystemand-iquotedirectories ofcompile_commands.jsonthat lie inside the project tree (system directories are skipped), and the include directories of a.vcxproj, Bazel or CMake file. Use it when you have a build file and want the most complete macro-generated entities.
Measured on three real projects (4 CPUs, October 2026, reference = Doxygen configured to expand the project's entity-generating macros; missing is what Doxygen found and the backend did not, extra what the backend found and Doxygen did not and that is not a verified macro-generated entity):
| Project | Profile | Time | Entities found | Missing | Extra | Verified macro-generated extras |
|---|---|---|---|---|---|---|
microsoft/terminal (C++/WinRT, .vcxproj) | fast | 27 s | 5,962 | 235 | 20 | 1,459 |
balanced | 35 s | 6,728 | 0 | 16 | 2,174 | |
thorough | 36 s | 6,728 | 0 | 16 | 2,174 | |
| dmlc/xgboost (CMake) | fast | 33 s | 5,039 | 132 | 106 | 54 |
balanced | 61 s | 5,167 | 0 | 0 | 156 | |
thorough | 68 s | 5,301 | 0 | 0 | 332 | |
| nlohmann/json (header-only) | fast | 4 s | 953 | 933 | 946 | 0 |
balanced | 11 s | 938 | 83 | 81 | 0 | |
thorough | 10 s | 938 | 83 | 81 | 0 |
How to read it:
fastis 1.3 to 3 times faster and loses what lives in headers. On terminal and xgboost its worst category misses 4 to 10% of what Doxygen finds, and it loses a third to two thirds of the macro-generated entities. On nlohmann/json it finds nothing that matches: that library's namespace is spelled by a macro defined in its own headers, so without following#includeevery qualified name comes out different. Use it for a structural scan or a size estimate, not for documentation.balancedis the reference. It is the profile every measurement in this project is made with. The remaining differences on nlohmann/json are known gaps of the syntactic backend on heavily templated header-only code; usenativethere if you can build it.thoroughcosts about 10% more time and pays off when the build declares include directories you did not list. On xgboost it adds 134 entities, 176 more of them verified macro-generated ones (the library's parameter-registration macros), with the same recall and precision; on terminal and nlohmann/json there is nothing to add.
🔎 What the numbers mean
The reference for these measurements is Doxygen, configured to expand the project's entity-generating macros. A syntactic entity that Doxygen does not report is not necessarily wrong: the macro-generated members (properties, factories, X-list settings) are real code that Doxygen's simplified preprocessor does not expand even when asked. Each such entity is verified against the sources before it is excluded from the precision figure; the few that could not be verified stay in the figures. Recall of 100% means every entity Doxygen found, the syntactic backend found too.
🩺 Timing a run
Set the environment variable FLUDE_PROFILE=1 and the catalog metadata gets a per-stage timing (discovery, prefix_baseline, preprocess, normalize, tree_sitter_parse, extract, merge_finalize), wall and CPU seconds, plus the number and size of the parsed files. Without the variable nothing is measured and nothing changes.
🧭 Where This Fits
The backend key and the other doc-config keys are described in Target Settings; the global cascade in Global Settings.
