# Copyright 2025 Dennis Hezel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#[[===
# /* [asio_grpc_protobuf_generate] */
In the same directory that called `find_package(asio-grpc)` the following CMake function will be made available.
It can be used to generate Protobuf/gRPC source files from `.proto` schemas.
If you are using [cmake-format](https://github.com/cheshirekow/cmake_format) then you can copy the `asio_grpc_protobuf_generate` section from
[cmake-format.yaml](https://github.com/Tradias/asio-grpc/blob/v3.5.0/cmake-format.yaml#L2-L14) to get proper formatting.
```cmake
asio_grpc_protobuf_generate(PROTOS [...]
[OUT_DIR ]
[OUT_VAR ]
[TARGET ]
[USAGE_REQUIREMENT PRIVATE|PUBLIC|INTERFACE]
[IMPORT_DIRS ...]
[EXTRA_ARGS ...]
[GENERATE_GRPC]
[GENERATE_DESCRIPTORS]
[GENERATE_MOCK_CODE]
[PROTOC ]
[GRPC_PLUGIN ])
```
__PROTOS__: Input `.proto` schema files.
__OUT_DIR__: Generated files output directory. Default: `CMAKE_CURRENT_BINARY_DIR`.
__OUT_VAR__: Variable to define with generated source files.
__TARGET__: Add generated source files to target.
__USAGE_REQUIREMENT__: How to add sources to ``: `PRIVATE`, `PUBLIC`, `INTERFACE`. Default: `PRIVATE`.
__IMPORT_DIRS__: Import directories to be added to the protoc command line. If unspecified then the directory of each .proto file will be used.
__EXTRA_ARGS__: Additional protoc command line arguments.
__GENERATE_GRPC__: Generate gRPC files (.grpc.pb.h and .grpc.pb.cc).
__GENERATE_DESCRIPTORS__: Generate descriptor files named `.desc`.
__GENERATE_MOCK_CODE__: Generate gRPC client stub mock files named `_mock.grpc.pb.h`.
__PROTOC__: Path to the Protocol Buffers compiler (`protoc`). Default: `$`.
__GRPC_PLUGIN__: Path to the gRPC plugin for C++ code generation. Default: `$`.
# /* [asio_grpc_protobuf_generate] */
===]]
function(asio_grpc_protobuf_generate)
include(CMakeParseArguments)
set(_asio_grpc_options GENERATE_GRPC GENERATE_DESCRIPTORS GENERATE_MOCK_CODE)
set(_asio_grpc_singleargs OUT_VAR OUT_DIR TARGET USAGE_REQUIREMENT PROTOC GRPC_PLUGIN)
set(_asio_grpc_multiargs PROTOS IMPORT_DIRS EXTRA_ARGS)
cmake_parse_arguments(asio_grpc_protobuf_generate "${_asio_grpc_options}" "${_asio_grpc_singleargs}"
"${_asio_grpc_multiargs}" "${ARGN}")
if(asio_grpc_protobuf_generate_UNPARSED_ARGUMENTS)
message(
AUTHOR_WARNING
"asio_grpc_protobuf_generate unknown argument: ${asio_grpc_protobuf_generate_UNPARSED_ARGUMENTS}")
endif()
if(asio_grpc_protobuf_generate_KEYWORDS_MISSING_VALUES)
message(
AUTHOR_WARNING
"asio_grpc_protobuf_generate missing values for: ${asio_grpc_protobuf_generate_KEYWORDS_MISSING_VALUES}"
)
endif()
if(NOT asio_grpc_protobuf_generate_PROTOS)
message(SEND_ERROR "asio_grpc_protobuf_generate called without any proto files: PROTOS")
return()
endif()
if(NOT asio_grpc_protobuf_generate_OUT_VAR AND NOT asio_grpc_protobuf_generate_TARGET)
message(SEND_ERROR "asio_grpc_protobuf_generate called without a target or output variable: TARGET or OUT_VAR")
return()
endif()
if(asio_grpc_protobuf_generate_TARGET AND NOT TARGET ${asio_grpc_protobuf_generate_TARGET})
message(
SEND_ERROR
"asio_grpc_protobuf_generate argument passed to TARGET is not a target: ${asio_grpc_protobuf_generate_TARGET}"
)
return()
endif()
if(asio_grpc_protobuf_generate_GENERATE_MOCK_CODE AND NOT asio_grpc_protobuf_generate_GENERATE_GRPC)
message(SEND_ERROR "asio_grpc_protobuf_generate argument GENERATE_MOCK_CODE requires GENERATE_GRPC")
return()
endif()
if(NOT asio_grpc_protobuf_generate_OUT_DIR)
set(asio_grpc_protobuf_generate_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
# Verify PROTOC and GRPC_PLUGIN paths if provided
if(asio_grpc_protobuf_generate_PROTOC AND NOT EXISTS "${asio_grpc_protobuf_generate_PROTOC}")
message(
SEND_ERROR "asio_grpc_protobuf_generate PROTOC path does not exist: ${asio_grpc_protobuf_generate_PROTOC}")
return()
endif()
if(asio_grpc_protobuf_generate_GRPC_PLUGIN AND NOT EXISTS "${asio_grpc_protobuf_generate_GRPC_PLUGIN}")
message(
SEND_ERROR
"asio_grpc_protobuf_generate GRPC_PLUGIN path does not exist: ${asio_grpc_protobuf_generate_GRPC_PLUGIN}"
)
return()
endif()
set(_asio_grpc_generated_extensions ".pb.cc" ".pb.h")
if(asio_grpc_protobuf_generate_GENERATE_GRPC)
list(APPEND _asio_grpc_generated_extensions ".grpc.pb.cc" ".grpc.pb.h")
if(asio_grpc_protobuf_generate_GENERATE_MOCK_CODE)
list(APPEND _asio_grpc_generated_extensions "_mock.grpc.pb.h")
endif()
endif()
if(NOT asio_grpc_protobuf_generate_USAGE_REQUIREMENT)
set(asio_grpc_protobuf_generate_USAGE_REQUIREMENT "PRIVATE")
endif()
if(NOT asio_grpc_protobuf_generate_IMPORT_DIRS)
# Create an include path for each file specified
foreach(_asio_grpc_file ${asio_grpc_protobuf_generate_PROTOS})
get_filename_component(_asio_grpc_abs_file "${_asio_grpc_file}" ABSOLUTE)
get_filename_component(_asio_grpc_abs_path "${_asio_grpc_abs_file}" PATH)
list(APPEND asio_grpc_protobuf_generate_IMPORT_DIRS "${_asio_grpc_abs_path}")
endforeach()
endif()
# Add all explicitly specified import directory to the include path
foreach(_asio_grpc_dir ${asio_grpc_protobuf_generate_IMPORT_DIRS})
get_filename_component(_asio_grpc_abs_dir "${_asio_grpc_dir}" ABSOLUTE)
list(FIND _asio_grpc_protobuf_include_args "${_asio_grpc_abs_dir}" _asio_grpc_contains_already)
if(${_asio_grpc_contains_already} EQUAL -1)
list(APPEND _asio_grpc_protobuf_include_args -I "${_asio_grpc_abs_dir}")
endif()
endforeach()
set(_asio_grpc_abs_input_files)
set(_asio_grpc_generated_srcs)
foreach(_asio_grpc_proto ${asio_grpc_protobuf_generate_PROTOS})
get_filename_component(_asio_grpc_abs_file "${_asio_grpc_proto}" ABSOLUTE)
# Get .proto base name
get_filename_component(_asio_grpc_full_name ${_asio_grpc_abs_file} NAME)
string(FIND "${_asio_grpc_full_name}" "." _asio_grpc_file_last_ext_pos REVERSE)
string(SUBSTRING "${_asio_grpc_full_name}" 0 ${_asio_grpc_file_last_ext_pos} _asio_grpc_basename)
list(APPEND _asio_grpc_abs_input_files "${_asio_grpc_abs_file}")
# Compute generated file output directory by checking that the .proto file is not in a parent directory of all
# import directories.
get_filename_component(_asio_grpc_abs_dir ${_asio_grpc_abs_file} DIRECTORY)
set(_asio_grpc_suitable_include_found off)
foreach(_asio_grpc_dir ${_asio_grpc_protobuf_include_args})
if(NOT _asio_grpc_dir STREQUAL "-I")
file(RELATIVE_PATH _asio_grpc_rel_out_dir ${_asio_grpc_dir} ${_asio_grpc_abs_dir})
string(FIND "${_asio_grpc_rel_out_dir}" "../" _asio_grpc_is_in_parent_folder)
if(NOT ${_asio_grpc_is_in_parent_folder} EQUAL 0)
set(_asio_grpc_suitable_include_found on)
break()
endif()
endif()
endforeach()
if(NOT _asio_grpc_suitable_include_found)
string(REPLACE ";" " " _asio_grpc_pretty_import_dirs "${asio_grpc_protobuf_generate_IMPORT_DIRS}")
message(
SEND_ERROR
"None of the IMPORT_DIRS passed to asio_grpc_protobuf_generate contain the proto file: \"${_asio_grpc_abs_file}\".\nIMPORT_DIRS: ${_asio_grpc_pretty_import_dirs}"
)
return()
endif()
set(_asio_grpc_actual_out_dir "${asio_grpc_protobuf_generate_OUT_DIR}/${_asio_grpc_rel_out_dir}")
# Collect generated files
foreach(_asio_grpc_ext ${_asio_grpc_generated_extensions})
list(APPEND _asio_grpc_generated_srcs
"${_asio_grpc_actual_out_dir}/${_asio_grpc_basename}${_asio_grpc_ext}")
endforeach()
if(asio_grpc_protobuf_generate_GENERATE_DESCRIPTORS)
set(_asio_grpc_descriptor_file "${_asio_grpc_actual_out_dir}/${_asio_grpc_basename}.desc")
set(_asio_grpc_descriptor_command "--descriptor_set_out=${_asio_grpc_descriptor_file}")
list(APPEND _asio_grpc_generated_srcs "${_asio_grpc_descriptor_file}")
endif()
endforeach()
# Set default values for PROTOC and GRPC_PLUGIN if not provided
if(NOT asio_grpc_protobuf_generate_PROTOC)
set(asio_grpc_protobuf_generate_PROTOC $)
endif()
if(NOT asio_grpc_protobuf_generate_GRPC_PLUGIN)
set(asio_grpc_protobuf_generate_GRPC_PLUGIN $)
endif()
# Run protoc
set(_asio_grpc_command_arguments --cpp_out "${asio_grpc_protobuf_generate_OUT_DIR}"
"${_asio_grpc_descriptor_command}" "${_asio_grpc_protobuf_include_args}")
if(asio_grpc_protobuf_generate_GENERATE_GRPC)
if(asio_grpc_protobuf_generate_GENERATE_MOCK_CODE)
set(_asio_grpc_generate_mock_code "generate_mock_code=true:")
endif()
list(APPEND _asio_grpc_command_arguments --grpc_out
"${_asio_grpc_generate_mock_code}${asio_grpc_protobuf_generate_OUT_DIR}"
"--plugin=protoc-gen-grpc=${asio_grpc_protobuf_generate_GRPC_PLUGIN}")
endif()
list(APPEND _asio_grpc_command_arguments ${asio_grpc_protobuf_generate_EXTRA_ARGS} ${_asio_grpc_abs_input_files})
string(REPLACE ";" " " _asio_grpc_pretty_command_arguments "${_asio_grpc_command_arguments}")
add_custom_command(
OUTPUT ${_asio_grpc_generated_srcs}
COMMAND "${CMAKE_COMMAND}" "-E" "make_directory" "${asio_grpc_protobuf_generate_OUT_DIR}"
COMMAND ${asio_grpc_protobuf_generate_PROTOC} ${_asio_grpc_command_arguments}
DEPENDS ${asio_grpc_protobuf_generate_PROTOC} ${_asio_grpc_abs_input_files}
COMMENT "protoc ${_asio_grpc_pretty_command_arguments}"
VERBATIM)
set_source_files_properties(${_asio_grpc_generated_srcs} PROPERTIES SKIP_UNITY_BUILD_INCLUSION on)
if(asio_grpc_protobuf_generate_TARGET)
if("${asio_grpc_protobuf_generate_USAGE_REQUIREMENT}" STREQUAL "INTERFACE")
target_sources(${asio_grpc_protobuf_generate_TARGET} INTERFACE ${_asio_grpc_generated_srcs})
else()
target_sources(${asio_grpc_protobuf_generate_TARGET} PRIVATE ${_asio_grpc_generated_srcs})
endif()
if("${asio_grpc_protobuf_generate_USAGE_REQUIREMENT}" STREQUAL "PUBLIC")
target_include_directories(${asio_grpc_protobuf_generate_TARGET}
PUBLIC "$")
else()
target_include_directories(
${asio_grpc_protobuf_generate_TARGET} "${asio_grpc_protobuf_generate_USAGE_REQUIREMENT}"
"${asio_grpc_protobuf_generate_OUT_DIR}")
endif()
endif()
if(asio_grpc_protobuf_generate_OUT_VAR)
set(${asio_grpc_protobuf_generate_OUT_VAR}
${_asio_grpc_generated_srcs}
PARENT_SCOPE)
endif()
endfunction()