Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dependency Analyzers

rsconstruct uses dependency analyzers to scan source files and discover dependencies between files. Analyzers run after processors discover products and add dependency information to the build graph.

How analyzers work

  1. Product discovery: Processors discover products (source → output mappings).
  2. Dependency analysis: Analyzers scan source files to find dependencies.
  3. Graph resolution: Dependencies are added to products for correct build ordering.

Analyzers are decoupled from processors — they operate on any product with matching source files, regardless of which processor created it.

Built-in analyzers

Per-analyzer reference pages:

  • cpp — C/C++ #include scanning (invokes gcc/pkg-config)
  • icpp — C/C++ #include scanning, pure Rust (no subprocess)
  • python — Python import / from ... import resolution
  • markdown — Markdown image and link references
  • tera — Tera {% include %}, {% import %}, {% extends %} references

Configuration

Analyzers are configured in rsconstruct.toml:

[analyzer]
auto_detect = true                                  # default: true
enabled     = ["cpp", "markdown", "python", "tera"] # instances to run

[analyzer.cpp]
include_paths = ["include", "src"]

Only analyzers listed under [analyzer.X] (or enabled) are instantiated — there is no global “all analyzers always run” mode.

Auto-detection

An analyzer runs if:

  1. It is declared (listed in enabled or configured via [analyzer.X]).
  2. AND either auto_detect = false, OR the analyzer detects relevant files in the project.

This mirrors how processors work.

Caching

Analyzer results are cached in the dependency cache (.rsconstruct/deps.redb). On subsequent builds:

  • If a source file hasn’t changed, its cached dependencies are used.
  • If a source file has changed, dependencies are re-scanned.
  • The cache is shared across all analyzers.

Use the analyzers and deps commands to inspect the cache:

rsconstruct analyzers list            # list available analyzers
rsconstruct analyzers defconfig cpp   # show default config for an analyzer
rsconstruct analyzers add cpp         # append [analyzer.cpp] to rsconstruct.toml with comments
rsconstruct analyzers add cpp --dry-run  # preview without writing
rsconstruct deps all                  # show all cached dependencies
rsconstruct deps for src/main.c       # show dependencies for specific files
rsconstruct deps clean                # clear the dependency cache

Build phases

With --phases, you can see when analyzers run:

rsconstruct --phases build

Output:

Phase: Building dependency graph...
  Phase: discover
  Phase: add_dependencies    # Analyzers run here
  Phase: apply_tool_version_hashes
  Phase: resolve_dependencies

Use --stop-after add-dependencies to stop after dependency analysis:

rsconstruct build --stop-after add-dependencies

Adding a custom analyzer

Analyzers implement the DepAnalyzer trait:

#![allow(unused)]
fn main() {
pub trait DepAnalyzer: Sync + Send {
    fn description(&self) -> &str;
    fn auto_detect(&self, file_index: &FileIndex) -> bool;
    fn analyze(
        &self,
        graph: &mut BuildGraph,
        deps_cache: &mut DepsCache,
        file_index: &FileIndex,
        verbose: bool,
    ) -> Result<()>;
}
}

The analyze method should:

  1. Find products with relevant source files.
  2. Scan each source file for dependencies (using the cache when available).
  3. Add discovered dependencies to the product’s inputs.