• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
16# Declares a protocol buffers library. This function creates a library for each
17# supported protocol buffer implementation:
18#
19#   ${NAME}.pwpb - pw_protobuf generated code
20#   ${NAME}.nanopb - Nanopb generated code (requires Nanopb)
21#
22# This function also creates libraries for generating pw_rpc code:
23#
24#   ${NAME}.nanopb_rpc - generates Nanopb pw_rpc code
25#   ${NAME}.raw_rpc - generates raw pw_rpc (no protobuf library) code
26#   ${NAME}.pwpb_rpc - (Not implemented) generates pw_protobuf pw_rpc code
27#
28# Args:
29#
30#   NAME - the base name of the libraries to create
31#   SOURCES - .proto source files
32#   DEPS - dependencies on other pw_proto_library targets
33#   PREFIX - prefix add to the proto files
34#   STRIP_PREFIX - prefix to remove from the proto files
35#   INPUTS - files to include along with the .proto files (such as Nanopb
36#       .options files)
37#
38function(pw_proto_library NAME)
39  cmake_parse_arguments(PARSE_ARGV 1 arg "" "STRIP_PREFIX;PREFIX"
40      "SOURCES;INPUTS;DEPS")
41
42  if("${arg_SOURCES}" STREQUAL "")
43    message(FATAL_ERROR
44        "pw_proto_library requires at least one .proto file in SOURCES. No "
45        "SOURCES were listed for ${NAME}.")
46  endif()
47
48  set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${NAME}")
49
50  # Use INTERFACE libraries to track the proto include paths that are passed to
51  # protoc.
52  set(include_deps "${arg_DEPS}")
53  list(TRANSFORM include_deps APPEND ._includes)
54
55  add_library("${NAME}._includes" INTERFACE)
56  target_include_directories("${NAME}._includes" INTERFACE "${out_dir}/sources")
57  target_link_libraries("${NAME}._includes" INTERFACE ${include_deps})
58
59  # Generate a file with all include paths needed by protoc. Use the include
60  # directory paths and replace ; with \n.
61  set(include_file "${out_dir}/include_paths.txt")
62  file(GENERATE OUTPUT "${include_file}"
63     CONTENT
64       "$<JOIN:$<TARGET_PROPERTY:${NAME}._includes,INTERFACE_INCLUDE_DIRECTORIES>,\n>")
65
66  if("${arg_STRIP_PREFIX}" STREQUAL "")
67    set(arg_STRIP_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}")
68  else()
69    get_filename_component(arg_STRIP_PREFIX "${arg_STRIP_PREFIX}" ABSOLUTE)
70  endif()
71
72  foreach(path IN LISTS arg_SOURCES arg_INPUTS)
73    get_filename_component(abspath "${path}" ABSOLUTE)
74    list(APPEND files_to_mirror "${abspath}")
75  endforeach()
76
77  # Mirror the sources to the output directory with the specified prefix.
78  _pw_rebase_paths(
79      sources "${out_dir}/sources/${arg_PREFIX}" "${arg_STRIP_PREFIX}" "${arg_SOURCES}" "")
80  _pw_rebase_paths(
81      inputs "${out_dir}/sources/${arg_PREFIX}" "${arg_STRIP_PREFIX}" "${arg_INPUTS}" "")
82
83  add_custom_command(
84    COMMAND
85      python3
86      "$ENV{PW_ROOT}/pw_build/py/pw_build/mirror_tree.py"
87      --source-root "${arg_STRIP_PREFIX}"
88      --directory "${out_dir}/sources/${arg_PREFIX}"
89      ${files_to_mirror}
90    DEPENDS
91      "$ENV{PW_ROOT}/pw_build/py/pw_build/mirror_tree.py"
92      ${files_to_mirror}
93    OUTPUT
94      ${sources} ${inputs}
95  )
96  add_custom_target("${NAME}._sources" DEPENDS ${sources} ${inputs})
97
98  set(sources_deps "${arg_DEPS}")
99  list(TRANSFORM sources_deps APPEND ._sources)
100
101  if(sources_deps)
102    add_dependencies("${NAME}._sources" ${sources_deps})
103  endif()
104
105  # Create a protobuf target for each supported protobuf library.
106  _pw_pwpb_library(
107      "${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
108  _pw_raw_rpc_library(
109      "${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
110  _pw_nanopb_library(
111      "${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
112  _pw_nanopb_rpc_library(
113      "${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
114endfunction(pw_proto_library)
115
116function(_pw_rebase_paths VAR OUT_DIR ROOT FILES EXTENSIONS)
117  foreach(file IN LISTS FILES)
118    get_filename_component(file "${file}" ABSOLUTE)
119    file(RELATIVE_PATH file "${ROOT}" "${file}")
120
121    if ("${EXTENSIONS}" STREQUAL "")
122      list(APPEND mirrored_files "${OUT_DIR}/${file}")
123    else()
124      foreach(ext IN LISTS EXTENSIONS)
125        get_filename_component(dir "${file}" DIRECTORY)
126        get_filename_component(name "${file}" NAME_WE)
127        list(APPEND mirrored_files "${OUT_DIR}/${dir}/${name}${ext}")
128      endforeach()
129    endif()
130  endforeach()
131
132  set("${VAR}" "${mirrored_files}" PARENT_SCOPE)
133endfunction(_pw_rebase_paths)
134
135# Internal function that invokes protoc through generate_protos.py.
136function(_pw_generate_protos
137    TARGET LANGUAGE PLUGIN OUTPUT_EXTS INCLUDE_FILE OUT_DIR SOURCES INPUTS DEPS)
138  # Determine the names of the compiled output files.
139  _pw_rebase_paths(outputs
140      "${OUT_DIR}/${LANGUAGE}" "${OUT_DIR}/sources" "${SOURCES}" "${OUTPUT_EXTS}")
141
142  # Export the output files to the caller's scope so it can use them if needed.
143  set(generated_outputs "${outputs}" PARENT_SCOPE)
144
145  if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
146      get_filename_component(dir "${source_file}" DIRECTORY)
147      get_filename_component(name "${source_file}" NAME_WE)
148      set(PLUGIN "${dir}/${name}.bat")
149  endif()
150
151  set(script "$ENV{PW_ROOT}/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py")
152  add_custom_command(
153    COMMAND
154      python3
155      "${script}"
156      --language "${LANGUAGE}"
157      --plugin-path "${PLUGIN}"
158      --include-file "${INCLUDE_FILE}"
159      --compile-dir "${OUT_DIR}/sources"
160      --out-dir "${OUT_DIR}/${LANGUAGE}"
161      --sources ${SOURCES}
162    DEPENDS
163      ${script}
164      ${SOURCES}
165      ${INPUTS}
166      ${DEPS}
167    OUTPUT
168      ${outputs}
169  )
170  add_custom_target("${TARGET}._generate.${LANGUAGE}" DEPENDS ${outputs})
171  add_dependencies("${TARGET}._generate.${LANGUAGE}" "${TARGET}._sources")
172endfunction(_pw_generate_protos)
173
174# Internal function that creates a pwpb proto library.
175function(_pw_pwpb_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
176  list(TRANSFORM DEPS APPEND .pwpb)
177
178  _pw_generate_protos("${NAME}"
179      pwpb
180      "$ENV{PW_ROOT}/pw_protobuf/py/pw_protobuf/plugin.py"
181      ".pwpb.h"
182      "${INCLUDE_FILE}"
183      "${OUT_DIR}"
184      "${SOURCES}"
185      "${INPUTS}"
186      "${DEPS}"
187  )
188
189  # Create the library with the generated source files.
190  add_library("${NAME}.pwpb" INTERFACE)
191  target_include_directories("${NAME}.pwpb" INTERFACE "${OUT_DIR}/pwpb")
192  target_link_libraries("${NAME}.pwpb"
193    INTERFACE
194      pw_build
195      pw_polyfill.cstddef
196      pw_polyfill.span
197      pw_protobuf
198      ${DEPS}
199  )
200  add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb")
201endfunction(_pw_pwpb_library)
202
203# Internal function that creates a raw_rpc proto library.
204function(_pw_raw_rpc_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
205  list(TRANSFORM DEPS APPEND .raw_rpc)
206
207  _pw_generate_protos("${NAME}"
208      raw_rpc
209      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_raw.py"
210      ".raw_rpc.pb.h"
211      "${INCLUDE_FILE}"
212      "${OUT_DIR}"
213      "${SOURCES}"
214      "${INPUTS}"
215      "${DEPS}"
216  )
217
218  # Create the library with the generated source files.
219  add_library("${NAME}.raw_rpc" INTERFACE)
220  target_include_directories("${NAME}.raw_rpc" INTERFACE "${OUT_DIR}/raw_rpc")
221  target_link_libraries("${NAME}.raw_rpc"
222    INTERFACE
223      pw_build
224      pw_rpc.raw
225      pw_rpc.server
226      ${DEPS}
227  )
228  add_dependencies("${NAME}.raw_rpc" "${NAME}._generate.raw_rpc")
229endfunction(_pw_raw_rpc_library)
230
231# Internal function that creates a nanopb proto library.
232function(_pw_nanopb_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
233  list(TRANSFORM DEPS APPEND .nanopb)
234
235  if("${dir_pw_third_party_nanopb}" STREQUAL "")
236    add_custom_target("${NAME}._generate.nanopb"
237        "${CMAKE_COMMAND}" -E echo
238            ERROR: Attempting to use pw_proto_library, but
239            dir_pw_third_party_nanopb is not set. Set dir_pw_third_party_nanopb
240            to the path to the Nanopb repository.
241      COMMAND
242        "${CMAKE_COMMAND}" -E false
243      DEPENDS
244        ${DEPS}
245      SOURCES
246        ${SOURCES}
247    )
248    set(generated_outputs $<TARGET_PROPERTY:pw_build.empty,SOURCES>)
249  else()
250    # When compiling with the Nanopb plugin, the nanopb.proto file is already
251    # compiled internally, so skip recompiling it with protoc.
252    if("${SOURCES}" MATCHES "nanopb\\.proto")
253      add_custom_target("${NAME}._generate.nanopb")  # Nothing to do
254      add_library("${NAME}.nanopb" INTERFACE)
255      target_link_libraries("${NAME}.nanopb"
256        INTERFACE
257          pw_build
258          pw_third_party.nanopb
259          ${DEPS}
260      )
261    else()
262      _pw_generate_protos("${NAME}"
263          nanopb
264          "${dir_pw_third_party_nanopb}/generator/protoc-gen-nanopb"
265          ".pb.h;.pb.c"
266          "${INCLUDE_FILE}"
267          "${OUT_DIR}"
268          "${SOURCES}"
269          "${INPUTS}"
270          "${DEPS}"
271      )
272
273      # Create the library with the generated source files.
274      add_library("${NAME}.nanopb" EXCLUDE_FROM_ALL ${generated_outputs})
275      target_include_directories("${NAME}.nanopb" PUBLIC "${OUT_DIR}/nanopb")
276      target_link_libraries("${NAME}.nanopb" PUBLIC pw_build pw_third_party.nanopb ${DEPS})
277    endif()
278
279    add_dependencies("${NAME}.nanopb" "${NAME}._generate.nanopb")
280
281    # Ensure that nanopb_pb2.py is generated to avoid race conditions.
282    add_dependencies("${NAME}._generate.nanopb"
283        pw_third_party.nanopb.generate_proto
284    )
285  endif()
286endfunction(_pw_nanopb_library)
287
288# Internal function that creates a nanopb_rpc library.
289function(_pw_nanopb_rpc_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
290  # Determine the names of the output files.
291  list(TRANSFORM DEPS APPEND .nanopb_rpc)
292
293  _pw_generate_protos("${NAME}"
294      nanopb_rpc
295      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_nanopb.py"
296      ".rpc.pb.h"
297      "${INCLUDE_FILE}"
298      "${OUT_DIR}"
299      "${SOURCES}"
300      "${INPUTS}"
301      "${DEPS}"
302  )
303
304  # Create the library with the generated source files.
305  add_library("${NAME}.nanopb_rpc" INTERFACE)
306  target_include_directories("${NAME}.nanopb_rpc"
307    INTERFACE
308      "${OUT_DIR}/nanopb_rpc"
309  )
310  target_link_libraries("${NAME}.nanopb_rpc"
311    INTERFACE
312      "${NAME}.nanopb"
313      pw_build
314      pw_rpc.nanopb.client
315      pw_rpc.nanopb.method_union
316      pw_rpc.server
317      ${DEPS}
318  )
319  add_dependencies("${NAME}.nanopb_rpc" "${NAME}._generate.nanopb_rpc")
320endfunction(_pw_nanopb_rpc_library)
321