Skip to content

Commit 813302f

Browse files
committed
Generator executable example
1 parent d8e3a53 commit 813302f

7 files changed

Lines changed: 243 additions & 1 deletion

File tree

docs/examples/custom-command.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,44 @@ nav_order: 12
99

1010
# Tests add_custom_command and add_custom_target support
1111

12-
12+
This test demonstrates add_custom_command and add_custom_target support in cmkr.
1313

1414
```toml
15+
#
16+
# There are two forms of add_custom_command in CMake:
17+
# 1. Output form: generates files that can be consumed by other targets
18+
# 2. Target form: runs commands at build time for a specific target (pre-build, pre-link, post-build)
19+
1520
[project]
1621
name = "custom-command"
1722
description = "Tests add_custom_command and add_custom_target support"
1823

24+
# -----------------------------------------------------------------------------
25+
# Simple executable demonstrating post-build events
26+
# -----------------------------------------------------------------------------
27+
28+
[target.hello]
29+
type = "executable"
30+
sources = ["src/hello.cpp"]
31+
32+
# This post-build command runs after the 'hello' executable is built.
33+
# build-event can be: "pre-build", "pre-link", or "post-build"
34+
[[target.hello.custom-command]]
35+
build-event = "post-build"
36+
command = ["${CMAKE_COMMAND}", "-E", "echo", "Built executable: $<TARGET_FILE_NAME:hello>"]
37+
comment = "Print the built executable name"
38+
39+
# -----------------------------------------------------------------------------
40+
# Executable with code generation (output form custom command)
41+
# -----------------------------------------------------------------------------
42+
1943
[target.custom-command]
2044
type = "executable"
2145
sources = ["src/main.cpp"]
2246
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
2347

48+
# Output form: This custom command generates source files before building.
49+
# The outputs are automatically added as sources to the target.
2450
[[target.custom-command.custom-command]]
2551
outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/generated.cpp"]
2652
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/generated/generated.hpp"]
@@ -35,6 +61,7 @@ command = [
3561
comment = "Generate source files"
3662
verbatim = true
3763

64+
# Target form: This post-build command runs after 'custom-command' is built.
3865
[[target.custom-command.custom-command]]
3966
build-event = "post-build"
4067
command = [
@@ -47,6 +74,12 @@ byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp"]
4774
comment = "Create a post-build stamp file"
4875
verbatim = true
4976

77+
# -----------------------------------------------------------------------------
78+
# Custom target (add_custom_target)
79+
# -----------------------------------------------------------------------------
80+
81+
# A custom target runs commands independently of any executable/library.
82+
# Setting 'all = true' makes it run as part of the default build.
5083
[target.custom-codegen]
5184
type = "custom"
5285
all = true
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
# Automatically generated from tests/generator-executable/cmake.toml - DO NOT EDIT
3+
layout: default
4+
title: Tests using an executable to generate sources for another target
5+
permalink: /examples/generator-executable
6+
parent: Examples
7+
nav_order: 13
8+
---
9+
10+
# Tests using an executable to generate sources for another target
11+
12+
This test demonstrates a common pattern: using an executable to generate sources
13+
14+
for another target. The generator runs at build time and produces code that is
15+
16+
consumed by the main executable.
17+
18+
```toml
19+
#
20+
# CMake handles the dependency automatically: the generator executable is built
21+
# first, then it runs to produce the generated sources, and finally the main
22+
# executable is built using those sources.
23+
24+
[project]
25+
name = "generator-executable"
26+
description = "Tests using an executable to generate sources for another target"
27+
28+
# -----------------------------------------------------------------------------
29+
# Generator executable: produces code at build time
30+
# -----------------------------------------------------------------------------
31+
32+
[target.generate_numbers]
33+
type = "executable"
34+
sources = ["src/generate_numbers.cpp"]
35+
36+
# Post-build: confirm the generator was built
37+
[[target.generate_numbers.custom-command]]
38+
build-event = "post-build"
39+
command = ["${CMAKE_COMMAND}", "-E", "echo", "Generator built successfully: $<TARGET_FILE_NAME:generate_numbers>"]
40+
comment = "Confirm generator executable was built"
41+
42+
# -----------------------------------------------------------------------------
43+
# Main executable: uses generated sources
44+
# -----------------------------------------------------------------------------
45+
46+
[target.main]
47+
type = "executable"
48+
sources = ["src/main.cpp"]
49+
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
50+
51+
# Output form custom command: runs the generate_numbers executable to generate sources.
52+
# The outputs are automatically added as sources to this target.
53+
# The DEPENDS on "generate_numbers" ensures the generator is built before this runs.
54+
[[target.main.custom-command]]
55+
outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
56+
depends = ["generate_numbers"]
57+
command = ["$<TARGET_FILE:generate_numbers>", "${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
58+
comment = "Run generate_numbers to generate numbers.cpp"
59+
60+
# Post-build: run the executable to verify it works
61+
[[target.main.custom-command]]
62+
build-event = "post-build"
63+
command = ["$<TARGET_FILE:main>"]
64+
comment = "Run the main executable to verify generated code works"
65+
```
66+
67+
68+
69+
<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>

tests/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/cmake.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,9 @@ name = "custom-command"
7777
working-directory = "custom-command"
7878
command = "$<TARGET_FILE:cmkr>"
7979
arguments = ["build"]
80+
81+
[[test]]
82+
name = "generator-executable"
83+
working-directory = "generator-executable"
84+
command = "$<TARGET_FILE:cmkr>"
85+
arguments = ["build"]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# This test demonstrates a common pattern: using an executable to generate sources
2+
# for another target. The generator runs at build time and produces code that is
3+
# consumed by the main executable.
4+
#
5+
# CMake handles the dependency automatically: the generator executable is built
6+
# first, then it runs to produce the generated sources, and finally the main
7+
# executable is built using those sources.
8+
9+
[project]
10+
name = "generator-executable"
11+
description = "Tests using an executable to generate sources for another target"
12+
13+
# -----------------------------------------------------------------------------
14+
# Generator executable: produces code at build time
15+
# -----------------------------------------------------------------------------
16+
17+
[target.generate_numbers]
18+
type = "executable"
19+
sources = ["src/generate_numbers.cpp"]
20+
21+
# Post-build: confirm the generator was built
22+
[[target.generate_numbers.custom-command]]
23+
build-event = "post-build"
24+
command = ["${CMAKE_COMMAND}", "-E", "echo", "Generator built successfully: $<TARGET_FILE_NAME:generate_numbers>"]
25+
comment = "Confirm generator executable was built"
26+
27+
# -----------------------------------------------------------------------------
28+
# Main executable: uses generated sources
29+
# -----------------------------------------------------------------------------
30+
31+
[target.main]
32+
type = "executable"
33+
sources = ["src/main.cpp"]
34+
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
35+
36+
# Output form custom command: runs the generate_numbers executable to generate sources.
37+
# The outputs are automatically added as sources to this target.
38+
# The DEPENDS on "generate_numbers" ensures the generator is built before this runs.
39+
[[target.main.custom-command]]
40+
outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
41+
depends = ["generate_numbers"]
42+
command = ["$<TARGET_FILE:generate_numbers>", "${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
43+
comment = "Run generate_numbers to generate numbers.cpp"
44+
45+
# Post-build: run the executable to verify it works
46+
[[target.main.custom-command]]
47+
build-event = "post-build"
48+
command = ["$<TARGET_FILE:main>"]
49+
comment = "Run the main executable to verify generated code works"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Simple code generator that produces a C++ source file with a function
2+
// that returns a vector of numbers.
3+
4+
#include <fstream>
5+
#include <iostream>
6+
#include <string>
7+
8+
int main(int argc, char *argv[]) {
9+
if (argc != 2) {
10+
std::cerr << "Usage: " << argv[0] << " <output_file>" << std::endl;
11+
return 1;
12+
}
13+
14+
std::string output_path = argv[1];
15+
16+
// Create output directory if needed
17+
auto last_slash = output_path.find_last_of("/\\");
18+
if (last_slash != std::string::npos) {
19+
// Note: In a real generator, you'd create the directory
20+
// CMake creates it for us when using add_custom_command
21+
}
22+
23+
std::ofstream out(output_path);
24+
if (!out) {
25+
std::cerr << "Failed to open: " << output_path << std::endl;
26+
return 1;
27+
}
28+
29+
out << "// Auto-generated by codegen - DO NOT EDIT\n";
30+
out << "#include <vector>\n";
31+
out << "#include <cstddef>\n";
32+
out << "\n";
33+
out << "std::vector<int> get_numbers() {\n";
34+
out << " return {";
35+
for (int i = 1; i <= 10; ++i) {
36+
if (i > 1)
37+
out << ", ";
38+
out << i * i; // 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
39+
}
40+
out << "};\n";
41+
out << "}\n";
42+
out << "\n";
43+
out << "std::size_t get_numbers_count() {\n";
44+
out << " return 10;\n";
45+
out << "}\n";
46+
47+
std::cout << "Generated: " << output_path << std::endl;
48+
return 0;
49+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Main executable that uses the generated code
2+
3+
#include <iostream>
4+
#include <vector>
5+
6+
// Functions generated by codegen executable
7+
std::vector<int> get_numbers();
8+
std::size_t get_numbers_count();
9+
10+
int main() {
11+
std::cout << "Numbers from generated code:" << std::endl;
12+
13+
auto numbers = get_numbers();
14+
for (std::size_t i = 0; i < numbers.size(); ++i) {
15+
std::cout << " numbers[" << i << "] = " << numbers[i] << std::endl;
16+
}
17+
18+
// Verify the count
19+
if (numbers.size() == get_numbers_count()) {
20+
std::cout << "Success: Generated " << numbers.size() << " numbers!" << std::endl;
21+
return 0;
22+
} else {
23+
std::cerr << "Error: Count mismatch!" << std::endl;
24+
return 1;
25+
}
26+
}

0 commit comments

Comments
 (0)