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 library API is usable today. The crate is still pre-1.0, so the public
API surface may evolve before long-term compatibility is guaranteed.
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 |
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 |
Error::Formatter | a formatter-layer invariant or unsupported case was hit |
For parse, config, and spec errors, the library retains file-path and location context so callers can surface useful diagnostics to users.
Current Limits
Section titled “Current Limits”- the public API is useful today, but still smaller than the CLI feature surface
- library stability is not promised yet — the crate is still pre-
1.0 - 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.