mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39b4522bc5 | |||
| da4ec26df0 | |||
| 31f77ab328 | |||
| 6d41c92e37 | |||
| 411eb03db4 | |||
| 9f80ce4429 | |||
| 8650f53174 | |||
| 6cb1009c0e |
@@ -10,7 +10,6 @@ if(CMAKE_SCRIPT_MODE_FILE)
|
|||||||
set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build")
|
set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build")
|
||||||
set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}")
|
set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}")
|
||||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
file(WRITE "${CMAKE_BINARY_DIR}/.gitignore" "# This file is autogenerated by cmkr.\n*\n")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set these from the command line to customize for development/debugging purposes
|
# Set these from the command line to customize for development/debugging purposes
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ tag = "v0.1"
|
|||||||
shallow = false # shallow clone (--depth 1)
|
shallow = false # shallow clone (--depth 1)
|
||||||
system = false
|
system = false
|
||||||
subdir = "" # folder containing CMakeLists.txt
|
subdir = "" # folder containing CMakeLists.txt
|
||||||
|
# Set cache variables before FetchContent_MakeAvailable
|
||||||
|
options = { BUILD_TESTS = false, BUILD_EXAMPLES = false }
|
||||||
|
|
||||||
# Include a CMake project from a URL
|
# Include a CMake project from a URL
|
||||||
[fetch-content.urlcontent]
|
[fetch-content.urlcontent]
|
||||||
@@ -213,10 +215,19 @@ sha1 = "502a4e25b8b209889c99c7fa0732102682c2e4ff"
|
|||||||
condition = "mycondition"
|
condition = "mycondition"
|
||||||
svn = "https://svn-host.com/url"
|
svn = "https://svn-host.com/url"
|
||||||
rev = "svn_rev"
|
rev = "svn_rev"
|
||||||
|
|
||||||
|
# For many options, you can also use a separate table:
|
||||||
|
# [fetch-content.bigproject.options]
|
||||||
|
# BUILD_TESTS = false
|
||||||
|
# BUILD_EXAMPLES = false
|
||||||
|
# SOME_STRING_OPTION = "value"
|
||||||
|
# SOME_LIST_OPTION = ["item1", "item2"] # becomes "item1;item2"
|
||||||
```
|
```
|
||||||
|
|
||||||
Table keys that match CMake variable names (`[A-Z_]+`) will be passed to the [`FetchContent_Declare`](https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare) command.
|
Table keys that match CMake variable names (`[A-Z_]+`) will be passed to the [`FetchContent_Declare`](https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare) command.
|
||||||
|
|
||||||
|
The `options` field allows setting CACHE variables before `FetchContent_MakeAvailable` is called. This is useful for configuring options on the fetched dependency (e.g., disabling tests). Boolean values become `ON`/`OFF`, arrays become semicolon-separated strings.
|
||||||
|
|
||||||
## Targets
|
## Targets
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -246,6 +257,24 @@ link-options = [""] # linker flags
|
|||||||
private-link-options = [""]
|
private-link-options = [""]
|
||||||
precompile-headers = [""] # precompiled headers
|
precompile-headers = [""] # precompiled headers
|
||||||
private-precompile-headers = [""]
|
private-precompile-headers = [""]
|
||||||
|
dependencies = [""] # add_dependencies(target ...)
|
||||||
|
|
||||||
|
# Keys below are for type = "custom" (add_custom_target)
|
||||||
|
all = false
|
||||||
|
command = ["${CMAKE_COMMAND}", "-E", "echo", "Hello"] # one command line
|
||||||
|
commands = [ # multiple command lines
|
||||||
|
["${CMAKE_COMMAND}", "-E", "echo", "First"],
|
||||||
|
["${CMAKE_COMMAND}", "-E", "echo", "Second"],
|
||||||
|
]
|
||||||
|
depends = ["generated.txt"] # add_custom_target(... DEPENDS ...)
|
||||||
|
byproducts = ["generated.txt"]
|
||||||
|
working-directory = "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
comment = "Run custom target"
|
||||||
|
job-pool = "pool"
|
||||||
|
job-server-aware = true
|
||||||
|
verbatim = true
|
||||||
|
uses-terminal = false # incompatible with job-pool when true
|
||||||
|
command-expand-lists = true
|
||||||
|
|
||||||
cmake-before = """
|
cmake-before = """
|
||||||
message(STATUS "CMake injected before the target")
|
message(STATUS "CMake injected before the target")
|
||||||
@@ -263,6 +292,51 @@ CXX_STANDARD_REQUIRED = true
|
|||||||
FOLDER = "MyFolder"
|
FOLDER = "MyFolder"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom commands
|
||||||
|
|
||||||
|
Use `[[target.<name>.custom-command]]` to map directly to [`add_custom_command`](https://cmake.org/cmake/help/latest/command/add_custom_command.html). A custom command can use either the `OUTPUT` form or the `TARGET` form:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# OUTPUT form (add_custom_command(OUTPUT ...))
|
||||||
|
[[target.mytarget.custom-command]]
|
||||||
|
condition = "mycondition"
|
||||||
|
outputs = ["generated.cpp"] # relative paths are interpreted from ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/generated.cpp",
|
||||||
|
"-P",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate.cmake",
|
||||||
|
]
|
||||||
|
depends = ["cmake/generate.cmake"]
|
||||||
|
byproducts = ["generated.hpp"]
|
||||||
|
main-dependency = "cmake/generate.cmake"
|
||||||
|
implicit-depends = [["CXX", "src/input.idl"]]
|
||||||
|
working-directory = "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
comment = "Generate source"
|
||||||
|
depfile = "${CMAKE_CURRENT_BINARY_DIR}/generated.d"
|
||||||
|
job-pool = "pool" # incompatible with uses-terminal when uses-terminal = true
|
||||||
|
job-server-aware = true
|
||||||
|
verbatim = true
|
||||||
|
append = false
|
||||||
|
uses-terminal = false
|
||||||
|
codegen = false
|
||||||
|
command-expand-lists = false
|
||||||
|
depends-explicit-only = false
|
||||||
|
|
||||||
|
# TARGET form (add_custom_command(TARGET ...))
|
||||||
|
[[target.mytarget.custom-command]]
|
||||||
|
build-event = "post-build" # pre-build, pre-link, post-build
|
||||||
|
command = ["${CMAKE_COMMAND}", "-E", "touch", "${CMAKE_CURRENT_BINARY_DIR}/post-build.stamp"]
|
||||||
|
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/post-build.stamp"]
|
||||||
|
working-directory = "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
comment = "Post-build step"
|
||||||
|
verbatim = true
|
||||||
|
command-expand-lists = false
|
||||||
|
uses-terminal = false
|
||||||
|
```
|
||||||
|
|
||||||
|
For each custom command entry, exactly one of `outputs` or `build-event` is required. Relative `outputs`/`byproducts` paths follow CMake and are interpreted from the current binary directory. `build-event` is only valid for non-`interface` targets, and `pre-link` is not supported for `type = "custom"`. In both `add_custom_command(OUTPUT ...)` and `add_custom_target(...)`, `job-pool` cannot be combined with `uses-terminal = true`. When `append = true` is used with the `OUTPUT` form, at least one `command`/`commands` entry or `depends` must also be provided.
|
||||||
|
|
||||||
A table mapping the cmkr features to the relevant CMake construct and the relevant documentation pages:
|
A table mapping the cmkr features to the relevant CMake construct and the relevant documentation pages:
|
||||||
|
|
||||||
| cmkr | CMake construct | Description |
|
| cmkr | CMake construct | Description |
|
||||||
@@ -279,6 +353,9 @@ A table mapping the cmkr features to the relevant CMake construct and the releva
|
|||||||
| `link-libraries` | [`target_link_libraries`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) | Adds library dependencies. Use `::mylib` to make sure a target exists. |
|
| `link-libraries` | [`target_link_libraries`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) | Adds library dependencies. Use `::mylib` to make sure a target exists. |
|
||||||
| `link-options` | [`target_link_options`](https://cmake.org/cmake/help/latest/command/target_link_options.html) | Adds linker flags. |
|
| `link-options` | [`target_link_options`](https://cmake.org/cmake/help/latest/command/target_link_options.html) | Adds linker flags. |
|
||||||
| `precompile-headers` | [`target_precompile_headers`](https://cmake.org/cmake/help/latest/command/target_precompile_headers.html) | Specifies precompiled headers. |
|
| `precompile-headers` | [`target_precompile_headers`](https://cmake.org/cmake/help/latest/command/target_precompile_headers.html) | Specifies precompiled headers. |
|
||||||
|
| `dependencies` | [`add_dependencies`](https://cmake.org/cmake/help/latest/command/add_dependencies.html) | Adds target-level dependencies. |
|
||||||
|
| `type = "custom"` + custom target keys | [`add_custom_target`](https://cmake.org/cmake/help/latest/command/add_custom_target.html) | Creates a custom target with command/dependency options. |
|
||||||
|
| `[[target.<name>.custom-command]]` | [`add_custom_command`](https://cmake.org/cmake/help/latest/command/add_custom_command.html) | Supports both `OUTPUT` and `TARGET` forms. |
|
||||||
| `properties` | [`set_target_properties`](https://cmake.org/cmake/help/latest/command/set_target_properties.html) | See [properties on targets](https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#properties-on-targets) for more information. |
|
| `properties` | [`set_target_properties`](https://cmake.org/cmake/help/latest/command/set_target_properties.html) | See [properties on targets](https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#properties-on-targets) for more information. |
|
||||||
|
|
||||||
The default [visibility](/basics) is as follows:
|
The default [visibility](/basics) is as follows:
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
---
|
||||||
|
# Automatically generated from tests/custom-command/cmake.toml - DO NOT EDIT
|
||||||
|
layout: default
|
||||||
|
title: Tests add_custom_command and add_custom_target support
|
||||||
|
permalink: /examples/custom-command
|
||||||
|
parent: Examples
|
||||||
|
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.
|
||||||
|
# Relative outputs follow CMake and are interpreted from the binary directory.
|
||||||
|
# The outputs are automatically added as sources to the target.
|
||||||
|
[[target.custom-command.custom-command]]
|
||||||
|
outputs = ["generated/generated.cpp"]
|
||||||
|
byproducts = ["generated/generated.hpp"]
|
||||||
|
depends = ["cmake/generate_source.cmake"]
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-DOUTPUT_CPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.cpp",
|
||||||
|
"-DOUTPUT_HPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.hpp",
|
||||||
|
"-P",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate_source.cmake",
|
||||||
|
]
|
||||||
|
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 = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-E",
|
||||||
|
"touch",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp",
|
||||||
|
]
|
||||||
|
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
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-E",
|
||||||
|
"touch",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp",
|
||||||
|
]
|
||||||
|
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp"]
|
||||||
|
comment = "Run custom target"
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Template-based custom target
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Custom target options should also work when the target type comes from a template.
|
||||||
|
[template.generated-custom-target]
|
||||||
|
type = "custom"
|
||||||
|
all = true
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
[target.custom-codegen-template]
|
||||||
|
type = "generated-custom-target"
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-DINPUT=${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||||
|
"-P",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_file_exists.cmake",
|
||||||
|
]
|
||||||
|
comment = "Verify the generated stamp exists"
|
||||||
|
|
||||||
|
# The output-form custom command should automatically become a dependency of the
|
||||||
|
# custom target, so the verification command sees the stamp file.
|
||||||
|
[[target.custom-codegen-template.custom-command]]
|
||||||
|
outputs = ["template-custom-target.stamp"]
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-E",
|
||||||
|
"touch",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||||
|
]
|
||||||
|
comment = "Create a stamp file for the template-based custom target"
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
# A target-level all = false should override the template's all = true.
|
||||||
|
# If this target is incorrectly included in the default build, the build fails.
|
||||||
|
[target.custom-codegen-template-disabled]
|
||||||
|
type = "generated-custom-target"
|
||||||
|
all = false
|
||||||
|
command = ["${CMAKE_COMMAND}", "-E", "false"]
|
||||||
|
comment = "This target must not run as part of the default build"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<sup><sub>This page was automatically generated from [tests/custom-command/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/custom-command/cmake.toml).</sub></sup>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
# 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"]
|
||||||
|
|
||||||
|
# Create the generated directory before the generator runs.
|
||||||
|
cmake-before = """
|
||||||
|
file(MAKE_DIRECTORY "${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>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
# Automatically generated from tests/protobuf/cmake.toml - DO NOT EDIT
|
||||||
|
layout: default
|
||||||
|
title: Demonstrates protobuf integration with cmkr custom commands
|
||||||
|
permalink: /examples/protobuf
|
||||||
|
parent: Examples
|
||||||
|
nav_order: 14
|
||||||
|
---
|
||||||
|
|
||||||
|
# Demonstrates protobuf integration with cmkr custom commands
|
||||||
|
|
||||||
|
This test demonstrates using protobuf with cmkr for code generation.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
#
|
||||||
|
# The example:
|
||||||
|
# 1. Fetches protobuf from GitHub using FetchContent
|
||||||
|
# 2. Uses a custom command to generate C++ sources from a .proto file
|
||||||
|
# 3. Links an executable against the generated sources and protobuf library
|
||||||
|
# 4. Serializes and deserializes a simple message
|
||||||
|
#
|
||||||
|
# Note: protobuf v21.x is used because newer versions (v22+) require abseil-cpp
|
||||||
|
# as a dependency, which significantly complicates the build.
|
||||||
|
|
||||||
|
[cmake]
|
||||||
|
version = "3.18...3.31"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "protobuf-example"
|
||||||
|
description = "Demonstrates protobuf integration with cmkr custom commands"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Fetch protobuf from GitHub
|
||||||
|
# Using v21.12 (last version before abseil-cpp became required)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[fetch-content.protobuf]
|
||||||
|
git = "https://github.com/protocolbuffers/protobuf"
|
||||||
|
tag = "v21.12"
|
||||||
|
options = {
|
||||||
|
protobuf_BUILD_TESTS = false,
|
||||||
|
protobuf_BUILD_EXAMPLES = false,
|
||||||
|
protobuf_BUILD_LIBPROTOC = false,
|
||||||
|
protobuf_BUILD_SHARED_LIBS = false,
|
||||||
|
protobuf_MSVC_STATIC_RUNTIME = false,
|
||||||
|
SKIP_INSTALL_ALL = true,
|
||||||
|
protobuf_WITH_ZLIB = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Main executable using protobuf
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[target.protobuf_example]
|
||||||
|
type = "executable"
|
||||||
|
sources = [
|
||||||
|
"src/main.cpp",
|
||||||
|
]
|
||||||
|
include-directories = [
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||||
|
]
|
||||||
|
link-libraries = ["protobuf::libprotobuf"]
|
||||||
|
compile-features = ["cxx_std_17"]
|
||||||
|
|
||||||
|
# Create the generated directory before running protoc
|
||||||
|
cmake-before = """
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Custom command to generate C++ sources from .proto file
|
||||||
|
# This uses protoc from the fetched protobuf repository
|
||||||
|
[[target.protobuf_example.custom-command]]
|
||||||
|
outputs = [
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.h",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.cc",
|
||||||
|
]
|
||||||
|
depends = [
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||||
|
"protobuf::protoc", # Ensure protoc is built first
|
||||||
|
]
|
||||||
|
command = [
|
||||||
|
"$<TARGET_FILE:protobuf::protoc>",
|
||||||
|
"--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||||
|
"-I${CMAKE_CURRENT_SOURCE_DIR}/proto",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||||
|
]
|
||||||
|
comment = "Generate C++ sources from addressbook.proto"
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
# Post-build: run the executable to verify it works
|
||||||
|
[[target.protobuf_example.custom-command]]
|
||||||
|
build-event = "post-build"
|
||||||
|
command = ["$<TARGET_FILE:protobuf_example>"]
|
||||||
|
comment = "Run protobuf_example to verify serialization/deserialization works"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<sup><sub>This page was automatically generated from [tests/protobuf/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/protobuf/cmake.toml).</sub></sup>
|
||||||
@@ -74,6 +74,104 @@ enum TargetType {
|
|||||||
extern const char *targetTypeNames[target_last];
|
extern const char *targetTypeNames[target_last];
|
||||||
|
|
||||||
struct Target {
|
struct Target {
|
||||||
|
struct CustomTarget {
|
||||||
|
bool has_all = false;
|
||||||
|
bool all = false;
|
||||||
|
|
||||||
|
bool has_command = false;
|
||||||
|
bool has_commands = false;
|
||||||
|
std::vector<std::vector<std::string>> commands;
|
||||||
|
std::vector<std::string> depends;
|
||||||
|
std::vector<std::string> byproducts;
|
||||||
|
|
||||||
|
bool has_working_directory = false;
|
||||||
|
std::string working_directory;
|
||||||
|
|
||||||
|
bool has_comment = false;
|
||||||
|
std::string comment;
|
||||||
|
|
||||||
|
bool has_job_pool = false;
|
||||||
|
std::string job_pool;
|
||||||
|
|
||||||
|
bool has_job_server_aware = false;
|
||||||
|
bool job_server_aware = false;
|
||||||
|
|
||||||
|
bool has_verbatim = false;
|
||||||
|
bool verbatim = false;
|
||||||
|
|
||||||
|
bool has_uses_terminal = false;
|
||||||
|
bool uses_terminal = false;
|
||||||
|
|
||||||
|
bool has_command_expand_lists = false;
|
||||||
|
bool command_expand_lists = false;
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return !has_all && !has_command && !has_commands && commands.empty() && depends.empty() && byproducts.empty() && !has_working_directory &&
|
||||||
|
!has_comment && !has_job_pool && !has_job_server_aware && !has_verbatim && !has_uses_terminal && !has_command_expand_lists;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CustomCommand {
|
||||||
|
struct ImplicitDependency {
|
||||||
|
std::string language;
|
||||||
|
std::string file;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string condition;
|
||||||
|
std::vector<std::vector<std::string>> commands;
|
||||||
|
|
||||||
|
std::vector<std::string> outputs;
|
||||||
|
std::string build_event;
|
||||||
|
|
||||||
|
bool has_append = false;
|
||||||
|
bool append = false;
|
||||||
|
|
||||||
|
bool has_main_dependency = false;
|
||||||
|
std::string main_dependency;
|
||||||
|
|
||||||
|
std::vector<std::string> depends;
|
||||||
|
std::vector<std::string> byproducts;
|
||||||
|
std::vector<ImplicitDependency> implicit_depends;
|
||||||
|
|
||||||
|
bool has_working_directory = false;
|
||||||
|
std::string working_directory;
|
||||||
|
|
||||||
|
bool has_comment = false;
|
||||||
|
std::string comment;
|
||||||
|
|
||||||
|
bool has_depfile = false;
|
||||||
|
std::string depfile;
|
||||||
|
|
||||||
|
bool has_job_pool = false;
|
||||||
|
std::string job_pool;
|
||||||
|
|
||||||
|
bool has_job_server_aware = false;
|
||||||
|
bool job_server_aware = false;
|
||||||
|
|
||||||
|
bool has_verbatim = false;
|
||||||
|
bool verbatim = false;
|
||||||
|
|
||||||
|
bool has_uses_terminal = false;
|
||||||
|
bool uses_terminal = false;
|
||||||
|
|
||||||
|
bool has_codegen = false;
|
||||||
|
bool codegen = false;
|
||||||
|
|
||||||
|
bool has_command_expand_lists = false;
|
||||||
|
bool command_expand_lists = false;
|
||||||
|
|
||||||
|
bool has_depends_explicit_only = false;
|
||||||
|
bool depends_explicit_only = false;
|
||||||
|
|
||||||
|
bool is_output_form() const {
|
||||||
|
return !outputs.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_target_form() const {
|
||||||
|
return !build_event.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
TargetType type = target_last;
|
TargetType type = target_last;
|
||||||
std::string type_name;
|
std::string type_name;
|
||||||
@@ -107,6 +205,9 @@ struct Target {
|
|||||||
|
|
||||||
ConditionVector dependencies;
|
ConditionVector dependencies;
|
||||||
|
|
||||||
|
CustomTarget custom_target;
|
||||||
|
std::vector<CustomCommand> custom_commands;
|
||||||
|
|
||||||
std::string condition;
|
std::string condition;
|
||||||
std::string alias;
|
std::string alias;
|
||||||
Condition<tsl::ordered_map<std::string, std::string>> properties;
|
Condition<tsl::ordered_map<std::string, std::string>> properties;
|
||||||
@@ -154,10 +255,15 @@ struct Subdir {
|
|||||||
ConditionVector include_after;
|
ConditionVector include_after;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ContentOption {
|
||||||
|
mpark::variant<bool, std::string> value;
|
||||||
|
};
|
||||||
|
|
||||||
struct Content {
|
struct Content {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string condition;
|
std::string condition;
|
||||||
tsl::ordered_map<std::string, std::string> arguments;
|
tsl::ordered_map<std::string, std::string> arguments;
|
||||||
|
tsl::ordered_map<std::string, ContentOption> options;
|
||||||
|
|
||||||
Condition<std::string> cmake_before;
|
Condition<std::string> cmake_before;
|
||||||
Condition<std::string> cmake_after;
|
Condition<std::string> cmake_after;
|
||||||
|
|||||||
+362
-31
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "project_parser.hpp"
|
#include "project_parser.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -1123,6 +1124,22 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
gen.conditional_includes(content.include_before);
|
gen.conditional_includes(content.include_before);
|
||||||
gen.conditional_cmake(content.cmake_before);
|
gen.conditional_cmake(content.cmake_before);
|
||||||
|
|
||||||
|
// Set cache variables before FetchContent
|
||||||
|
if (!content.options.empty()) {
|
||||||
|
for (const auto &opt : content.options) {
|
||||||
|
std::string value;
|
||||||
|
std::string type;
|
||||||
|
if (opt.second.value.index() == 0) {
|
||||||
|
value = mpark::get<0>(opt.second.value) ? "ON" : "OFF";
|
||||||
|
type = "BOOL";
|
||||||
|
} else {
|
||||||
|
value = mpark::get<1>(opt.second.value);
|
||||||
|
type = "STRING";
|
||||||
|
}
|
||||||
|
cmd("set")(opt.first, value, "CACHE", type, RawArg("\"\""), "FORCE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string version_info;
|
std::string version_info;
|
||||||
if (content.arguments.contains("GIT_TAG")) {
|
if (content.arguments.contains("GIT_TAG")) {
|
||||||
version_info = " (" + content.arguments.at("GIT_TAG") + ")";
|
version_info = " (" + content.arguments.at("GIT_TAG") + ")";
|
||||||
@@ -1205,6 +1222,14 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto resolved_target_type = target.type;
|
||||||
|
if (tmplate != nullptr) {
|
||||||
|
if (resolved_target_type != parser::target_template) {
|
||||||
|
throw_target_error("Unreachable code, unexpected target type for template");
|
||||||
|
}
|
||||||
|
resolved_target_type = tmplate->outline.type;
|
||||||
|
}
|
||||||
|
|
||||||
ConditionScope cs(gen, target.condition);
|
ConditionScope cs(gen, target.condition);
|
||||||
|
|
||||||
// Detect if there is cmake included before/after the target
|
// Detect if there is cmake included before/after the target
|
||||||
@@ -1244,6 +1269,103 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
gen.conditional_includes(target.include_before);
|
gen.conditional_includes(target.include_before);
|
||||||
gen.conditional_cmake(target.cmake_before);
|
gen.conditional_cmake(target.cmake_before);
|
||||||
|
|
||||||
|
auto merge_unique_vector = [](std::vector<std::string> &destination, const std::vector<std::string> &source) {
|
||||||
|
for (const auto &item : source) {
|
||||||
|
if (std::find(destination.begin(), destination.end(), item) == destination.end()) {
|
||||||
|
destination.push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto custom_target = target.custom_target;
|
||||||
|
if (tmplate != nullptr) {
|
||||||
|
const auto &custom = tmplate->outline.custom_target;
|
||||||
|
if (!custom_target.has_all && custom.has_all) {
|
||||||
|
custom_target.has_all = true;
|
||||||
|
custom_target.all = custom.all;
|
||||||
|
}
|
||||||
|
if (custom.has_command) {
|
||||||
|
custom_target.has_command = true;
|
||||||
|
}
|
||||||
|
if (custom.has_commands) {
|
||||||
|
custom_target.has_commands = true;
|
||||||
|
}
|
||||||
|
if (!custom.commands.empty()) {
|
||||||
|
custom_target.commands.insert(custom_target.commands.begin(), custom.commands.begin(), custom.commands.end());
|
||||||
|
}
|
||||||
|
merge_unique_vector(custom_target.depends, custom.depends);
|
||||||
|
merge_unique_vector(custom_target.byproducts, custom.byproducts);
|
||||||
|
if (!custom_target.has_working_directory && custom.has_working_directory) {
|
||||||
|
custom_target.has_working_directory = true;
|
||||||
|
custom_target.working_directory = custom.working_directory;
|
||||||
|
}
|
||||||
|
if (!custom_target.has_comment && custom.has_comment) {
|
||||||
|
custom_target.has_comment = true;
|
||||||
|
custom_target.comment = custom.comment;
|
||||||
|
}
|
||||||
|
if (!custom_target.has_job_pool && custom.has_job_pool) {
|
||||||
|
custom_target.has_job_pool = true;
|
||||||
|
custom_target.job_pool = custom.job_pool;
|
||||||
|
}
|
||||||
|
if (!custom_target.has_job_server_aware && custom.has_job_server_aware) {
|
||||||
|
custom_target.has_job_server_aware = true;
|
||||||
|
custom_target.job_server_aware = custom.job_server_aware;
|
||||||
|
}
|
||||||
|
if (!custom_target.has_verbatim && custom.has_verbatim) {
|
||||||
|
custom_target.has_verbatim = true;
|
||||||
|
custom_target.verbatim = custom.verbatim;
|
||||||
|
}
|
||||||
|
if (!custom_target.has_uses_terminal && custom.has_uses_terminal) {
|
||||||
|
custom_target.has_uses_terminal = true;
|
||||||
|
custom_target.uses_terminal = custom.uses_terminal;
|
||||||
|
}
|
||||||
|
if (!custom_target.has_command_expand_lists && custom.has_command_expand_lists) {
|
||||||
|
custom_target.has_command_expand_lists = true;
|
||||||
|
custom_target.command_expand_lists = custom.command_expand_lists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<parser::Target::CustomCommand> custom_commands;
|
||||||
|
if (tmplate != nullptr) {
|
||||||
|
custom_commands.insert(custom_commands.end(), tmplate->outline.custom_commands.begin(), tmplate->outline.custom_commands.end());
|
||||||
|
}
|
||||||
|
custom_commands.insert(custom_commands.end(), target.custom_commands.begin(), target.custom_commands.end());
|
||||||
|
|
||||||
|
if (resolved_target_type == parser::target_custom && custom_target.has_job_pool && custom_target.has_uses_terminal &&
|
||||||
|
custom_target.uses_terminal) {
|
||||||
|
throw_target_error("job-pool cannot be used with uses-terminal");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto has_cmake_path_reference = [](const std::string &value) {
|
||||||
|
return value.find("${") != std::string::npos || value.find("$ENV{") != std::string::npos ||
|
||||||
|
value.find("$CACHE{") != std::string::npos;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto normalize_generated_output_source = [&has_cmake_path_reference](const std::string &output) {
|
||||||
|
auto starts_with = [](const std::string &value, const std::string &prefix) {
|
||||||
|
return value.rfind(prefix, 0) == 0;
|
||||||
|
};
|
||||||
|
if (fs::path(output).is_absolute()) {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
if (starts_with(output, "${CMAKE_CURRENT_BINARY_DIR}") || starts_with(output, "${CMAKE_BINARY_DIR}") ||
|
||||||
|
starts_with(output, "${PROJECT_BINARY_DIR}") || starts_with(output, "${CMAKE_CURRENT_SOURCE_DIR}") ||
|
||||||
|
starts_with(output, "${CMAKE_SOURCE_DIR}") || starts_with(output, "${PROJECT_SOURCE_DIR}") ||
|
||||||
|
starts_with(output, "${CMAKE_CURRENT_LIST_DIR}") || starts_with(output, "$ENV{") || starts_with(output, "$CACHE{") ||
|
||||||
|
has_cmake_path_reference(output)) {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
return "${CMAKE_CURRENT_BINARY_DIR}/" + output;
|
||||||
|
};
|
||||||
|
|
||||||
|
parser::Condition<tsl::ordered_set<std::string>> mcustom_target_depends;
|
||||||
|
if (resolved_target_type == parser::target_custom) {
|
||||||
|
auto &unconditional_depends = mcustom_target_depends[""];
|
||||||
|
for (const auto &dep : custom_target.depends) {
|
||||||
|
unconditional_depends.insert(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Merge the sources from the template and the target. The sources
|
// Merge the sources from the template and the target. The sources
|
||||||
// without condition need to be processed first
|
// without condition need to be processed first
|
||||||
parser::Condition<tsl::ordered_set<std::string>> msources;
|
parser::Condition<tsl::ordered_set<std::string>> msources;
|
||||||
@@ -1262,8 +1384,21 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
merge_sources(target.sources);
|
merge_sources(target.sources);
|
||||||
|
|
||||||
|
for (const auto &custom_command : custom_commands) {
|
||||||
|
if (!custom_command.is_output_form()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto &condition_sources = msources[custom_command.condition];
|
||||||
|
for (const auto &output : custom_command.outputs) {
|
||||||
|
condition_sources.insert(normalize_generated_output_source(output));
|
||||||
|
if (resolved_target_type == parser::target_custom) {
|
||||||
|
mcustom_target_depends[custom_command.condition].insert(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Improve IDE support
|
// Improve IDE support
|
||||||
if (target.type != parser::target_interface) {
|
if (resolved_target_type != parser::target_interface) {
|
||||||
msources[""].insert("cmake.toml");
|
msources[""].insert("cmake.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1297,7 +1432,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure there are source files for the languages used by the project
|
// Make sure there are source files for the languages used by the project
|
||||||
switch (target.type) {
|
switch (resolved_target_type) {
|
||||||
case parser::target_executable:
|
case parser::target_executable:
|
||||||
case parser::target_library:
|
case parser::target_library:
|
||||||
case parser::target_shared:
|
case parser::target_shared:
|
||||||
@@ -1316,8 +1451,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
|
|
||||||
// Make sure relative source files exist
|
// Make sure relative source files exist
|
||||||
for (const auto &source : sources) {
|
for (const auto &source : sources) {
|
||||||
auto var_index = source.find("${");
|
if (has_cmake_path_reference(source))
|
||||||
if (var_index != std::string::npos)
|
|
||||||
continue;
|
continue;
|
||||||
const auto &source_path = fs::path(path) / source;
|
const auto &source_path = fs::path(path) / source;
|
||||||
if (!fs::exists(source_path)) {
|
if (!fs::exists(source_path)) {
|
||||||
@@ -1341,18 +1475,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
auto target_type = target.type;
|
auto target_type = resolved_target_type;
|
||||||
|
|
||||||
if (tmplate != nullptr) {
|
|
||||||
if (target_type != parser::target_template) {
|
|
||||||
throw_target_error("Unreachable code, unexpected target type for template");
|
|
||||||
}
|
|
||||||
target_type = tmplate->outline.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string add_command;
|
std::string add_command;
|
||||||
std::string target_type_string;
|
std::string target_type_string;
|
||||||
std::string target_scope;
|
std::string target_scope;
|
||||||
|
bool is_custom_target = false;
|
||||||
|
|
||||||
switch (target_type) {
|
switch (target_type) {
|
||||||
case parser::target_executable:
|
case parser::target_executable:
|
||||||
@@ -1381,9 +1509,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
target_scope = "INTERFACE";
|
target_scope = "INTERFACE";
|
||||||
break;
|
break;
|
||||||
case parser::target_custom:
|
case parser::target_custom:
|
||||||
// TODO: add proper support, this is hacky
|
is_custom_target = true;
|
||||||
add_command = "add_custom_target";
|
|
||||||
target_type_string = "SOURCES";
|
|
||||||
target_scope = "PUBLIC";
|
target_scope = "PUBLIC";
|
||||||
break;
|
break;
|
||||||
case parser::target_object:
|
case parser::target_object:
|
||||||
@@ -1396,6 +1522,157 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
throw_target_error("Unimplemented enum value");
|
throw_target_error("Unimplemented enum value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto has_custom_target_depends = false;
|
||||||
|
auto custom_target_depends_var = target.name + "_DEPENDS";
|
||||||
|
auto custom_target_depends_with_set = true;
|
||||||
|
if (is_custom_target) {
|
||||||
|
for (const auto &itr : mcustom_target_depends) {
|
||||||
|
if (!itr.second.empty()) {
|
||||||
|
has_custom_target_depends = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_custom_target_depends && mcustom_target_depends[""].empty()) {
|
||||||
|
custom_target_depends_with_set = false;
|
||||||
|
cmd("set")(custom_target_depends_var, RawArg("\"\"")).endl();
|
||||||
|
}
|
||||||
|
gen.handle_condition(mcustom_target_depends, [&](const std::string &condition, const tsl::ordered_set<std::string> &depend_set) {
|
||||||
|
std::vector<std::string> depends;
|
||||||
|
depends.reserve(depend_set.size());
|
||||||
|
for (const auto &depend : depend_set) {
|
||||||
|
depends.push_back(depend);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_target_depends_with_set) {
|
||||||
|
if (!condition.empty()) {
|
||||||
|
throw_target_error("Unreachable code, make sure unconditional depends are first");
|
||||||
|
}
|
||||||
|
cmd("set")(custom_target_depends_var, depends);
|
||||||
|
custom_target_depends_with_set = false;
|
||||||
|
} else {
|
||||||
|
cmd("list")("APPEND", custom_target_depends_var, depends);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto append_keyword = [](std::vector<RawArg> &args, const std::string &keyword) {
|
||||||
|
args.emplace_back(keyword);
|
||||||
|
};
|
||||||
|
auto append_value = [](std::vector<RawArg> &args, const std::string &value) {
|
||||||
|
args.emplace_back(Command::quote(value));
|
||||||
|
};
|
||||||
|
auto append_values = [&](std::vector<RawArg> &args, const std::vector<std::string> &values) {
|
||||||
|
for (const auto &value : values) {
|
||||||
|
append_value(args, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto append_commands = [&](std::vector<RawArg> &args, const std::vector<std::vector<std::string>> &commands) {
|
||||||
|
for (const auto &command_args : commands) {
|
||||||
|
append_keyword(args, "COMMAND");
|
||||||
|
append_values(args, command_args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto emit_custom_command = [&](const parser::Target::CustomCommand &custom_command) {
|
||||||
|
std::vector<RawArg> args;
|
||||||
|
if (custom_command.is_output_form()) {
|
||||||
|
append_keyword(args, "OUTPUT");
|
||||||
|
append_values(args, custom_command.outputs);
|
||||||
|
append_commands(args, custom_command.commands);
|
||||||
|
if (custom_command.has_main_dependency) {
|
||||||
|
append_keyword(args, "MAIN_DEPENDENCY");
|
||||||
|
append_value(args, custom_command.main_dependency);
|
||||||
|
}
|
||||||
|
if (!custom_command.depends.empty()) {
|
||||||
|
append_keyword(args, "DEPENDS");
|
||||||
|
append_values(args, custom_command.depends);
|
||||||
|
}
|
||||||
|
if (!custom_command.byproducts.empty()) {
|
||||||
|
append_keyword(args, "BYPRODUCTS");
|
||||||
|
append_values(args, custom_command.byproducts);
|
||||||
|
}
|
||||||
|
if (!custom_command.implicit_depends.empty()) {
|
||||||
|
append_keyword(args, "IMPLICIT_DEPENDS");
|
||||||
|
for (const auto &implicit_dependency : custom_command.implicit_depends) {
|
||||||
|
append_value(args, implicit_dependency.language);
|
||||||
|
append_value(args, implicit_dependency.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (custom_command.has_working_directory) {
|
||||||
|
append_keyword(args, "WORKING_DIRECTORY");
|
||||||
|
append_value(args, custom_command.working_directory);
|
||||||
|
}
|
||||||
|
if (custom_command.has_comment) {
|
||||||
|
append_keyword(args, "COMMENT");
|
||||||
|
append_value(args, custom_command.comment);
|
||||||
|
}
|
||||||
|
if (custom_command.has_depfile) {
|
||||||
|
append_keyword(args, "DEPFILE");
|
||||||
|
append_value(args, custom_command.depfile);
|
||||||
|
}
|
||||||
|
if (custom_command.has_job_pool) {
|
||||||
|
append_keyword(args, "JOB_POOL");
|
||||||
|
append_value(args, custom_command.job_pool);
|
||||||
|
}
|
||||||
|
if (custom_command.has_job_server_aware) {
|
||||||
|
append_keyword(args, "JOB_SERVER_AWARE");
|
||||||
|
append_value(args, custom_command.job_server_aware ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
if (custom_command.has_verbatim && custom_command.verbatim) {
|
||||||
|
append_keyword(args, "VERBATIM");
|
||||||
|
}
|
||||||
|
if (custom_command.has_append && custom_command.append) {
|
||||||
|
append_keyword(args, "APPEND");
|
||||||
|
}
|
||||||
|
if (custom_command.has_uses_terminal && custom_command.uses_terminal) {
|
||||||
|
append_keyword(args, "USES_TERMINAL");
|
||||||
|
}
|
||||||
|
if (custom_command.has_codegen && custom_command.codegen) {
|
||||||
|
append_keyword(args, "CODEGEN");
|
||||||
|
}
|
||||||
|
if (custom_command.has_command_expand_lists && custom_command.command_expand_lists) {
|
||||||
|
append_keyword(args, "COMMAND_EXPAND_LISTS");
|
||||||
|
}
|
||||||
|
if (custom_command.has_depends_explicit_only && custom_command.depends_explicit_only) {
|
||||||
|
append_keyword(args, "DEPENDS_EXPLICIT_ONLY");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
append_keyword(args, "TARGET");
|
||||||
|
append_value(args, target.name);
|
||||||
|
append_keyword(args, custom_command.build_event);
|
||||||
|
append_commands(args, custom_command.commands);
|
||||||
|
if (!custom_command.byproducts.empty()) {
|
||||||
|
append_keyword(args, "BYPRODUCTS");
|
||||||
|
append_values(args, custom_command.byproducts);
|
||||||
|
}
|
||||||
|
if (custom_command.has_working_directory) {
|
||||||
|
append_keyword(args, "WORKING_DIRECTORY");
|
||||||
|
append_value(args, custom_command.working_directory);
|
||||||
|
}
|
||||||
|
if (custom_command.has_comment) {
|
||||||
|
append_keyword(args, "COMMENT");
|
||||||
|
append_value(args, custom_command.comment);
|
||||||
|
}
|
||||||
|
if (custom_command.has_verbatim && custom_command.verbatim) {
|
||||||
|
append_keyword(args, "VERBATIM");
|
||||||
|
}
|
||||||
|
if (custom_command.has_command_expand_lists && custom_command.command_expand_lists) {
|
||||||
|
append_keyword(args, "COMMAND_EXPAND_LISTS");
|
||||||
|
}
|
||||||
|
if (custom_command.has_uses_terminal && custom_command.uses_terminal) {
|
||||||
|
append_keyword(args, "USES_TERMINAL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd("add_custom_command")(args).endl();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &custom_command : custom_commands) {
|
||||||
|
if (custom_command.is_output_form()) {
|
||||||
|
ConditionScope custom_scope(gen, custom_command.condition);
|
||||||
|
emit_custom_command(custom_command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle custom add commands from templates.
|
// Handle custom add commands from templates.
|
||||||
if (tmplate != nullptr && !tmplate->add_function.empty()) {
|
if (tmplate != nullptr && !tmplate->add_function.empty()) {
|
||||||
add_command = tmplate->add_function;
|
add_command = tmplate->add_function;
|
||||||
@@ -1409,6 +1686,51 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
"${" + sources_var + "}");
|
"${" + sources_var + "}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (is_custom_target) {
|
||||||
|
std::vector<RawArg> args;
|
||||||
|
append_value(args, target.name);
|
||||||
|
if (custom_target.has_all && custom_target.all) {
|
||||||
|
append_keyword(args, "ALL");
|
||||||
|
}
|
||||||
|
append_commands(args, custom_target.commands);
|
||||||
|
if (has_custom_target_depends) {
|
||||||
|
append_keyword(args, "DEPENDS");
|
||||||
|
append_value(args, "${" + custom_target_depends_var + "}");
|
||||||
|
}
|
||||||
|
if (!custom_target.byproducts.empty()) {
|
||||||
|
append_keyword(args, "BYPRODUCTS");
|
||||||
|
append_values(args, custom_target.byproducts);
|
||||||
|
}
|
||||||
|
if (custom_target.has_working_directory) {
|
||||||
|
append_keyword(args, "WORKING_DIRECTORY");
|
||||||
|
append_value(args, custom_target.working_directory);
|
||||||
|
}
|
||||||
|
if (custom_target.has_comment) {
|
||||||
|
append_keyword(args, "COMMENT");
|
||||||
|
append_value(args, custom_target.comment);
|
||||||
|
}
|
||||||
|
if (custom_target.has_job_pool) {
|
||||||
|
append_keyword(args, "JOB_POOL");
|
||||||
|
append_value(args, custom_target.job_pool);
|
||||||
|
}
|
||||||
|
if (custom_target.has_job_server_aware) {
|
||||||
|
append_keyword(args, "JOB_SERVER_AWARE");
|
||||||
|
append_value(args, custom_target.job_server_aware ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
if (custom_target.has_verbatim && custom_target.verbatim) {
|
||||||
|
append_keyword(args, "VERBATIM");
|
||||||
|
}
|
||||||
|
if (custom_target.has_uses_terminal && custom_target.uses_terminal) {
|
||||||
|
append_keyword(args, "USES_TERMINAL");
|
||||||
|
}
|
||||||
|
if (custom_target.has_command_expand_lists && custom_target.command_expand_lists) {
|
||||||
|
append_keyword(args, "COMMAND_EXPAND_LISTS");
|
||||||
|
}
|
||||||
|
if (has_sources) {
|
||||||
|
append_keyword(args, "SOURCES");
|
||||||
|
append_value(args, "${" + sources_var + "}");
|
||||||
|
}
|
||||||
|
cmd("add_custom_target")(args).endl();
|
||||||
} else {
|
} else {
|
||||||
cmd(add_command)(target.name, target_type_string).endl();
|
cmd(add_command)(target.name, target_type_string).endl();
|
||||||
if (has_sources) {
|
if (has_sources) {
|
||||||
@@ -1416,6 +1738,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &custom_command : custom_commands) {
|
||||||
|
if (custom_command.is_target_form()) {
|
||||||
|
ConditionScope custom_scope(gen, custom_command.condition);
|
||||||
|
emit_custom_command(custom_command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: support sources from other directories
|
// TODO: support sources from other directories
|
||||||
if (has_sources) {
|
if (has_sources) {
|
||||||
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + sources_var + "}").endl();
|
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + sources_var + "}").endl();
|
||||||
@@ -1452,29 +1781,31 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
auto gen_target_cmds = [&](const parser::Target &t) {
|
auto gen_target_cmds = [&](const parser::Target &t) {
|
||||||
target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
|
if (!is_custom_target) {
|
||||||
target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
|
target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
|
||||||
|
target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_compile_features", t.compile_features, target_scope);
|
target_cmd("target_compile_features", t.compile_features, target_scope);
|
||||||
target_cmd("target_compile_features", t.private_compile_features, "PRIVATE");
|
target_cmd("target_compile_features", t.private_compile_features, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_compile_options", t.compile_options, target_scope);
|
target_cmd("target_compile_options", t.compile_options, target_scope);
|
||||||
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_include_directories", t.include_directories, target_scope);
|
target_cmd("target_include_directories", t.include_directories, target_scope);
|
||||||
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_link_directories", t.link_directories, target_scope);
|
target_cmd("target_link_directories", t.link_directories, target_scope);
|
||||||
target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
|
target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
|
||||||
|
|
||||||
link_cmd("target_link_libraries", t.link_libraries, target_scope);
|
link_cmd("target_link_libraries", t.link_libraries, target_scope);
|
||||||
link_cmd("target_link_libraries", t.private_link_libraries, "PRIVATE");
|
link_cmd("target_link_libraries", t.private_link_libraries, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_link_options", t.link_options, target_scope);
|
target_cmd("target_link_options", t.link_options, target_scope);
|
||||||
target_cmd("target_link_options", t.private_link_options, "PRIVATE");
|
target_cmd("target_link_options", t.private_link_options, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
|
target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
|
||||||
target_cmd("target_precompile_headers", t.private_precompile_headers, "PRIVATE");
|
target_cmd("target_precompile_headers", t.private_precompile_headers, "PRIVATE");
|
||||||
|
}
|
||||||
|
|
||||||
link_cmd("add_dependencies", t.dependencies, "");
|
link_cmd("add_dependencies", t.dependencies, "");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "project_parser.hpp"
|
#include "project_parser.hpp"
|
||||||
|
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
|
#include <cctype>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <toml.hpp>
|
#include <toml.hpp>
|
||||||
@@ -209,6 +210,105 @@ class TomlCheckerRoot {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::vector<std::string> parse_string_array(const TomlBasicValue &value, const toml::key &key) {
|
||||||
|
if (!value.is_array()) {
|
||||||
|
throw_key_error("Expected an array", key, value);
|
||||||
|
}
|
||||||
|
std::vector<std::string> values;
|
||||||
|
for (const auto &entry : value.as_array()) {
|
||||||
|
if (!entry.is_string()) {
|
||||||
|
throw_key_error("Expected an array of strings", key, entry);
|
||||||
|
}
|
||||||
|
values.push_back(entry.as_string());
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::vector<std::string>> parse_command_arguments(const TomlBasicValue &value, const toml::key &key) {
|
||||||
|
if (!value.is_array()) {
|
||||||
|
throw_key_error("Expected an array", key, value);
|
||||||
|
}
|
||||||
|
const auto &array = value.as_array();
|
||||||
|
if (array.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool all_strings = true;
|
||||||
|
bool all_arrays = true;
|
||||||
|
for (const auto &entry : array) {
|
||||||
|
if (!entry.is_string()) {
|
||||||
|
all_strings = false;
|
||||||
|
}
|
||||||
|
if (!entry.is_array()) {
|
||||||
|
all_arrays = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<std::string>> commands;
|
||||||
|
if (all_strings) {
|
||||||
|
commands.push_back(parse_string_array(value, key));
|
||||||
|
if (commands[0].empty()) {
|
||||||
|
throw_key_error("Command arrays cannot be empty", key, value);
|
||||||
|
}
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!all_arrays) {
|
||||||
|
throw_key_error("Expected an array of command arrays", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &entry : array) {
|
||||||
|
auto command = parse_string_array(entry, key);
|
||||||
|
if (command.empty()) {
|
||||||
|
throw_key_error("Command arrays cannot be empty", key, entry);
|
||||||
|
}
|
||||||
|
commands.push_back(std::move(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<Target::CustomCommand::ImplicitDependency> parse_implicit_dependencies(const TomlBasicValue &value, const toml::key &key) {
|
||||||
|
if (!value.is_array()) {
|
||||||
|
throw_key_error("Expected an array", key, value);
|
||||||
|
}
|
||||||
|
std::vector<Target::CustomCommand::ImplicitDependency> implicit_dependencies;
|
||||||
|
for (const auto &entry : value.as_array()) {
|
||||||
|
auto values = parse_string_array(entry, key);
|
||||||
|
if (values.size() != 2) {
|
||||||
|
throw_key_error("Each implicit dependency must have exactly two strings: [language, file]", key, entry);
|
||||||
|
}
|
||||||
|
Target::CustomCommand::ImplicitDependency implicit_dependency;
|
||||||
|
implicit_dependency.language = std::move(values[0]);
|
||||||
|
implicit_dependency.file = std::move(values[1]);
|
||||||
|
implicit_dependencies.push_back(std::move(implicit_dependency));
|
||||||
|
}
|
||||||
|
return implicit_dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string normalize_build_event(std::string build_event) {
|
||||||
|
for (auto &ch : build_event) {
|
||||||
|
if (ch == '-') {
|
||||||
|
ch = '_';
|
||||||
|
} else {
|
||||||
|
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return build_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TargetType resolve_target_type(const Target &target, const std::vector<Template> &templates) {
|
||||||
|
if (target.type != target_template) {
|
||||||
|
return target.type;
|
||||||
|
}
|
||||||
|
for (const auto &tmplate : templates) {
|
||||||
|
if (target.type_name == tmplate.outline.name) {
|
||||||
|
return tmplate.outline.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target.type;
|
||||||
|
}
|
||||||
|
|
||||||
Project::Project(const Project *parent, const std::string &path, bool build) : parent(parent) {
|
Project::Project(const Project *parent, const std::string &path, bool build) : parent(parent) {
|
||||||
const auto toml_path = fs::path(path) / "cmake.toml";
|
const auto toml_path = fs::path(path) / "cmake.toml";
|
||||||
if (!fs::exists(toml_path)) {
|
if (!fs::exists(toml_path)) {
|
||||||
@@ -505,7 +605,37 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
|
|||||||
"system", "");
|
"system", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse options table - these are CACHE variables set before FetchContent
|
||||||
|
if (c.contains("options")) {
|
||||||
|
c.visit("options");
|
||||||
|
const auto &opts = toml::find(itr.second, "options").as_table();
|
||||||
|
for (const auto &optItr : opts) {
|
||||||
|
ContentOption opt;
|
||||||
|
if (optItr.second.is_boolean()) {
|
||||||
|
opt.value = optItr.second.as_boolean();
|
||||||
|
} else if (optItr.second.is_array()) {
|
||||||
|
// Arrays become semicolon-separated lists
|
||||||
|
std::string list_value;
|
||||||
|
for (const auto &list_val : optItr.second.as_array()) {
|
||||||
|
if (!list_value.empty()) {
|
||||||
|
list_value += ';';
|
||||||
|
}
|
||||||
|
list_value += list_val.as_string();
|
||||||
|
}
|
||||||
|
opt.value = list_value;
|
||||||
|
} else {
|
||||||
|
opt.value = optItr.second.as_string();
|
||||||
|
}
|
||||||
|
content.options.emplace(optItr.first, opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &argItr : itr.second.as_table()) {
|
for (const auto &argItr : itr.second.as_table()) {
|
||||||
|
// Skip keys that were already handled (like "options")
|
||||||
|
if (c.visisted(argItr.first)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
std::string value;
|
std::string value;
|
||||||
if (argItr.second.is_array()) {
|
if (argItr.second.is_array()) {
|
||||||
for (const auto &list_val : argItr.second.as_array()) {
|
for (const auto &list_val : argItr.second.as_array()) {
|
||||||
@@ -698,6 +828,253 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
|
|||||||
|
|
||||||
t.optional("dependencies", target.dependencies);
|
t.optional("dependencies", target.dependencies);
|
||||||
|
|
||||||
|
const auto resolved_target_type = resolve_target_type(target, templates);
|
||||||
|
|
||||||
|
if (t.contains("all")) {
|
||||||
|
target.custom_target.has_all = true;
|
||||||
|
target.custom_target.all = t.find("all").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("command")) {
|
||||||
|
target.custom_target.has_command = true;
|
||||||
|
auto commands = parse_command_arguments(t.find("command"), "command");
|
||||||
|
for (auto &command : commands) {
|
||||||
|
target.custom_target.commands.push_back(std::move(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("commands")) {
|
||||||
|
target.custom_target.has_commands = true;
|
||||||
|
auto commands = parse_command_arguments(t.find("commands"), "commands");
|
||||||
|
for (auto &command : commands) {
|
||||||
|
target.custom_target.commands.push_back(std::move(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.optional("depends", target.custom_target.depends);
|
||||||
|
t.optional("byproducts", target.custom_target.byproducts);
|
||||||
|
|
||||||
|
if (t.contains("working-directory")) {
|
||||||
|
target.custom_target.has_working_directory = true;
|
||||||
|
target.custom_target.working_directory = t.find("working-directory").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("comment")) {
|
||||||
|
target.custom_target.has_comment = true;
|
||||||
|
target.custom_target.comment = t.find("comment").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("job-pool")) {
|
||||||
|
target.custom_target.has_job_pool = true;
|
||||||
|
target.custom_target.job_pool = t.find("job-pool").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("job-server-aware")) {
|
||||||
|
target.custom_target.has_job_server_aware = true;
|
||||||
|
target.custom_target.job_server_aware = t.find("job-server-aware").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("verbatim")) {
|
||||||
|
target.custom_target.has_verbatim = true;
|
||||||
|
target.custom_target.verbatim = t.find("verbatim").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("uses-terminal")) {
|
||||||
|
target.custom_target.has_uses_terminal = true;
|
||||||
|
target.custom_target.uses_terminal = t.find("uses-terminal").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("command-expand-lists")) {
|
||||||
|
target.custom_target.has_command_expand_lists = true;
|
||||||
|
target.custom_target.command_expand_lists = t.find("command-expand-lists").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains("custom-command")) {
|
||||||
|
const auto &custom_commands = t.find("custom-command");
|
||||||
|
if (!custom_commands.is_array()) {
|
||||||
|
throw_key_error("Expected an array of tables", "custom-command", custom_commands);
|
||||||
|
}
|
||||||
|
for (const auto &custom_command_value : custom_commands.as_array()) {
|
||||||
|
if (!custom_command_value.is_table()) {
|
||||||
|
throw_key_error("Expected an array of tables", "custom-command", custom_command_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &custom = checker.create(custom_command_value);
|
||||||
|
Target::CustomCommand custom_command;
|
||||||
|
|
||||||
|
custom.optional("condition", custom_command.condition);
|
||||||
|
|
||||||
|
if (custom.contains("command")) {
|
||||||
|
auto commands = parse_command_arguments(custom.find("command"), "command");
|
||||||
|
for (auto &command : commands) {
|
||||||
|
custom_command.commands.push_back(std::move(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("commands")) {
|
||||||
|
auto commands = parse_command_arguments(custom.find("commands"), "commands");
|
||||||
|
for (auto &command : commands) {
|
||||||
|
custom_command.commands.push_back(std::move(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
custom.optional("outputs", custom_command.outputs);
|
||||||
|
|
||||||
|
if (custom.contains("build-event")) {
|
||||||
|
custom_command.build_event = normalize_build_event(custom.find("build-event").as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("append")) {
|
||||||
|
custom_command.has_append = true;
|
||||||
|
custom_command.append = custom.find("append").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("main-dependency")) {
|
||||||
|
custom_command.has_main_dependency = true;
|
||||||
|
custom_command.main_dependency = custom.find("main-dependency").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
custom.optional("depends", custom_command.depends);
|
||||||
|
custom.optional("byproducts", custom_command.byproducts);
|
||||||
|
|
||||||
|
if (custom.contains("implicit-depends")) {
|
||||||
|
custom_command.implicit_depends = parse_implicit_dependencies(custom.find("implicit-depends"), "implicit-depends");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("working-directory")) {
|
||||||
|
custom_command.has_working_directory = true;
|
||||||
|
custom_command.working_directory = custom.find("working-directory").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("comment")) {
|
||||||
|
custom_command.has_comment = true;
|
||||||
|
custom_command.comment = custom.find("comment").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("depfile")) {
|
||||||
|
custom_command.has_depfile = true;
|
||||||
|
custom_command.depfile = custom.find("depfile").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("job-pool")) {
|
||||||
|
custom_command.has_job_pool = true;
|
||||||
|
custom_command.job_pool = custom.find("job-pool").as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("job-server-aware")) {
|
||||||
|
custom_command.has_job_server_aware = true;
|
||||||
|
custom_command.job_server_aware = custom.find("job-server-aware").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("verbatim")) {
|
||||||
|
custom_command.has_verbatim = true;
|
||||||
|
custom_command.verbatim = custom.find("verbatim").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("uses-terminal")) {
|
||||||
|
custom_command.has_uses_terminal = true;
|
||||||
|
custom_command.uses_terminal = custom.find("uses-terminal").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("codegen")) {
|
||||||
|
custom_command.has_codegen = true;
|
||||||
|
custom_command.codegen = custom.find("codegen").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("command-expand-lists")) {
|
||||||
|
custom_command.has_command_expand_lists = true;
|
||||||
|
custom_command.command_expand_lists = custom.find("command-expand-lists").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom.contains("depends-explicit-only")) {
|
||||||
|
custom_command.has_depends_explicit_only = true;
|
||||||
|
custom_command.depends_explicit_only = custom.find("depends-explicit-only").as_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_command.is_output_form() == custom_command.is_target_form()) {
|
||||||
|
throw_key_error("Specify exactly one of outputs or build-event", "custom-command", custom_command_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_command.is_target_form()) {
|
||||||
|
if (custom_command.build_event != "PRE_BUILD" && custom_command.build_event != "PRE_LINK" &&
|
||||||
|
custom_command.build_event != "POST_BUILD") {
|
||||||
|
throw_key_error("build-event must be one of: pre-build, pre-link, post-build", "build-event", custom.find("build-event"));
|
||||||
|
}
|
||||||
|
if (resolved_target_type == target_interface) {
|
||||||
|
throw_key_error("build-event cannot be used with type = \"interface\"", "build-event", custom.find("build-event"));
|
||||||
|
}
|
||||||
|
if (resolved_target_type == target_custom && custom_command.build_event == "PRE_LINK") {
|
||||||
|
throw_key_error("build-event = \"pre-link\" cannot be used with type = \"custom\"", "build-event",
|
||||||
|
custom.find("build-event"));
|
||||||
|
}
|
||||||
|
if (custom_command.has_append || custom_command.has_main_dependency || !custom_command.depends.empty() ||
|
||||||
|
!custom_command.implicit_depends.empty() || custom_command.has_depfile || custom_command.has_job_pool ||
|
||||||
|
custom_command.has_job_server_aware || custom_command.has_codegen || custom_command.has_depends_explicit_only) {
|
||||||
|
throw_key_error("Unsupported option for TARGET custom commands", "custom-command", custom_command_value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (custom_command.has_depfile && !custom_command.implicit_depends.empty()) {
|
||||||
|
throw_key_error("depfile cannot be used with implicit-depends", "depfile", custom.find("depfile"));
|
||||||
|
}
|
||||||
|
if (custom_command.has_job_pool && custom_command.has_uses_terminal && custom_command.uses_terminal) {
|
||||||
|
throw_key_error("job-pool cannot be used with uses-terminal", "job-pool", custom.find("job-pool"));
|
||||||
|
}
|
||||||
|
if (custom_command.append &&
|
||||||
|
(custom_command.has_depfile || custom_command.has_job_pool || custom_command.has_job_server_aware ||
|
||||||
|
custom_command.has_codegen || custom_command.has_command_expand_lists || custom_command.has_uses_terminal ||
|
||||||
|
custom_command.has_verbatim || custom_command.has_depends_explicit_only || !custom_command.byproducts.empty())) {
|
||||||
|
throw_key_error("append cannot be used with depfile, job-pool, job-server-aware, codegen, command-expand-lists, "
|
||||||
|
"uses-terminal, verbatim, depends-explicit-only, or byproducts",
|
||||||
|
"append", custom.find("append"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_command.is_output_form() && custom_command.append && custom_command.commands.empty() && custom_command.depends.empty()) {
|
||||||
|
throw_key_error("append requires at least one command or depends", "append", custom.find("append"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_command.commands.empty() &&
|
||||||
|
!(custom_command.is_output_form() && custom_command.append && !custom_command.depends.empty())) {
|
||||||
|
throw_key_error("At least one command is required", "custom-command", custom_command_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.custom_commands.push_back(std::move(custom_command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolved_target_type == target_custom && target.custom_target.has_job_pool && target.custom_target.has_uses_terminal &&
|
||||||
|
target.custom_target.uses_terminal) {
|
||||||
|
throw_key_error("job-pool cannot be used with uses-terminal", "job-pool", t.find("job-pool"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolved_target_type != target_custom && !target.custom_target.empty()) {
|
||||||
|
const char *custom_key = "all";
|
||||||
|
if (target.custom_target.has_command) {
|
||||||
|
custom_key = "command";
|
||||||
|
} else if (target.custom_target.has_commands) {
|
||||||
|
custom_key = "commands";
|
||||||
|
} else if (!target.custom_target.depends.empty()) {
|
||||||
|
custom_key = "depends";
|
||||||
|
} else if (!target.custom_target.byproducts.empty()) {
|
||||||
|
custom_key = "byproducts";
|
||||||
|
} else if (target.custom_target.has_working_directory) {
|
||||||
|
custom_key = "working-directory";
|
||||||
|
} else if (target.custom_target.has_comment) {
|
||||||
|
custom_key = "comment";
|
||||||
|
} else if (target.custom_target.has_job_pool) {
|
||||||
|
custom_key = "job-pool";
|
||||||
|
} else if (target.custom_target.has_job_server_aware) {
|
||||||
|
custom_key = "job-server-aware";
|
||||||
|
} else if (target.custom_target.has_verbatim) {
|
||||||
|
custom_key = "verbatim";
|
||||||
|
} else if (target.custom_target.has_uses_terminal) {
|
||||||
|
custom_key = "uses-terminal";
|
||||||
|
} else if (target.custom_target.has_command_expand_lists) {
|
||||||
|
custom_key = "command-expand-lists";
|
||||||
|
}
|
||||||
|
throw_key_error("Custom target options can only be used with type = \"custom\"", custom_key, t.find(custom_key));
|
||||||
|
}
|
||||||
|
|
||||||
Condition<std::string> msvc_runtime;
|
Condition<std::string> msvc_runtime;
|
||||||
t.optional("msvc-runtime", msvc_runtime);
|
t.optional("msvc-runtime", msvc_runtime);
|
||||||
for (const auto &cond_itr : msvc_runtime) {
|
for (const auto &cond_itr : msvc_runtime) {
|
||||||
|
|||||||
Generated
+30
@@ -131,3 +131,33 @@ add_test(
|
|||||||
"$<TARGET_FILE:cmkr>"
|
"$<TARGET_FILE:cmkr>"
|
||||||
build
|
build
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME
|
||||||
|
custom-command
|
||||||
|
WORKING_DIRECTORY
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/custom-command"
|
||||||
|
COMMAND
|
||||||
|
"$<TARGET_FILE:cmkr>"
|
||||||
|
build
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME
|
||||||
|
generator-executable
|
||||||
|
WORKING_DIRECTORY
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/generator-executable"
|
||||||
|
COMMAND
|
||||||
|
"$<TARGET_FILE:cmkr>"
|
||||||
|
build
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME
|
||||||
|
protobuf
|
||||||
|
WORKING_DIRECTORY
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/protobuf"
|
||||||
|
COMMAND
|
||||||
|
"$<TARGET_FILE:cmkr>"
|
||||||
|
build
|
||||||
|
)
|
||||||
|
|||||||
@@ -71,3 +71,21 @@ name = "objective-c"
|
|||||||
working-directory = "objective-c"
|
working-directory = "objective-c"
|
||||||
command = "$<TARGET_FILE:cmkr>"
|
command = "$<TARGET_FILE:cmkr>"
|
||||||
arguments = ["build"]
|
arguments = ["build"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
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"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "protobuf"
|
||||||
|
working-directory = "protobuf"
|
||||||
|
command = "$<TARGET_FILE:cmkr>"
|
||||||
|
arguments = ["build"]
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# This test demonstrates add_custom_command and add_custom_target support in cmkr.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
# Relative outputs follow CMake and are interpreted from the binary directory.
|
||||||
|
# The outputs are automatically added as sources to the target.
|
||||||
|
[[target.custom-command.custom-command]]
|
||||||
|
outputs = ["generated/generated.cpp"]
|
||||||
|
byproducts = ["generated/generated.hpp"]
|
||||||
|
depends = ["cmake/generate_source.cmake"]
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-DOUTPUT_CPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.cpp",
|
||||||
|
"-DOUTPUT_HPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.hpp",
|
||||||
|
"-P",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate_source.cmake",
|
||||||
|
]
|
||||||
|
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 = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-E",
|
||||||
|
"touch",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp",
|
||||||
|
]
|
||||||
|
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
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-E",
|
||||||
|
"touch",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp",
|
||||||
|
]
|
||||||
|
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp"]
|
||||||
|
comment = "Run custom target"
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Template-based custom target
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Custom target options should also work when the target type comes from a template.
|
||||||
|
[template.generated-custom-target]
|
||||||
|
type = "custom"
|
||||||
|
all = true
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
[target.custom-codegen-template]
|
||||||
|
type = "generated-custom-target"
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-DINPUT=${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||||
|
"-P",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_file_exists.cmake",
|
||||||
|
]
|
||||||
|
comment = "Verify the generated stamp exists"
|
||||||
|
|
||||||
|
# The output-form custom command should automatically become a dependency of the
|
||||||
|
# custom target, so the verification command sees the stamp file.
|
||||||
|
[[target.custom-codegen-template.custom-command]]
|
||||||
|
outputs = ["template-custom-target.stamp"]
|
||||||
|
command = [
|
||||||
|
"${CMAKE_COMMAND}",
|
||||||
|
"-E",
|
||||||
|
"touch",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||||
|
]
|
||||||
|
comment = "Create a stamp file for the template-based custom target"
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
# A target-level all = false should override the template's all = true.
|
||||||
|
# If this target is incorrectly included in the default build, the build fails.
|
||||||
|
[target.custom-codegen-template-disabled]
|
||||||
|
type = "generated-custom-target"
|
||||||
|
all = false
|
||||||
|
command = ["${CMAKE_COMMAND}", "-E", "false"]
|
||||||
|
comment = "This target must not run as part of the default build"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
if(NOT DEFINED OUTPUT_CPP)
|
||||||
|
message(FATAL_ERROR "OUTPUT_CPP is required")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED OUTPUT_HPP)
|
||||||
|
message(FATAL_ERROR "OUTPUT_HPP is required")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_filename_component(_output_dir "${OUTPUT_CPP}" DIRECTORY)
|
||||||
|
file(MAKE_DIRECTORY "${_output_dir}")
|
||||||
|
|
||||||
|
file(WRITE "${OUTPUT_HPP}" "#pragma once\n#define GENERATED_VALUE 42\n")
|
||||||
|
file(WRITE "${OUTPUT_CPP}" "#include \"generated.hpp\"\nint generated_value() { return GENERATED_VALUE; }\n")
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
if(NOT DEFINED INPUT)
|
||||||
|
message(FATAL_ERROR "INPUT is required")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${INPUT}")
|
||||||
|
message(FATAL_ERROR "Expected file not found: ${INPUT}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Verified file exists: ${INPUT}")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Hello, world!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#include "generated.hpp"
|
||||||
|
|
||||||
|
int generated_value();
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return generated_value() == GENERATED_VALUE ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# 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"]
|
||||||
|
|
||||||
|
# Create the generated directory before the generator runs.
|
||||||
|
cmake-before = """
|
||||||
|
file(MAKE_DIRECTORY "${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,44 @@
|
|||||||
|
// 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];
|
||||||
|
|
||||||
|
// The example ensures the output directory exists before invoking the generator.
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
*.bin
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# This test demonstrates using protobuf with cmkr for code generation.
|
||||||
|
#
|
||||||
|
# The example:
|
||||||
|
# 1. Fetches protobuf from GitHub using FetchContent
|
||||||
|
# 2. Uses a custom command to generate C++ sources from a .proto file
|
||||||
|
# 3. Links an executable against the generated sources and protobuf library
|
||||||
|
# 4. Serializes and deserializes a simple message
|
||||||
|
#
|
||||||
|
# Note: protobuf v21.x is used because newer versions (v22+) require abseil-cpp
|
||||||
|
# as a dependency, which significantly complicates the build.
|
||||||
|
|
||||||
|
[cmake]
|
||||||
|
version = "3.18...3.31"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "protobuf-example"
|
||||||
|
description = "Demonstrates protobuf integration with cmkr custom commands"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Fetch protobuf from GitHub
|
||||||
|
# Using v21.12 (last version before abseil-cpp became required)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[fetch-content.protobuf]
|
||||||
|
git = "https://github.com/protocolbuffers/protobuf"
|
||||||
|
tag = "v21.12"
|
||||||
|
options = {
|
||||||
|
protobuf_BUILD_TESTS = false,
|
||||||
|
protobuf_BUILD_EXAMPLES = false,
|
||||||
|
protobuf_BUILD_LIBPROTOC = false,
|
||||||
|
protobuf_BUILD_SHARED_LIBS = false,
|
||||||
|
protobuf_MSVC_STATIC_RUNTIME = false,
|
||||||
|
SKIP_INSTALL_ALL = true,
|
||||||
|
protobuf_WITH_ZLIB = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Main executable using protobuf
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[target.protobuf_example]
|
||||||
|
type = "executable"
|
||||||
|
sources = [
|
||||||
|
"src/main.cpp",
|
||||||
|
]
|
||||||
|
include-directories = [
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||||
|
]
|
||||||
|
link-libraries = ["protobuf::libprotobuf"]
|
||||||
|
compile-features = ["cxx_std_17"]
|
||||||
|
|
||||||
|
# Create the generated directory before running protoc
|
||||||
|
cmake-before = """
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Custom command to generate C++ sources from .proto file
|
||||||
|
# This uses protoc from the fetched protobuf repository
|
||||||
|
[[target.protobuf_example.custom-command]]
|
||||||
|
outputs = [
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.h",
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.cc",
|
||||||
|
]
|
||||||
|
depends = [
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||||
|
"protobuf::protoc", # Ensure protoc is built first
|
||||||
|
]
|
||||||
|
command = [
|
||||||
|
"$<TARGET_FILE:protobuf::protoc>",
|
||||||
|
"--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||||
|
"-I${CMAKE_CURRENT_SOURCE_DIR}/proto",
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||||
|
]
|
||||||
|
comment = "Generate C++ sources from addressbook.proto"
|
||||||
|
verbatim = true
|
||||||
|
|
||||||
|
# Post-build: run the executable to verify it works
|
||||||
|
[[target.protobuf_example.custom-command]]
|
||||||
|
build-event = "post-build"
|
||||||
|
command = ["$<TARGET_FILE:protobuf_example>"]
|
||||||
|
comment = "Run protobuf_example to verify serialization/deserialization works"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// A simple address book example demonstrating protobuf serialization.
|
||||||
|
// Based on the official protobuf tutorial example.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package tutorial;
|
||||||
|
|
||||||
|
// A person with basic contact information
|
||||||
|
message Person {
|
||||||
|
string name = 1;
|
||||||
|
int32 id = 2;
|
||||||
|
string email = 3;
|
||||||
|
|
||||||
|
enum PhoneType {
|
||||||
|
MOBILE = 0;
|
||||||
|
HOME = 1;
|
||||||
|
WORK = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PhoneNumber {
|
||||||
|
string number = 1;
|
||||||
|
PhoneType type = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated PhoneNumber phones = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An address book containing multiple people
|
||||||
|
message AddressBook {
|
||||||
|
repeated Person people = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
// Protobuf serialization/deserialization example
|
||||||
|
// This demonstrates using protobuf with cmkr for code generation
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include "addressbook.pb.h"
|
||||||
|
|
||||||
|
// Creates a sample address book with some people
|
||||||
|
void create_address_book(tutorial::AddressBook &book) {
|
||||||
|
// Add first person
|
||||||
|
tutorial::Person *person1 = book.add_people();
|
||||||
|
person1->set_name("Alice Smith");
|
||||||
|
person1->set_id(1);
|
||||||
|
person1->set_email("alice@example.com");
|
||||||
|
|
||||||
|
// Add phone numbers for Alice
|
||||||
|
tutorial::Person::PhoneNumber *phone1 = person1->add_phones();
|
||||||
|
phone1->set_number("555-1234");
|
||||||
|
phone1->set_type(tutorial::Person::HOME);
|
||||||
|
|
||||||
|
tutorial::Person::PhoneNumber *phone2 = person1->add_phones();
|
||||||
|
phone2->set_number("555-5678");
|
||||||
|
phone2->set_type(tutorial::Person::WORK);
|
||||||
|
|
||||||
|
// Add second person
|
||||||
|
tutorial::Person *person2 = book.add_people();
|
||||||
|
person2->set_name("Bob Johnson");
|
||||||
|
person2->set_id(2);
|
||||||
|
person2->set_email("bob@example.com");
|
||||||
|
|
||||||
|
tutorial::Person::PhoneNumber *phone3 = person2->add_phones();
|
||||||
|
phone3->set_number("555-9999");
|
||||||
|
phone3->set_type(tutorial::Person::MOBILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the contents of an address book
|
||||||
|
void print_address_book(const tutorial::AddressBook &book) {
|
||||||
|
std::cout << "Address Book (" << book.people_size() << " people):\n";
|
||||||
|
std::cout << "==========================================\n\n";
|
||||||
|
|
||||||
|
for (int i = 0; i < book.people_size(); ++i) {
|
||||||
|
const tutorial::Person &person = book.people(i);
|
||||||
|
|
||||||
|
std::cout << "Person ID: " << person.id() << "\n";
|
||||||
|
std::cout << " Name: " << person.name() << "\n";
|
||||||
|
std::cout << " Email: " << person.email() << "\n";
|
||||||
|
|
||||||
|
for (int j = 0; j < person.phones_size(); ++j) {
|
||||||
|
const tutorial::Person::PhoneNumber &phone = person.phones(j);
|
||||||
|
std::cout << " Phone #" << (j + 1) << ": " << phone.number() << " (";
|
||||||
|
|
||||||
|
switch (phone.type()) {
|
||||||
|
case tutorial::Person::MOBILE:
|
||||||
|
std::cout << "mobile";
|
||||||
|
break;
|
||||||
|
case tutorial::Person::HOME:
|
||||||
|
std::cout << "home";
|
||||||
|
break;
|
||||||
|
case tutorial::Person::WORK:
|
||||||
|
std::cout << "work";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cout << "unknown";
|
||||||
|
}
|
||||||
|
std::cout << ")\n";
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Verify that the version of the library that we linked against is
|
||||||
|
// compatible with the version of the headers we compiled against.
|
||||||
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
||||||
|
|
||||||
|
std::cout << "Protobuf cmkr Example\n";
|
||||||
|
std::cout << "=====================\n\n";
|
||||||
|
|
||||||
|
// Create an address book
|
||||||
|
tutorial::AddressBook original_book;
|
||||||
|
create_address_book(original_book);
|
||||||
|
|
||||||
|
std::cout << "Original Address Book:\n";
|
||||||
|
print_address_book(original_book);
|
||||||
|
|
||||||
|
// Serialize to string
|
||||||
|
std::string serialized;
|
||||||
|
if (!original_book.SerializeToString(&serialized)) {
|
||||||
|
std::cerr << "Error: Failed to serialize address book\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "Serialized size: " << serialized.size() << " bytes\n\n";
|
||||||
|
|
||||||
|
// Deserialize into a new object
|
||||||
|
tutorial::AddressBook deserialized_book;
|
||||||
|
if (!deserialized_book.ParseFromString(serialized)) {
|
||||||
|
std::cerr << "Error: Failed to deserialize address book\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Deserialized Address Book:\n";
|
||||||
|
print_address_book(deserialized_book);
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
const char *filename = "addressbook.bin";
|
||||||
|
{
|
||||||
|
std::ofstream output(filename, std::ios::binary);
|
||||||
|
if (!original_book.SerializeToOstream(&output)) {
|
||||||
|
std::cerr << "Error: Failed to write to file\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "Written to file: " << filename << "\n";
|
||||||
|
|
||||||
|
// Read back from file
|
||||||
|
tutorial::AddressBook file_book;
|
||||||
|
{
|
||||||
|
std::ifstream input(filename, std::ios::binary);
|
||||||
|
if (!file_book.ParseFromIstream(&input)) {
|
||||||
|
std::cerr << "Error: Failed to read from file\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "Read back from file successfully!\n\n";
|
||||||
|
|
||||||
|
// Verify all three versions are identical
|
||||||
|
bool all_match = true;
|
||||||
|
if (original_book.SerializeAsString() != deserialized_book.SerializeAsString()) {
|
||||||
|
std::cerr << "Error: Original and deserialized don't match!\n";
|
||||||
|
all_match = false;
|
||||||
|
}
|
||||||
|
if (original_book.SerializeAsString() != file_book.SerializeAsString()) {
|
||||||
|
std::cerr << "Error: Original and file don't match!\n";
|
||||||
|
all_match = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all_match) {
|
||||||
|
std::cout << "SUCCESS: All serialization methods produced identical results!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up any global objects allocated by protobuf
|
||||||
|
google::protobuf::ShutdownProtobufLibrary();
|
||||||
|
|
||||||
|
return all_match ? 0 : 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user