# Copyright 2020 The Pigweed Authors # # 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 # # https://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. include_guard(GLOBAL) include($ENV{PW_ROOT}/pw_build/pigweed.cmake) # Declares a protocol buffers library. This function creates a library for each # supported protocol buffer implementation: # # ${NAME}.pwpb - pw_protobuf generated code # ${NAME}.nanopb - Nanopb generated code (requires Nanopb) # # This function also creates libraries for generating pw_rpc code: # # ${NAME}.pwpb_rpc - generates pw_protobuf pw_rpc code # ${NAME}.nanopb_rpc - generates Nanopb pw_rpc code # ${NAME}.raw_rpc - generates raw pw_rpc (no protobuf library) code # # Args: # # NAME - the base name of the libraries to create # SOURCES - .proto source files # DEPS - dependencies on other pw_proto_library targets # PREFIX - prefix add to the proto files # STRIP_PREFIX - prefix to remove from the proto files # INPUTS - files to include along with the .proto files (such as Nanopb # .options files) # function(pw_proto_library NAME) pw_parse_arguments( NUM_POSITIONAL_ARGS 1 ONE_VALUE_ARGS STRIP_PREFIX PREFIX MULTI_VALUE_ARGS SOURCES INPUTS DEPS REQUIRED_ARGS SOURCES ) set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${NAME}") # Use INTERFACE libraries to track the proto include paths that are passed to # protoc. set(include_deps "${arg_DEPS}") list(TRANSFORM include_deps APPEND ._includes) pw_add_library_generic("${NAME}._includes" INTERFACE PUBLIC_INCLUDES "${out_dir}/sources" PUBLIC_DEPS ${include_deps} ) # Generate a file with all include paths needed by protoc. Use the include # directory paths and replace ; with \n. set(include_file "${out_dir}/include_paths.txt") file(GENERATE OUTPUT "${include_file}" CONTENT "$,\n>") if("${arg_STRIP_PREFIX}" STREQUAL "") set(arg_STRIP_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}") else() get_filename_component(arg_STRIP_PREFIX "${arg_STRIP_PREFIX}" ABSOLUTE) endif() foreach(path IN LISTS arg_SOURCES arg_INPUTS) get_filename_component(abspath "${path}" ABSOLUTE) list(APPEND files_to_mirror "${abspath}") endforeach() # Mirror the sources to the output directory with the specified prefix. pw_rebase_paths( sources "${out_dir}/sources/${arg_PREFIX}" "${arg_STRIP_PREFIX}" "${arg_SOURCES}" "") pw_rebase_paths( inputs "${out_dir}/sources/${arg_PREFIX}" "${arg_STRIP_PREFIX}" "${arg_INPUTS}" "") add_custom_command( COMMAND python3 "$ENV{PW_ROOT}/pw_build/py/pw_build/mirror_tree.py" --source-root "${arg_STRIP_PREFIX}" --directory "${out_dir}/sources/${arg_PREFIX}" ${files_to_mirror} DEPENDS "$ENV{PW_ROOT}/pw_build/py/pw_build/mirror_tree.py" ${files_to_mirror} OUTPUT ${sources} ${inputs} ) add_custom_target("${NAME}._sources" DEPENDS ${sources} ${inputs}) set(sources_deps "${arg_DEPS}") list(TRANSFORM sources_deps APPEND ._sources) if(sources_deps) add_dependencies("${NAME}._sources" ${sources_deps}) endif() # Create a protobuf target for each supported protobuf library. _pw_pwpb_library("${NAME}" SOURCES ${sources} INPUTS ${inputs} DEPS ${arg_DEPS} INCLUDE_FILE "${include_file}" OUT_DIR "${out_dir}" ) _pw_pwpb_rpc_library("${NAME}" SOURCES ${sources} INPUTS ${inputs} DEPS ${arg_DEPS} INCLUDE_FILE "${include_file}" OUT_DIR "${out_dir}" ) _pw_raw_rpc_library("${NAME}" SOURCES ${sources} INPUTS ${inputs} DEPS ${arg_DEPS} INCLUDE_FILE "${include_file}" OUT_DIR "${out_dir}" ) _pw_nanopb_library("${NAME}" SOURCES ${sources} INPUTS ${inputs} DEPS ${arg_DEPS} INCLUDE_FILE "${include_file}" OUT_DIR "${out_dir}" ) _pw_nanopb_rpc_library("${NAME}" SOURCES ${sources} INPUTS ${inputs} DEPS ${arg_DEPS} INCLUDE_FILE "${include_file}" OUT_DIR "${out_dir}" ) endfunction(pw_proto_library) # Internal function that invokes protoc through generate_protos.py. function(_pw_generate_protos TARGET LANGUAGE) pw_parse_arguments( NUM_POSITIONAL_ARGS 2 ONE_VALUE_ARGS PLUGIN INCLUDE_FILE OUT_DIR MULTI_VALUE_ARGS OUTPUT_EXTS SOURCES INPUTS DEPENDS ) # Determine the names of the compiled output files. pw_rebase_paths(outputs "${arg_OUT_DIR}/${LANGUAGE}" "${arg_OUT_DIR}/sources" "${arg_SOURCES}" "${arg_OUTPUT_EXTS}") # Export the output files to the caller's scope so it can use them if needed. set(generated_outputs "${outputs}" PARENT_SCOPE) if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") foreach(source_file IN LISTS SOURCES) get_filename_component(dir "${source_file}" DIRECTORY) get_filename_component(name "${source_file}" NAME_WE) set(arg_PLUGIN "${dir}/${name}.bat") endforeach() endif() set(script "$ENV{PW_ROOT}/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py") add_custom_command( COMMAND python3 "${script}" --language "${LANGUAGE}" --plugin-path "${arg_PLUGIN}" --include-file "${arg_INCLUDE_FILE}" --compile-dir "${arg_OUT_DIR}/sources" --out-dir "${arg_OUT_DIR}/${LANGUAGE}" --sources ${arg_SOURCES} DEPENDS ${script} ${arg_SOURCES} ${arg_INPUTS} ${arg_DEPENDS} OUTPUT ${outputs} ) add_custom_target("${TARGET}._generate.${LANGUAGE}" DEPENDS ${outputs}) add_dependencies("${TARGET}._generate.${LANGUAGE}" "${TARGET}._sources") endfunction(_pw_generate_protos) # Internal function that creates a pwpb proto library. function(_pw_pwpb_library NAME) pw_parse_arguments( NUM_POSITIONAL_ARGS 1 ONE_VALUE_ARGS INCLUDE_FILE OUT_DIR MULTI_VALUE_ARGS SOURCES INPUTS DEPS ) list(TRANSFORM arg_DEPS APPEND .pwpb) _pw_generate_protos("${NAME}" pwpb PLUGIN "$ENV{PW_ROOT}/pw_protobuf/py/pw_protobuf/plugin.py" OUTPUT_EXTS ".pwpb.h" INCLUDE_FILE "${arg_INCLUDE_FILE}" OUT_DIR "${arg_OUT_DIR}" SOURCES ${arg_SOURCES} INPUTS ${arg_INPUTS} DEPENDS ${arg_DEPS} ) # Create the library with the generated source files. pw_add_library_generic("${NAME}.pwpb" INTERFACE PUBLIC_INCLUDES "${arg_OUT_DIR}/pwpb" PUBLIC_DEPS pw_build pw_protobuf pw_span pw_string.string ${arg_DEPS} ) add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb") endfunction(_pw_pwpb_library) # Internal function that creates a pwpb_rpc library. function(_pw_pwpb_rpc_library NAME) pw_parse_arguments( NUM_POSITIONAL_ARGS 1 ONE_VALUE_ARGS INCLUDE_FILE OUT_DIR MULTI_VALUE_ARGS SOURCES INPUTS DEPS ) # Determine the names of the output files. list(TRANSFORM arg_DEPS APPEND .pwpb_rpc) _pw_generate_protos("${NAME}" pwpb_rpc PLUGIN "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_pwpb.py" OUTPUT_EXTS ".rpc.pwpb.h" INCLUDE_FILE "${arg_INCLUDE_FILE}" OUT_DIR "${arg_OUT_DIR}" SOURCES ${arg_SOURCES} INPUTS ${arg_INPUTS} DEPENDS ${arg_DEPS} "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py" ) # Create the library with the generated source files. pw_add_library_generic("${NAME}.pwpb_rpc" INTERFACE PUBLIC_INCLUDES "${arg_OUT_DIR}/pwpb_rpc" PUBLIC_DEPS "${NAME}.pwpb" pw_build pw_rpc.pwpb.client_api pw_rpc.pwpb.server_api pw_rpc.server ${arg_DEPS} ) add_dependencies("${NAME}.pwpb_rpc" "${NAME}._generate.pwpb_rpc") endfunction(_pw_pwpb_rpc_library) # Internal function that creates a raw_rpc proto library. function(_pw_raw_rpc_library NAME) pw_parse_arguments( NUM_POSITIONAL_ARGS 1 ONE_VALUE_ARGS INCLUDE_FILE OUT_DIR MULTI_VALUE_ARGS SOURCES INPUTS DEPS ) list(TRANSFORM arg_DEPS APPEND .raw_rpc) _pw_generate_protos("${NAME}" raw_rpc PLUGIN "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_raw.py" OUTPUT_EXTS ".raw_rpc.pb.h" INCLUDE_FILE "${arg_INCLUDE_FILE}" OUT_DIR "${arg_OUT_DIR}" SOURCES ${arg_SOURCES} INPUTS ${arg_INPUTS} DEPENDS ${arg_DEPS} "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py" ) # Create the library with the generated source files. pw_add_library_generic("${NAME}.raw_rpc" INTERFACE PUBLIC_INCLUDES "${arg_OUT_DIR}/raw_rpc" PUBLIC_DEPS pw_build pw_rpc.raw.server_api pw_rpc.raw.client_api pw_rpc.server ${arg_DEPS} ) add_dependencies("${NAME}.raw_rpc" "${NAME}._generate.raw_rpc") endfunction(_pw_raw_rpc_library) # Internal function that creates a nanopb proto library. function(_pw_nanopb_library NAME) pw_parse_arguments( NUM_POSITIONAL_ARGS 1 ONE_VALUE_ARGS INCLUDE_FILE OUT_DIR MULTI_VALUE_ARGS SOURCES INPUTS DEPS ) list(TRANSFORM arg_DEPS APPEND .nanopb) if("${dir_pw_third_party_nanopb}" STREQUAL "") add_custom_target("${NAME}._generate.nanopb") # Nothing to do pw_add_error_target("${NAME}.nanopb" MESSAGE "Attempting to use pw_proto_library, but dir_pw_third_party_nanopb is " "not set. Set dir_pw_third_party_nanopb to the path to the Nanopb " "repository." ) else() # When compiling with the Nanopb plugin, the nanopb.proto file is already # compiled internally, so skip recompiling it with protoc. if("${arg_SOURCES}" MATCHES "nanopb\\.proto") add_custom_target("${NAME}._generate.nanopb") # Nothing to do pw_add_library_generic("${NAME}.nanopb" INTERFACE PUBLIC_DEPS pw_build pw_third_party.nanopb ${arg_DEPS} ) else() _pw_generate_protos("${NAME}" nanopb PLUGIN "${dir_pw_third_party_nanopb}/generator/protoc-gen-nanopb" OUTPUT_EXTS ".pb.h" ".pb.c" INCLUDE_FILE "${arg_INCLUDE_FILE}" OUT_DIR "${arg_OUT_DIR}" SOURCES ${arg_SOURCES} INPUTS ${arg_INPUTS} DEPENDS ${arg_DEPS} ) # Create the library with the generated source files. pw_add_library_generic("${NAME}.nanopb" STATIC SOURCES ${generated_outputs} PUBLIC_INCLUDES "${arg_OUT_DIR}/nanopb" PUBLIC_DEPS pw_build pw_third_party.nanopb ${arg_DEPS} ) endif() add_dependencies("${NAME}.nanopb" "${NAME}._generate.nanopb") # Ensure that nanopb_pb2.py is generated to avoid race conditions. add_dependencies("${NAME}._generate.nanopb" pw_third_party.nanopb.generate_proto ) endif() endfunction(_pw_nanopb_library) # Internal function that creates a nanopb_rpc library. function(_pw_nanopb_rpc_library NAME) pw_parse_arguments( NUM_POSITIONAL_ARGS 1 ONE_VALUE_ARGS INCLUDE_FILE OUT_DIR MULTI_VALUE_ARGS SOURCES INPUTS DEPS ) # Determine the names of the output files. list(TRANSFORM arg_DEPS APPEND .nanopb_rpc) _pw_generate_protos("${NAME}" nanopb_rpc PLUGIN "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_nanopb.py" OUTPUT_EXTS ".rpc.pb.h" INCLUDE_FILE "${arg_INCLUDE_FILE}" OUT_DIR "${arg_OUT_DIR}" SOURCES ${arg_SOURCES} INPUTS ${arg_INPUTS} DEPENDS ${arg_DEPS} "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py" ) # Create the library with the generated source files. pw_add_library_generic("${NAME}.nanopb_rpc" INTERFACE PUBLIC_INCLUDES "${arg_OUT_DIR}/nanopb_rpc" PUBLIC_DEPS "${NAME}.nanopb" pw_build pw_rpc.nanopb.client_api pw_rpc.nanopb.server_api pw_rpc.server ${arg_DEPS} ) add_dependencies("${NAME}.nanopb_rpc" "${NAME}._generate.nanopb_rpc") endfunction(_pw_nanopb_rpc_library)