From 9f80ce4429358de3c8b2383e75cd7d76098bb928 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Fri, 13 Feb 2026 23:58:59 +0100 Subject: [PATCH] Generator executable example --- docs/examples/custom-command.md | 35 +++++++++- docs/examples/generator-executable.md | 69 +++++++++++++++++++ tests/CMakeLists.txt | 10 +++ tests/cmake.toml | 6 ++ tests/generator-executable/cmake.toml | 49 +++++++++++++ .../src/generate_numbers.cpp | 49 +++++++++++++ tests/generator-executable/src/main.cpp | 26 +++++++ 7 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 docs/examples/generator-executable.md create mode 100644 tests/generator-executable/cmake.toml create mode 100644 tests/generator-executable/src/generate_numbers.cpp create mode 100644 tests/generator-executable/src/main.cpp diff --git a/docs/examples/custom-command.md b/docs/examples/custom-command.md index 7a4413b..4a4b1ac 100644 --- a/docs/examples/custom-command.md +++ b/docs/examples/custom-command.md @@ -9,18 +9,44 @@ nav_order: 12 # Tests add_custom_command and add_custom_target support - +This test demonstrates add_custom_command and add_custom_target support in cmkr. ```toml +# +# There are two forms of add_custom_command in CMake: +# 1. Output form: generates files that can be consumed by other targets +# 2. Target form: runs commands at build time for a specific target (pre-build, pre-link, post-build) + [project] name = "custom-command" description = "Tests add_custom_command and add_custom_target support" +# ----------------------------------------------------------------------------- +# Simple executable demonstrating post-build events +# ----------------------------------------------------------------------------- + +[target.hello] +type = "executable" +sources = ["src/hello.cpp"] + +# This post-build command runs after the 'hello' executable is built. +# build-event can be: "pre-build", "pre-link", or "post-build" +[[target.hello.custom-command]] +build-event = "post-build" +command = ["${CMAKE_COMMAND}", "-E", "echo", "Built executable: $"] +comment = "Print the built executable name" + +# ----------------------------------------------------------------------------- +# Executable with code generation (output form custom command) +# ----------------------------------------------------------------------------- + [target.custom-command] type = "executable" sources = ["src/main.cpp"] include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"] +# Output form: This custom command generates source files before building. +# The outputs are automatically added as sources to the target. [[target.custom-command.custom-command]] outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/generated.cpp"] byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/generated/generated.hpp"] @@ -35,6 +61,7 @@ command = [ comment = "Generate source files" verbatim = true +# Target form: This post-build command runs after 'custom-command' is built. [[target.custom-command.custom-command]] build-event = "post-build" command = [ @@ -47,6 +74,12 @@ byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp"] comment = "Create a post-build stamp file" verbatim = true +# ----------------------------------------------------------------------------- +# Custom target (add_custom_target) +# ----------------------------------------------------------------------------- + +# A custom target runs commands independently of any executable/library. +# Setting 'all = true' makes it run as part of the default build. [target.custom-codegen] type = "custom" all = true diff --git a/docs/examples/generator-executable.md b/docs/examples/generator-executable.md new file mode 100644 index 0000000..8da3c86 --- /dev/null +++ b/docs/examples/generator-executable.md @@ -0,0 +1,69 @@ +--- +# Automatically generated from tests/generator-executable/cmake.toml - DO NOT EDIT +layout: default +title: Tests using an executable to generate sources for another target +permalink: /examples/generator-executable +parent: Examples +nav_order: 13 +--- + +# Tests using an executable to generate sources for another target + +This test demonstrates a common pattern: using an executable to generate sources + +for another target. The generator runs at build time and produces code that is + +consumed by the main executable. + +```toml +# +# CMake handles the dependency automatically: the generator executable is built +# first, then it runs to produce the generated sources, and finally the main +# executable is built using those sources. + +[project] +name = "generator-executable" +description = "Tests using an executable to generate sources for another target" + +# ----------------------------------------------------------------------------- +# Generator executable: produces code at build time +# ----------------------------------------------------------------------------- + +[target.generate_numbers] +type = "executable" +sources = ["src/generate_numbers.cpp"] + +# Post-build: confirm the generator was built +[[target.generate_numbers.custom-command]] +build-event = "post-build" +command = ["${CMAKE_COMMAND}", "-E", "echo", "Generator built successfully: $"] +comment = "Confirm generator executable was built" + +# ----------------------------------------------------------------------------- +# Main executable: uses generated sources +# ----------------------------------------------------------------------------- + +[target.main] +type = "executable" +sources = ["src/main.cpp"] +include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"] + +# Output form custom command: runs the generate_numbers executable to generate sources. +# The outputs are automatically added as sources to this target. +# The DEPENDS on "generate_numbers" ensures the generator is built before this runs. +[[target.main.custom-command]] +outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"] +depends = ["generate_numbers"] +command = ["$", "${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"] +comment = "Run generate_numbers to generate numbers.cpp" + +# Post-build: run the executable to verify it works +[[target.main.custom-command]] +build-event = "post-build" +command = ["$"] +comment = "Run the main executable to verify generated code works" +``` + + + +This page was automatically generated from [tests/generator-executable/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/generator-executable/cmake.toml). diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 43ad11f..c04a938 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -141,3 +141,13 @@ add_test( "$" build ) + +add_test( + NAME + generator-executable + WORKING_DIRECTORY + "${CMAKE_CURRENT_LIST_DIR}/generator-executable" + COMMAND + "$" + build +) diff --git a/tests/cmake.toml b/tests/cmake.toml index f994df2..3997904 100644 --- a/tests/cmake.toml +++ b/tests/cmake.toml @@ -77,3 +77,9 @@ name = "custom-command" working-directory = "custom-command" command = "$" arguments = ["build"] + +[[test]] +name = "generator-executable" +working-directory = "generator-executable" +command = "$" +arguments = ["build"] diff --git a/tests/generator-executable/cmake.toml b/tests/generator-executable/cmake.toml new file mode 100644 index 0000000..0cb90d3 --- /dev/null +++ b/tests/generator-executable/cmake.toml @@ -0,0 +1,49 @@ +# This test demonstrates a common pattern: using an executable to generate sources +# for another target. The generator runs at build time and produces code that is +# consumed by the main executable. +# +# CMake handles the dependency automatically: the generator executable is built +# first, then it runs to produce the generated sources, and finally the main +# executable is built using those sources. + +[project] +name = "generator-executable" +description = "Tests using an executable to generate sources for another target" + +# ----------------------------------------------------------------------------- +# Generator executable: produces code at build time +# ----------------------------------------------------------------------------- + +[target.generate_numbers] +type = "executable" +sources = ["src/generate_numbers.cpp"] + +# Post-build: confirm the generator was built +[[target.generate_numbers.custom-command]] +build-event = "post-build" +command = ["${CMAKE_COMMAND}", "-E", "echo", "Generator built successfully: $"] +comment = "Confirm generator executable was built" + +# ----------------------------------------------------------------------------- +# Main executable: uses generated sources +# ----------------------------------------------------------------------------- + +[target.main] +type = "executable" +sources = ["src/main.cpp"] +include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"] + +# Output form custom command: runs the generate_numbers executable to generate sources. +# The outputs are automatically added as sources to this target. +# The DEPENDS on "generate_numbers" ensures the generator is built before this runs. +[[target.main.custom-command]] +outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"] +depends = ["generate_numbers"] +command = ["$", "${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"] +comment = "Run generate_numbers to generate numbers.cpp" + +# Post-build: run the executable to verify it works +[[target.main.custom-command]] +build-event = "post-build" +command = ["$"] +comment = "Run the main executable to verify generated code works" diff --git a/tests/generator-executable/src/generate_numbers.cpp b/tests/generator-executable/src/generate_numbers.cpp new file mode 100644 index 0000000..981777a --- /dev/null +++ b/tests/generator-executable/src/generate_numbers.cpp @@ -0,0 +1,49 @@ +// Simple code generator that produces a C++ source file with a function +// that returns a vector of numbers. + +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + std::string output_path = argv[1]; + + // Create output directory if needed + auto last_slash = output_path.find_last_of("/\\"); + if (last_slash != std::string::npos) { + // Note: In a real generator, you'd create the directory + // CMake creates it for us when using add_custom_command + } + + std::ofstream out(output_path); + if (!out) { + std::cerr << "Failed to open: " << output_path << std::endl; + return 1; + } + + out << "// Auto-generated by codegen - DO NOT EDIT\n"; + out << "#include \n"; + out << "#include \n"; + out << "\n"; + out << "std::vector get_numbers() {\n"; + out << " return {"; + for (int i = 1; i <= 10; ++i) { + if (i > 1) + out << ", "; + out << i * i; // 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 + } + out << "};\n"; + out << "}\n"; + out << "\n"; + out << "std::size_t get_numbers_count() {\n"; + out << " return 10;\n"; + out << "}\n"; + + std::cout << "Generated: " << output_path << std::endl; + return 0; +} diff --git a/tests/generator-executable/src/main.cpp b/tests/generator-executable/src/main.cpp new file mode 100644 index 0000000..0b5fae9 --- /dev/null +++ b/tests/generator-executable/src/main.cpp @@ -0,0 +1,26 @@ +// Main executable that uses the generated code + +#include +#include + +// Functions generated by codegen executable +std::vector get_numbers(); +std::size_t get_numbers_count(); + +int main() { + std::cout << "Numbers from generated code:" << std::endl; + + auto numbers = get_numbers(); + for (std::size_t i = 0; i < numbers.size(); ++i) { + std::cout << " numbers[" << i << "] = " << numbers[i] << std::endl; + } + + // Verify the count + if (numbers.size() == get_numbers_count()) { + std::cout << "Success: Generated " << numbers.size() << " numbers!" << std::endl; + return 0; + } else { + std::cerr << "Error: Count mismatch!" << std::endl; + return 1; + } +}