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 follows Rust semver within each major version. See the 2.0 migration guide when upgrading from 1.x.

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

Argument comment placement is controlled by ArgumentCommentStyle:

use cmakefmt::{ArgumentCommentStyle, Config};
let config = Config {
argument_comment_style: ArgumentCommentStyle::Standalone,
..Config::default()
};

ArgumentCommentStyle::Preserve is the default: standalone comments stay on separate lines and inline comments remain attached where they fit. ArgumentCommentStyle::Standalone puts all argument comments on separate lines. Comments after a command’s closing ) are unaffected.

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

  • 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 inthe repository and builddocs/ locally.