Generator executable example

This commit is contained in:
Duncan Ogilvie
2026-02-13 23:58:59 +01:00
parent 8650f53174
commit 9f80ce4429
7 changed files with 243 additions and 1 deletions
+34 -1
View File
@@ -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: $<TARGET_FILE_NAME:hello>"]
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
+69
View File
@@ -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: $<TARGET_FILE_NAME:generate_numbers>"]
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 = ["$<TARGET_FILE:generate_numbers>", "${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 = ["$<TARGET_FILE:main>"]
comment = "Run the main executable to verify generated code works"
```
<sup><sub>This page was automatically generated from [tests/generator-executable/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/generator-executable/cmake.toml).</sub></sup>
+10
View File
@@ -141,3 +141,13 @@ add_test(
"$<TARGET_FILE:cmkr>"
build
)
add_test(
NAME
generator-executable
WORKING_DIRECTORY
"${CMAKE_CURRENT_LIST_DIR}/generator-executable"
COMMAND
"$<TARGET_FILE:cmkr>"
build
)
+6
View File
@@ -77,3 +77,9 @@ name = "custom-command"
working-directory = "custom-command"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
[[test]]
name = "generator-executable"
working-directory = "generator-executable"
command = "$<TARGET_FILE:cmkr>"
arguments = ["build"]
+49
View File
@@ -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: $<TARGET_FILE_NAME:generate_numbers>"]
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 = ["$<TARGET_FILE:generate_numbers>", "${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 = ["$<TARGET_FILE:main>"]
comment = "Run the main executable to verify generated code works"
@@ -0,0 +1,49 @@
// Simple code generator that produces a C++ source file with a function
// that returns a vector of numbers.
#include <fstream>
#include <iostream>
#include <string>
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <output_file>" << 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 <vector>\n";
out << "#include <cstddef>\n";
out << "\n";
out << "std::vector<int> 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;
}
+26
View File
@@ -0,0 +1,26 @@
// Main executable that uses the generated code
#include <iostream>
#include <vector>
// Functions generated by codegen executable
std::vector<int> 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;
}
}