mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43c72160b5 | |||
| ef3c1a288a |
@@ -10,6 +10,7 @@ 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
|
||||||
|
|||||||
+1
-78
@@ -199,8 +199,6 @@ 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]
|
||||||
@@ -215,19 +213,10 @@ 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
|
||||||
@@ -257,24 +246,6 @@ 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")
|
||||||
@@ -292,51 +263,6 @@ 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 |
|
||||||
@@ -353,9 +279,6 @@ 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:
|
||||||
@@ -419,4 +342,4 @@ files = ["content/my.png"]
|
|||||||
dirs = ["include"]
|
dirs = ["include"]
|
||||||
configs = ["Release", "Debug"]
|
configs = ["Release", "Debug"]
|
||||||
optional = false
|
optional = false
|
||||||
```
|
```
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
---
|
|
||||||
# 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>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
# 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>
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
---
|
|
||||||
# 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,104 +74,6 @@ 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;
|
||||||
@@ -205,9 +107,6 @@ 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;
|
||||||
@@ -255,15 +154,10 @@ 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;
|
||||||
|
|||||||
+31
-362
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#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>
|
||||||
@@ -1124,22 +1123,6 @@ 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") + ")";
|
||||||
@@ -1222,14 +1205,6 @@ 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
|
||||||
@@ -1269,103 +1244,6 @@ 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;
|
||||||
@@ -1384,21 +1262,8 @@ 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 (resolved_target_type != parser::target_interface) {
|
if (target.type != parser::target_interface) {
|
||||||
msources[""].insert("cmake.toml");
|
msources[""].insert("cmake.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1432,7 +1297,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 (resolved_target_type) {
|
switch (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:
|
||||||
@@ -1451,7 +1316,8 @@ 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) {
|
||||||
if (has_cmake_path_reference(source))
|
auto var_index = source.find("${");
|
||||||
|
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)) {
|
||||||
@@ -1475,12 +1341,18 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
auto target_type = resolved_target_type;
|
auto target_type = 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:
|
||||||
@@ -1509,7 +1381,9 @@ 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:
|
||||||
is_custom_target = true;
|
// TODO: add proper support, this is hacky
|
||||||
|
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:
|
||||||
@@ -1522,157 +1396,6 @@ 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;
|
||||||
@@ -1686,51 +1409,6 @@ 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) {
|
||||||
@@ -1738,13 +1416,6 @@ 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();
|
||||||
@@ -1781,31 +1452,29 @@ 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) {
|
||||||
if (!is_custom_target) {
|
target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
|
||||||
target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
|
target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
|
||||||
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,7 +1,6 @@
|
|||||||
#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>
|
||||||
@@ -210,105 +209,6 @@ 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)) {
|
||||||
@@ -605,37 +505,7 @@ 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()) {
|
||||||
@@ -828,253 +698,6 @@ 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,33 +131,3 @@ 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,21 +71,3 @@ 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"]
|
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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")
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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}")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
std::cout << "Hello, world!" << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#include "generated.hpp"
|
|
||||||
|
|
||||||
int generated_value();
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
return generated_value() == GENERATED_VALUE ? 0 : 1;
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
build/
|
|
||||||
*.bin
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
// 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