Library API
cmakefmt is primarily a CLI tool, but the crate also exposes a capable
embedded API for Rust code that needs to parse or format CMake sources
in-process — no subprocess, no shell escaping, no overhead.
When To Use The Library
Section titled “When To Use The Library”The crate is a strong fit when you want to:
- format generated CMake from Rust code
- build editor or IDE tooling around CMake formatting
- run
cmakefmtin-process instead of spawning a subprocess - experiment with custom command registries
- parse CMake and inspect the AST directly
Crate Status
Section titled “Crate Status”The Rust library API is intended to be stable for the 1.x line.
The stable contract is:
- top-level formatting entry points such as
format_sourceandformat_parsed_file - configuration loading and merging through
Config - the public AST returned by
cmakefmt::parser::parse - command-registry loading and override APIs through
CommandRegistry - crate-owned error diagnostics through
cmakefmt::Error
Internal implementation details are not part of that contract. That includes the parser engine, grammar-rule names, and other private helper types.
Public Entry Points
Section titled “Public Entry Points”The most important items today:
format_sourceformat_source_with_debugformat_source_with_registryformat_source_with_registry_debugConfigCaseStyleDangleAlignPerCommandConfigErrorResult
Lower-level access is available through:
cmakefmt::parsercmakefmt::spec::registry::CommandRegistry
Minimal Formatting Example
Section titled “Minimal Formatting Example”use cmakefmt::{Config, format_source};
fn main() -> Result<(), cmakefmt::Error> { let src = "target_link_libraries(foo PUBLIC bar)"; let out = format_source(src, &Config::default())?; println!("{out}"); Ok(())}The simplest entry point when you already have source text in memory.
Formatting With A Tweaked Config
Section titled “Formatting With A Tweaked Config”use cmakefmt::{CaseStyle, Config, format_source};
fn main() -> Result<(), cmakefmt::Error> { let mut config = Config::default(); config.line_width = 100; config.command_case = CaseStyle::Lower; config.keyword_case = CaseStyle::Upper; config.dangle_parens = true;
let src = r#"add_library(foo STATIC a.cc b.cc)target_link_libraries(foo PUBLIC bar baz)"#;
let out = format_source(src, &config)?; println!("{out}"); Ok(())}Use this pattern when the application needs to supply formatter policy at runtime rather than discovering it from disk.
Loading Config From Disk
Section titled “Loading Config From Disk”To use the same config-loading behavior the CLI uses:
use std::path::Path;
use cmakefmt::Config;
fn main() -> Result<(), cmakefmt::Error> { let config = Config::from_file(Path::new(".cmakefmt.yaml"))?; println!("line width: {}", config.line_width); Ok(())}Merge multiple explicit config files in precedence order:
use std::path::PathBuf;
use cmakefmt::Config;
fn main() -> Result<(), cmakefmt::Error> { let config = Config::from_files(&[ PathBuf::from("base.yaml"), PathBuf::from("team.yaml"), ])?; println!("{:#?}", config); Ok(())}Ask which config files would be discovered for a given target:
use std::path::Path;
use cmakefmt::Config;
fn main() { let sources = Config::config_sources_for(Path::new("src/CMakeLists.txt")); for path in sources { println!("{}", path.display()); }}Formatting With Debug Decisions
Section titled “Formatting With Debug Decisions”Building tooling and want insight into what the formatter decided? Use the debug variant:
use cmakefmt::{Config, format_source_with_debug};
fn main() -> Result<(), cmakefmt::Error> { let src = "install(TARGETS mylib DESTINATION lib)"; let (formatted, debug_lines) = format_source_with_debug(src, &Config::default())?;
println!("{formatted}"); for line in debug_lines { eprintln!("{line}"); }
Ok(())}The returned debug lines are the same formatter-decision detail that the CLI
emits under --debug.
Using A Custom Command Registry
Section titled “Using A Custom Command Registry”For syntax that is not part of the built-in registry, use CommandRegistry
directly:
use cmakefmt::{Config, format_source_with_registry};use cmakefmt::spec::registry::CommandRegistry;
fn main() -> Result<(), cmakefmt::Error> { let mut registry = CommandRegistry::load()?; registry.merge_override_str( r#"[commands.my_custom_command]pargs = 1flags = ["QUIET"]
[commands.my_custom_command.kwargs.SOURCES]nargs = "+""#, "inline-override.toml", )?;
let src = "my_custom_command(foo QUIET SOURCES a.cc b.cc)"; let out = format_source_with_registry(src, &Config::default(), ®istry)?; println!("{out}"); Ok(())}This is the primary embedded path for generated or custom CMake DSLs.
Parsing Without Formatting
Section titled “Parsing Without Formatting”When you only need the AST:
use cmakefmt::parser::parse;
fn main() -> Result<(), cmakefmt::Error> { let file = parse("project(example LANGUAGES CXX)")?; println!("{:#?}", file); Ok(())}Useful for analysis tools, migration tooling, or experiments that want the CMake parse tree but not the formatter.
Error Model
Section titled “Error Model”The library uses a shared cmakefmt::Error type across parsing, config
loading, registry loading, and formatting:
| Error kind | Meaning |
|---|---|
Error::Parse | the input was not valid CMake under the current grammar, with crate-owned source and location diagnostics |
Error::Config | a user config file failed to parse or validate |
Error::Spec | a command-spec override or built-in spec failed to parse |
Error::Io | file I/O failed without an associated path (e.g. stdin/stdout streams) |
Error::IoAt | file I/O failed with the offending path attached |
Error::CliArg | a CLI argument validation failed — incompatible flag combinations, missing required arguments, conflicting overrides |
Error::InvalidRegex | a regex pattern from the user (CLI flag, config file, or spec override) failed to compile; carries the underlying regex::Error via #[source] |
Error::Render | failure rendering a Config, Spec, or report (JSON / SARIF / Checkstyle / JUnit / Edit) to text |
Error::LegacyMigration | failure during legacy cmake-format config migration (parsing, converting, or writing the modernised file); carries the source path |
Error::Formatter | residual catch-all for genuinely miscellaneous CLI/runtime conditions; most v1.5 sites have migrated to one of the structured variants above |
Error::LayoutTooWide | the debug layout renderer exceeded the configured line width |
All variants are #[non_exhaustive], so further sub-splitting in future
releases stays patch-safe. The IoAt, InvalidRegex, and LegacyMigration
variants all carry contextual data (path, regex source, file path
respectively) — the InvalidRegex regex::Error chain via #[source]
means std::error::Error::source() walks into the underlying regex
parser failure for cleaner diagnostics.
For parse, config, and spec errors, the library retains crate-owned path and location context so callers can surface useful diagnostics without depending on parser-library or YAML-library internals.
Current Limits
Section titled “Current Limits”- the public API is smaller than the CLI feature surface
- workflow features like Git-aware selection and ignore-file handling live in the CLI layer, not the formatting API itself
For deeper implementation details, continue with Architecture.
Docs track main. For historical docs, check out a release tag in
the repository and build
docs/ locally.