xref: /aosp_15_r20/external/pigweed/pw_protobuf_compiler/proto.cmake (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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# Args for generate_protos.py
181set(pw_protobuf_compiler_GENERATE_PROTOS_ARGS "" CACHE STRING "Args to generate_protos.py")
182
183# Internal function that invokes protoc through generate_protos.py.
184function(_pw_generate_protos TARGET LANGUAGE)
185  pw_parse_arguments(
186    NUM_POSITIONAL_ARGS
187      2
188    ONE_VALUE_ARGS
189      PLUGIN
190      INCLUDE_FILE
191      OUT_DIR
192    MULTI_VALUE_ARGS
193      OUTPUT_EXTS
194      SOURCES
195      INPUTS
196      DEPENDS
197  )
198
199  # Determine the names of the compiled output files.
200  pw_rebase_paths(outputs
201      "${arg_OUT_DIR}/${LANGUAGE}" "${arg_OUT_DIR}/sources" "${arg_SOURCES}"
202      "${arg_OUTPUT_EXTS}")
203
204  # Export the output files to the caller's scope so it can use them if needed.
205  set(generated_outputs "${outputs}" PARENT_SCOPE)
206
207  if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
208      foreach(source_file IN LISTS SOURCES)
209        get_filename_component(dir "${source_file}" DIRECTORY)
210        get_filename_component(name "${source_file}" NAME_WE)
211        set(arg_PLUGIN "${dir}/${name}.bat")
212      endforeach()
213  endif()
214
215  set(script "$ENV{PW_ROOT}/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py")
216  add_custom_command(
217    COMMAND
218      python3
219      "${script}"
220      --language "${LANGUAGE}"
221      --plugin-path "${arg_PLUGIN}"
222      --include-file "${arg_INCLUDE_FILE}"
223      --compile-dir "${arg_OUT_DIR}/sources"
224      --out-dir "${arg_OUT_DIR}/${LANGUAGE}"
225      --sources ${arg_SOURCES}
226      "${pw_protobuf_compiler_GENERATE_PROTOS_ARGS}"
227    DEPENDS
228      ${script}
229      ${arg_SOURCES}
230      ${arg_INPUTS}
231      ${arg_DEPENDS}
232    OUTPUT
233      ${outputs}
234  )
235  add_custom_target("${TARGET}._generate.${LANGUAGE}" DEPENDS ${outputs})
236  add_dependencies("${TARGET}._generate.${LANGUAGE}" "${TARGET}._sources")
237endfunction(_pw_generate_protos)
238
239# Internal function that creates a pwpb proto library.
240function(_pw_pwpb_library NAME)
241  pw_parse_arguments(
242    NUM_POSITIONAL_ARGS
243      1
244    ONE_VALUE_ARGS
245      INCLUDE_FILE
246      OUT_DIR
247    MULTI_VALUE_ARGS
248      SOURCES
249      INPUTS
250      DEPS
251  )
252
253  list(TRANSFORM arg_DEPS APPEND .pwpb)
254
255  _pw_generate_protos("${NAME}" pwpb
256    PLUGIN
257      "$ENV{PW_ROOT}/pw_protobuf/py/pw_protobuf/plugin.py"
258    OUTPUT_EXTS
259      ".pwpb.h"
260    INCLUDE_FILE
261      "${arg_INCLUDE_FILE}"
262    OUT_DIR
263      "${arg_OUT_DIR}"
264    SOURCES
265      ${arg_SOURCES}
266    INPUTS
267      ${arg_INPUTS}
268    DEPENDS
269      ${arg_DEPS}
270  )
271
272  # Create the library with the generated source files.
273  pw_add_library_generic("${NAME}.pwpb" INTERFACE
274    PUBLIC_INCLUDES
275      "${arg_OUT_DIR}/pwpb"
276    PUBLIC_DEPS
277      pw_build
278      pw_protobuf
279      pw_span
280      pw_string.string
281      ${arg_DEPS}
282  )
283  add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb")
284endfunction(_pw_pwpb_library)
285
286# Internal function that creates a pwpb_rpc library.
287function(_pw_pwpb_rpc_library NAME)
288  pw_parse_arguments(
289    NUM_POSITIONAL_ARGS
290      1
291    ONE_VALUE_ARGS
292      INCLUDE_FILE
293      OUT_DIR
294    MULTI_VALUE_ARGS
295      SOURCES
296      INPUTS
297      DEPS
298  )
299
300  # Determine the names of the output files.
301  list(TRANSFORM arg_DEPS APPEND .pwpb_rpc)
302
303  _pw_generate_protos("${NAME}" pwpb_rpc
304    PLUGIN
305      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_pwpb.py"
306    OUTPUT_EXTS
307      ".rpc.pwpb.h"
308    INCLUDE_FILE
309      "${arg_INCLUDE_FILE}"
310    OUT_DIR
311      "${arg_OUT_DIR}"
312    SOURCES
313      ${arg_SOURCES}
314    INPUTS
315      ${arg_INPUTS}
316    DEPENDS
317      ${arg_DEPS}
318      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py"
319  )
320
321  # Create the library with the generated source files.
322  pw_add_library_generic("${NAME}.pwpb_rpc" INTERFACE
323    PUBLIC_INCLUDES
324      "${arg_OUT_DIR}/pwpb_rpc"
325    PUBLIC_DEPS
326      "${NAME}.pwpb"
327      pw_build
328      pw_rpc.pwpb.client_api
329      pw_rpc.pwpb.server_api
330      pw_rpc.server
331      ${arg_DEPS}
332  )
333  add_dependencies("${NAME}.pwpb_rpc" "${NAME}._generate.pwpb_rpc")
334endfunction(_pw_pwpb_rpc_library)
335
336# Internal function that creates a raw_rpc proto library.
337function(_pw_raw_rpc_library NAME)
338  pw_parse_arguments(
339    NUM_POSITIONAL_ARGS
340      1
341    ONE_VALUE_ARGS
342      INCLUDE_FILE
343      OUT_DIR
344    MULTI_VALUE_ARGS
345      SOURCES
346      INPUTS
347      DEPS
348  )
349
350  list(TRANSFORM arg_DEPS APPEND .raw_rpc)
351
352  _pw_generate_protos("${NAME}" raw_rpc
353    PLUGIN
354      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_raw.py"
355    OUTPUT_EXTS
356      ".raw_rpc.pb.h"
357    INCLUDE_FILE
358      "${arg_INCLUDE_FILE}"
359    OUT_DIR
360      "${arg_OUT_DIR}"
361    SOURCES
362      ${arg_SOURCES}
363    INPUTS
364      ${arg_INPUTS}
365    DEPENDS
366      ${arg_DEPS}
367      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py"
368  )
369
370  # Create the library with the generated source files.
371  pw_add_library_generic("${NAME}.raw_rpc" INTERFACE
372    PUBLIC_INCLUDES
373      "${arg_OUT_DIR}/raw_rpc"
374    PUBLIC_DEPS
375      pw_build
376      pw_rpc.raw.server_api
377      pw_rpc.raw.client_api
378      pw_rpc.server
379      ${arg_DEPS}
380  )
381  add_dependencies("${NAME}.raw_rpc" "${NAME}._generate.raw_rpc")
382endfunction(_pw_raw_rpc_library)
383
384# Internal function that creates a nanopb proto library.
385function(_pw_nanopb_library NAME)
386  pw_parse_arguments(
387    NUM_POSITIONAL_ARGS
388      1
389    ONE_VALUE_ARGS
390      INCLUDE_FILE
391      OUT_DIR
392    MULTI_VALUE_ARGS
393      SOURCES
394      INPUTS
395      DEPS
396  )
397
398  list(TRANSFORM arg_DEPS APPEND .nanopb)
399
400  if("${dir_pw_third_party_nanopb}" STREQUAL "")
401    add_custom_target("${NAME}._generate.nanopb")  # Nothing to do
402    pw_add_error_target("${NAME}.nanopb"
403      MESSAGE
404        "Attempting to use pw_proto_library, but dir_pw_third_party_nanopb is "
405        "not set. Set dir_pw_third_party_nanopb to the path to the Nanopb "
406        "repository."
407    )
408  else()
409    # When compiling with the Nanopb plugin, the nanopb.proto file is already
410    # compiled internally, so skip recompiling it with protoc.
411    if("${arg_SOURCES}" MATCHES "nanopb\\.proto")
412      add_custom_target("${NAME}._generate.nanopb")  # Nothing to do
413      pw_add_library_generic("${NAME}.nanopb" INTERFACE
414        PUBLIC_DEPS
415          pw_build
416          pw_third_party.nanopb
417          ${arg_DEPS}
418      )
419    else()
420      _pw_generate_protos("${NAME}" nanopb
421        PLUGIN
422          "${dir_pw_third_party_nanopb}/generator/protoc-gen-nanopb"
423        OUTPUT_EXTS
424          ".pb.h"
425          ".pb.c"
426        INCLUDE_FILE
427          "${arg_INCLUDE_FILE}"
428        OUT_DIR
429          "${arg_OUT_DIR}"
430        SOURCES
431          ${arg_SOURCES}
432        INPUTS
433          ${arg_INPUTS}
434        DEPENDS
435          ${arg_DEPS}
436      )
437
438      # Create the library with the generated source files.
439      pw_add_library_generic("${NAME}.nanopb" STATIC
440        SOURCES
441          ${generated_outputs}
442        PUBLIC_INCLUDES
443          "${arg_OUT_DIR}/nanopb"
444        PUBLIC_DEPS
445          pw_build
446          pw_third_party.nanopb
447          ${arg_DEPS}
448      )
449    endif()
450
451    add_dependencies("${NAME}.nanopb" "${NAME}._generate.nanopb")
452
453    # Ensure that nanopb_pb2.py is generated to avoid race conditions.
454    add_dependencies("${NAME}._generate.nanopb"
455        pw_third_party.nanopb.generate_proto
456    )
457  endif()
458endfunction(_pw_nanopb_library)
459
460# Internal function that creates a nanopb_rpc library.
461function(_pw_nanopb_rpc_library NAME)
462  pw_parse_arguments(
463    NUM_POSITIONAL_ARGS
464      1
465    ONE_VALUE_ARGS
466      INCLUDE_FILE
467      OUT_DIR
468    MULTI_VALUE_ARGS
469      SOURCES
470      INPUTS
471      DEPS
472  )
473
474  # Determine the names of the output files.
475  list(TRANSFORM arg_DEPS APPEND .nanopb_rpc)
476
477  _pw_generate_protos("${NAME}" nanopb_rpc
478    PLUGIN
479      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_nanopb.py"
480    OUTPUT_EXTS
481      ".rpc.pb.h"
482    INCLUDE_FILE
483      "${arg_INCLUDE_FILE}"
484    OUT_DIR
485      "${arg_OUT_DIR}"
486    SOURCES
487      ${arg_SOURCES}
488    INPUTS
489      ${arg_INPUTS}
490    DEPENDS
491      ${arg_DEPS}
492      "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin.py"
493  )
494
495  # Create the library with the generated source files.
496  pw_add_library_generic("${NAME}.nanopb_rpc" INTERFACE
497    PUBLIC_INCLUDES
498      "${arg_OUT_DIR}/nanopb_rpc"
499    PUBLIC_DEPS
500      "${NAME}.nanopb"
501      pw_build
502      pw_rpc.nanopb.client_api
503      pw_rpc.nanopb.server_api
504      pw_rpc.server
505      ${arg_DEPS}
506  )
507  add_dependencies("${NAME}.nanopb_rpc" "${NAME}._generate.nanopb_rpc")
508endfunction(_pw_nanopb_rpc_library)
509