Skip to content

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.

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 cmakefmt in-process instead of spawning a subprocess
  • experiment with custom command registries
  • parse CMake and inspect the AST directly

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_source and format_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.

The most important items today:

  • format_source
  • format_source_with_debug
  • format_source_with_registry
  • format_source_with_registry_debug
  • Config
  • CaseStyle
  • DangleAlign
  • PerCommandConfig
  • Error
  • Result

Lower-level access is available through:

  • cmakefmt::parser
  • cmakefmt::spec::registry::CommandRegistry
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.

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.

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());
}
}

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.

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 = 1
flags = ["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(), &registry)?;
println!("{out}");
Ok(())
}

This is the primary embedded path for generated or custom CMake DSLs.

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.

The library uses a shared cmakefmt::Error type across parsing, config loading, registry loading, and formatting:

Error kindMeaning
Error::Parsethe input was not valid CMake under the current grammar, with crate-owned source and location diagnostics
Error::Configa user config file failed to parse or validate
Error::Speca command-spec override or built-in spec failed to parse
Error::Iofile I/O failed without an associated path (e.g. stdin/stdout streams)
Error::IoAtfile I/O failed with the offending path attached
Error::CliArga CLI argument validation failed — incompatible flag combinations, missing required arguments, conflicting overrides
Error::InvalidRegexa regex pattern from the user (CLI flag, config file, or spec override) failed to compile; carries the underlying regex::Error via #[source]
Error::Renderfailure rendering a Config, Spec, or report (JSON / SARIF / Checkstyle / JUnit / Edit) to text
Error::LegacyMigrationfailure during legacy cmake-format config migration (parsing, converting, or writing the modernised file); carries the source path
Error::Formatterresidual catch-all for genuinely miscellaneous CLI/runtime conditions; most v1.5 sites have migrated to one of the structured variants above
Error::LayoutTooWidethe 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.

  • 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.