1# Copyright 2020 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14include_guard(GLOBAL) 15 16include($ENV{PW_ROOT}/pw_build/pigweed.cmake) 17 18# Declares a protocol buffers library. This function creates a library for each 19# supported protocol buffer implementation: 20# 21# ${NAME}.pwpb - pw_protobuf generated code 22# ${NAME}.nanopb - Nanopb generated code (requires Nanopb) 23# 24# This function also creates libraries for generating pw_rpc code: 25# 26# ${NAME}.pwpb_rpc - generates pw_protobuf pw_rpc code 27# ${NAME}.nanopb_rpc - generates Nanopb pw_rpc code 28# ${NAME}.raw_rpc - generates raw pw_rpc (no protobuf library) code 29# 30# Args: 31# 32# NAME - the base name of the libraries to create 33# SOURCES - .proto source files 34# DEPS - dependencies on other pw_proto_library targets 35# PREFIX - prefix add to the proto files 36# STRIP_PREFIX - prefix to remove from the proto files 37# INPUTS - files to include along with the .proto files (such as Nanopb 38# .options files) 39# 40function(pw_proto_library NAME) 41 pw_parse_arguments( 42 NUM_POSITIONAL_ARGS 43 1 44 ONE_VALUE_ARGS 45 STRIP_PREFIX 46 PREFIX 47 MULTI_VALUE_ARGS 48 SOURCES 49 INPUTS 50 DEPS 51 REQUIRED_ARGS 52 SOURCES 53 ) 54 55 set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${NAME}") 56 57 # Use INTERFACE libraries to track the proto include paths that are passed to 58 # protoc. 59 set(include_deps "${arg_DEPS}") 60 list(TRANSFORM include_deps APPEND ._includes) 61 62 pw_add_library_generic("${NAME}._includes" INTERFACE 63 PUBLIC_INCLUDES 64 "${out_dir}/sources" 65 PUBLIC_DEPS 66 ${include_deps} 67 ) 68 69 # Generate a file with all include paths needed by protoc. Use the include 70 # directory paths and replace ; with \n. 71 set(include_file "${out_dir}/include_paths.txt") 72 file(GENERATE OUTPUT "${include_file}" 73 CONTENT 74 "$<JOIN:$<TARGET_PROPERTY:${NAME}._includes,INTERFACE_INCLUDE_DIRECTORIES>,\n>") 75 76 if("${arg_STRIP_PREFIX}" STREQUAL "") 77 set(arg_STRIP_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}") 78 else() 79 get_filename_component(arg_STRIP_PREFIX "${arg_STRIP_PREFIX}" ABSOLUTE) 80 endif() 81 82 foreach(path IN LISTS arg_SOURCES arg_INPUTS) 83 get_filename_component(abspath "${path}" ABSOLUTE) 84 list(APPEND files_to_mirror "${abspath}") 85 endforeach() 86 87 # Mirror the sources to the output directory with the specified prefix. 88 pw_rebase_paths( 89 sources "${out_dir}/sources/${arg_PREFIX}" "${arg_STRIP_PREFIX}" 90 "${arg_SOURCES}" "") 91 pw_rebase_paths( 92 inputs "${out_dir}/sources/${arg_PREFIX}" "${arg_STRIP_PREFIX}" 93 "${arg_INPUTS}" "") 94 95 add_custom_command( 96 COMMAND 97 python3 98 "$ENV{PW_ROOT}/pw_build/py/pw_build/mirror_tree.py" 99 --source-root "${arg_STRIP_PREFIX}" 100 --directory "${out_dir}/sources/${arg_PREFIX}" 101 ${files_to_mirror} 102 DEPENDS 103 "$ENV{PW_ROOT}/pw_build/py/pw_build/mirror_tree.py" 104 ${files_to_mirror} 105 OUTPUT 106 ${sources} ${inputs} 107 ) 108 add_custom_target("${NAME}._sources" DEPENDS ${sources} ${inputs}) 109 110 set(sources_deps "${arg_DEPS}") 111 list(TRANSFORM sources_deps APPEND ._sources) 112 113 if(sources_deps) 114 add_dependencies("${NAME}._sources" ${sources_deps}) 115 endif() 116 117 # Create a protobuf target for each supported protobuf library. 118 _pw_pwpb_library("${NAME}" 119 SOURCES 120 ${sources} 121 INPUTS 122 ${inputs} 123 DEPS 124 ${arg_DEPS} 125 INCLUDE_FILE 126 "${include_file}" 127 OUT_DIR 128 "${out_dir}" 129 ) 130 _pw_pwpb_rpc_library("${NAME}" 131 SOURCES 132 ${sources} 133 INPUTS 134 ${inputs} 135 DEPS 136 ${arg_DEPS} 137 INCLUDE_FILE 138 "${include_file}" 139 OUT_DIR 140 "${out_dir}" 141 ) 142 _pw_raw_rpc_library("${NAME}" 143 SOURCES 144 ${sources} 145 INPUTS 146 ${inputs} 147 DEPS 148 ${arg_DEPS} 149 INCLUDE_FILE 150 "${include_file}" 151 OUT_DIR 152 "${out_dir}" 153 ) 154 _pw_nanopb_library("${NAME}" 155 SOURCES 156 ${sources} 157 INPUTS 158 ${inputs} 159 DEPS 160 ${arg_DEPS} 161 INCLUDE_FILE 162 "${include_file}" 163 OUT_DIR 164 "${out_dir}" 165 ) 166 _pw_nanopb_rpc_library("${NAME}" 167 SOURCES 168 ${sources} 169 INPUTS 170 ${inputs} 171 DEPS 172 ${arg_DEPS} 173 INCLUDE_FILE 174 "${include_file}" 175 OUT_DIR 176 "${out_dir}" 177 ) 178endfunction(pw_proto_library) 179 180# Internal function that invokes protoc through generate_protos.py. 181function(_pw_generate_protos TARGET LANGUAGE) 182 pw_parse_arguments( 183 NUM_POSITIONAL_ARGS 184 2 185 ONE_VALUE_ARGS 186 PLUGIN 187 INCLUDE_FILE 188 OUT_DIR 189 MULTI_VALUE_ARGS 190 OUTPUT_EXTS 191 SOURCES 192 INPUTS 193 DEPENDS 194 ) 195 196 # Determine the names of the compiled output files. 197 pw_rebase_paths(outputs 198 "${arg_OUT_DIR}/${LANGUAGE}" "${arg_OUT_DIR}/sources" "${arg_SOURCES}" 199 "${arg_OUTPUT_EXTS}") 200 201 # Export the output files to the caller's scope so it can use them if needed. 202 set(generated_outputs "${outputs}" PARENT_SCOPE) 203 204 if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") 205 foreach(source_file IN LISTS SOURCES) 206 get_filename_component(dir "${source_file}" DIRECTORY) 207 get_filename_component(name "${source_file}" NAME_WE) 208 set(arg_PLUGIN "${dir}/${name}.bat") 209 endforeach() 210 endif() 211 212 set(script "$ENV{PW_ROOT}/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py") 213 add_custom_command( 214 COMMAND 215 python3 216 "${script}" 217 --language "${LANGUAGE}" 218 --plugin-path "${arg_PLUGIN}" 219 --include-file "${arg_INCLUDE_FILE}" 220 --compile-dir "${arg_OUT_DIR}/sources" 221 --out-dir "${arg_OUT_DIR}/${LANGUAGE}" 222 --sources ${arg_SOURCES} 223 DEPENDS 224 ${script} 225 ${arg_SOURCES} 226 ${arg_INPUTS} 227 ${arg_DEPENDS} 228 OUTPUT 229 ${outputs} 230 ) 231 add_custom_target("${TARGET}._generate.${LANGUAGE}" DEPENDS ${outputs}) 232 add_dependencies("${TARGET}._generate.${LANGUAGE}" "${TARGET}._sources") 233endfunction(_pw_generate_protos) 234 235# Internal function that creates a pwpb proto library. 236function(_pw_pwpb_library NAME) 237 pw_parse_arguments( 238 NUM_POSITIONAL_ARGS 239 1 240 ONE_VALUE_ARGS 241 INCLUDE_FILE 242 OUT_DIR 243 MULTI_VALUE_ARGS 244 SOURCES 245 INPUTS 246 DEPS 247 ) 248 249 list(TRANSFORM arg_DEPS APPEND .pwpb) 250 251 _pw_generate_protos("${NAME}" pwpb 252 PLUGIN 253 "$ENV{PW_ROOT}/pw_protobuf/py/pw_protobuf/plugin.py" 254 OUTPUT_EXTS 255 ".pwpb.h" 256 INCLUDE_FILE 257 "${arg_INCLUDE_FILE}" 258 OUT_DIR 259 "${arg_OUT_DIR}" 260 SOURCES 261 ${arg_SOURCES} 262 INPUTS 263 ${arg_INPUTS} 264 DEPENDS 265 ${arg_DEPS} 266 ) 267 268 # Create the library with the generated source files. 269 pw_add_library_generic("${NAME}.pwpb" INTERFACE 270 PUBLIC_INCLUDES 271 "${arg_OUT_DIR}/pwpb" 272 PUBLIC_DEPS 273 pw_build 274 pw_protobuf 275 pw_span 276 pw_string.string 277 ${arg_DEPS} 278 ) 279 add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb") 280endfunction(_pw_pwpb_library) 281 282# Internal function that creates a pwpb_rpc library. 283function(_pw_pwpb_rpc_library NAME) 284 pw_parse_arguments( 285 NUM_POSITIONAL_ARGS 286 1 287 ONE_VALUE_ARGS 288 INCLUDE_FILE 289 OUT_DIR 290 MULTI_VALUE_ARGS 291 SOURCES 292 INPUTS 293 DEPS 294 ) 295 296 # Determine the names of the output files. 297 list(TRANSFORM arg_DEPS APPEND .pwpb_rpc) 298 299 _pw_generate_protos("${NAME}" pwpb_rpc 300 PLUGIN 301 "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_pwpb.py" 302 OUTPUT_EXTS 303 ".rpc.pwpb.h" 304 INCLUDE_FILE 305 "${arg_INCLUDE_FILE}" 306 OUT_DIR 307 "${arg_OUT_DIR}" 308 SOURCES 309 ${arg_SOURCES} 310 INPUTS 311 ${arg_INPUTS} 312 DEPENDS 313 ${arg_DEPS} 314 "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py" 315 ) 316 317 # Create the library with the generated source files. 318 pw_add_library_generic("${NAME}.pwpb_rpc" INTERFACE 319 PUBLIC_INCLUDES 320 "${arg_OUT_DIR}/pwpb_rpc" 321 PUBLIC_DEPS 322 "${NAME}.pwpb" 323 pw_build 324 pw_rpc.pwpb.client_api 325 pw_rpc.pwpb.server_api 326 pw_rpc.server 327 ${arg_DEPS} 328 ) 329 add_dependencies("${NAME}.pwpb_rpc" "${NAME}._generate.pwpb_rpc") 330endfunction(_pw_pwpb_rpc_library) 331 332# Internal function that creates a raw_rpc proto library. 333function(_pw_raw_rpc_library NAME) 334 pw_parse_arguments( 335 NUM_POSITIONAL_ARGS 336 1 337 ONE_VALUE_ARGS 338 INCLUDE_FILE 339 OUT_DIR 340 MULTI_VALUE_ARGS 341 SOURCES 342 INPUTS 343 DEPS 344 ) 345 346 list(TRANSFORM arg_DEPS APPEND .raw_rpc) 347 348 _pw_generate_protos("${NAME}" raw_rpc 349 PLUGIN 350 "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_raw.py" 351 OUTPUT_EXTS 352 ".raw_rpc.pb.h" 353 INCLUDE_FILE 354 "${arg_INCLUDE_FILE}" 355 OUT_DIR 356 "${arg_OUT_DIR}" 357 SOURCES 358 ${arg_SOURCES} 359 INPUTS 360 ${arg_INPUTS} 361 DEPENDS 362 ${arg_DEPS} 363 "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py" 364 ) 365 366 # Create the library with the generated source files. 367 pw_add_library_generic("${NAME}.raw_rpc" INTERFACE 368 PUBLIC_INCLUDES 369 "${arg_OUT_DIR}/raw_rpc" 370 PUBLIC_DEPS 371 pw_build 372 pw_rpc.raw.server_api 373 pw_rpc.raw.client_api 374 pw_rpc.server 375 ${arg_DEPS} 376 ) 377 add_dependencies("${NAME}.raw_rpc" "${NAME}._generate.raw_rpc") 378endfunction(_pw_raw_rpc_library) 379 380# Internal function that creates a nanopb proto library. 381function(_pw_nanopb_library NAME) 382 pw_parse_arguments( 383 NUM_POSITIONAL_ARGS 384 1 385 ONE_VALUE_ARGS 386 INCLUDE_FILE 387 OUT_DIR 388 MULTI_VALUE_ARGS 389 SOURCES 390 INPUTS 391 DEPS 392 ) 393 394 list(TRANSFORM arg_DEPS APPEND .nanopb) 395 396 if("${dir_pw_third_party_nanopb}" STREQUAL "") 397 add_custom_target("${NAME}._generate.nanopb") # Nothing to do 398 pw_add_error_target("${NAME}.nanopb" 399 MESSAGE 400 "Attempting to use pw_proto_library, but dir_pw_third_party_nanopb is " 401 "not set. Set dir_pw_third_party_nanopb to the path to the Nanopb " 402 "repository." 403 ) 404 else() 405 # When compiling with the Nanopb plugin, the nanopb.proto file is already 406 # compiled internally, so skip recompiling it with protoc. 407 if("${arg_SOURCES}" MATCHES "nanopb\\.proto") 408 add_custom_target("${NAME}._generate.nanopb") # Nothing to do 409 pw_add_library_generic("${NAME}.nanopb" INTERFACE 410 PUBLIC_DEPS 411 pw_build 412 pw_third_party.nanopb 413 ${arg_DEPS} 414 ) 415 else() 416 _pw_generate_protos("${NAME}" nanopb 417 PLUGIN 418 "${dir_pw_third_party_nanopb}/generator/protoc-gen-nanopb" 419 OUTPUT_EXTS 420 ".pb.h" 421 ".pb.c" 422 INCLUDE_FILE 423 "${arg_INCLUDE_FILE}" 424 OUT_DIR 425 "${arg_OUT_DIR}" 426 SOURCES 427 ${arg_SOURCES} 428 INPUTS 429 ${arg_INPUTS} 430 DEPENDS 431 ${arg_DEPS} 432 ) 433 434 # Create the library with the generated source files. 435 pw_add_library_generic("${NAME}.nanopb" STATIC 436 SOURCES 437 ${generated_outputs} 438 PUBLIC_INCLUDES 439 "${arg_OUT_DIR}/nanopb" 440 PUBLIC_DEPS 441 pw_build 442 pw_third_party.nanopb 443 ${arg_DEPS} 444 ) 445 endif() 446 447 add_dependencies("${NAME}.nanopb" "${NAME}._generate.nanopb") 448 449 # Ensure that nanopb_pb2.py is generated to avoid race conditions. 450 add_dependencies("${NAME}._generate.nanopb" 451 pw_third_party.nanopb.generate_proto 452 ) 453 endif() 454endfunction(_pw_nanopb_library) 455 456# Internal function that creates a nanopb_rpc library. 457function(_pw_nanopb_rpc_library NAME) 458 pw_parse_arguments( 459 NUM_POSITIONAL_ARGS 460 1 461 ONE_VALUE_ARGS 462 INCLUDE_FILE 463 OUT_DIR 464 MULTI_VALUE_ARGS 465 SOURCES 466 INPUTS 467 DEPS 468 ) 469 470 # Determine the names of the output files. 471 list(TRANSFORM arg_DEPS APPEND .nanopb_rpc) 472 473 _pw_generate_protos("${NAME}" nanopb_rpc 474 PLUGIN 475 "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_nanopb.py" 476 OUTPUT_EXTS 477 ".rpc.pb.h" 478 INCLUDE_FILE 479 "${arg_INCLUDE_FILE}" 480 OUT_DIR 481 "${arg_OUT_DIR}" 482 SOURCES 483 ${arg_SOURCES} 484 INPUTS 485 ${arg_INPUTS} 486 DEPENDS 487 ${arg_DEPS} 488 "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py" 489 ) 490 491 # Create the library with the generated source files. 492 pw_add_library_generic("${NAME}.nanopb_rpc" INTERFACE 493 PUBLIC_INCLUDES 494 "${arg_OUT_DIR}/nanopb_rpc" 495 PUBLIC_DEPS 496 "${NAME}.nanopb" 497 pw_build 498 pw_rpc.nanopb.client_api 499 pw_rpc.nanopb.server_api 500 pw_rpc.server 501 ${arg_DEPS} 502 ) 503 add_dependencies("${NAME}.nanopb_rpc" "${NAME}._generate.nanopb_rpc") 504endfunction(_pw_nanopb_rpc_library) 505