xref: /aosp_15_r20/external/cronet/third_party/protobuf/proto_library.gni (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2014 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# Compile a protocol buffer.
6#
7# Protobuf parameters:
8#
9#   proto_in_dir (optional)
10#       Specifies the path relative to the current BUILD.gn file where
11#       proto files are located and the directory structure of
12#       this proto library starts.
13#
14#       This option can be calculated automatically but it will raise an
15#       assertion error if any nested directories are found.
16#
17#   proto_out_dir (optional)
18#       Specifies the path suffix that output files are generated under.
19#       This path will be appended to |root_gen_dir|, but for python stubs
20#       it will be appended to |root_build_dir|/pyproto.
21#
22#   generate_python (optional, default true)
23#       Generate Python protobuf stubs.
24#
25#   generate_cc (optional, default true)
26#       Generate C++ protobuf stubs.
27#
28#   generate_javascript (optional, default false)
29#       Generate Javascript protobuf stubs.
30#
31#   generate_library (optional, default true)
32#       Generate a "static_library" target for linking with the generated code.
33#
34#   generate_py_runtime (optional, default false)
35#       Generates a "_py_runtime"-suffixed target for test targets that need the
36#       Python stubs available at runtime.
37#
38#   cc_generator_options (optional)
39#       List of extra flags passed to the protocol compiler.  If you need to
40#       add an EXPORT macro to a protobuf's C++ header, set the
41#       'cc_generator_options' variable with the value:
42#       'dllexport_decl=FOO_EXPORT:' (note trailing colon).
43#
44#       It is likely you also need to #include a file for the above EXPORT
45#       macro to work (see cc_include) and set
46#       component_build_force_source_set = true.
47#
48#   cc_include (optional)
49#       String listing an extra include that should be passed.
50#       Example: cc_include = "foo/bar.h"
51#
52#   generator_plugin_label (optional)
53#       GN label for plugin executable which generates custom cc stubs.
54#       Don't specify a toolchain, host toolchain is assumed.
55#
56#   generator_plugin_script (optional)
57#       Path to plugin script. Mutually exclusive with |generator_plugin_label|.
58#
59#   generator_plugin_script_deps (optional)
60#       List of additional files required for generator plugin script.
61#
62#   generator_plugin_suffix[es] (required if using a plugin)
63#       Suffix (before extension) for generated .cc and .h files
64#       or list of suffixes for all files (with extensions).
65#
66#   generator_plugin_options (optional)
67#       Extra flags passed to the plugin. See cc_generator_options.
68#
69#   deps (optional)
70#       DEPRECATED: use proto_deps or link_deps instead (crbug.com/938011).
71#       Additional dependencies.
72#
73#   link_deps (optional)
74#       Additional dependencies linked to library.
75#
76#   proto_deps (optional)
77#       Additional dependencies required before running protoc.
78#       e.g. proto file generating action.
79#
80#   proto_data_sources (optional)
81#       Additional proto sources that will only be used for references
82#       and will not be compiled.
83#       This should be used in conjunction with import_dirs.
84#
85#   use_protobuf_full (optional)
86#       If adding protobuf library would be required, adds protobuf_full to deps
87#       instead of protobuf_lite.
88#
89#   import_dirs (optional)
90#       A list of extra import directories to be passed to protoc compiler.
91#       WARNING: This circumvents proto checkdeps, and should only be used when
92#       needed, typically when proto files cannot cleanly import through
93#       absolute paths, such as for third_party or generated .proto files.
94#       http://crbug.com/691451 tracks fixing this.
95#
96# Parameters for compiling the generated code:
97#
98#   force_source_set (Default=false)
99#       When set true the generated code will be compiled as a source set.
100#       This can be useful if you need to export the generated symbols from a
101#       shared library. You should use this carefully, as you probably only
102#       want this if your dependencies are *always* shared libraries. Most
103#       of the time, you probably want `component_build_force_source_set`
104#       instead (see the next option).
105#   component_build_force_source_set (Default=false)
106#       When set true the generated code will be compiled as a source set in
107#       the component build. This does not affect static builds.  If you are
108#       exporting symbols from a component, this is required to prevent those
109#       symbols from being stripped. If you're not using dllexports in
110#       cc_generator_options, it's usually best to leave this false.
111#
112#   defines (optional)
113#       Defines to supply to the source set that compiles the generated source
114#       code.
115#
116#   extra_configs (optional)
117#       A list of config labels that will be appended to the configs applying
118#       to the source set.
119#
120#   remove_configs (optional)
121#       A list of config labels that will be removed from the configs apllying
122#       to the source set.
123#
124#   propagate_imports_configs (optional)
125#       A boolean value (defaults to true) that specifies whether the config
126#       generated for the library's import directories will be propagated to
127#       dependents as one of the library target's public_configs. See
128#       crbug.com/1043279#c11 and crbug.com/gn/142 for why this option exists.
129#       WARNING: If set to false, the embedder target is responsible for
130#       propagating a suitable config, so that any dependent headers can resolve
131#       includes generated by proto imports.
132#
133# Example:
134#  proto_library("mylib") {
135#    sources = [
136#      "foo.proto",
137#    ]
138#  }
139
140import("//build/config/cronet/config.gni")
141import("//build/config/sanitizers/sanitizers.gni")
142import("//build/toolchain/kythe.gni")
143
144declare_args() {
145  # Allows subprojects to omit javascript dependencies (e.g.) closure_compiler
146  # and google-closure-library.
147  enable_js_protobuf = !is_cronet_build
148}
149
150if (enable_js_protobuf) {
151  import("//third_party/closure_compiler/compile_js.gni")
152}
153
154if (host_os == "win") {
155  _host_executable_suffix = ".exe"
156} else {
157  _host_executable_suffix = ""
158}
159
160_protoc_label = "//third_party/protobuf:protoc($host_toolchain)"
161_protoc_path = get_label_info(_protoc_label, "root_out_dir") + "/protoc" +
162               _host_executable_suffix
163_protoc_gen_js_label =
164    "//third_party/protobuf-javascript:protoc-gen-js($host_toolchain)"
165_protoc_gen_js_path = get_label_info(_protoc_gen_js_label, "root_out_dir") +
166                      "/protoc-gen-js" + _host_executable_suffix
167
168template("proto_library") {
169  assert(defined(invoker.sources), "Need sources for proto_library")
170  proto_sources = invoker.sources
171
172  if (defined(invoker.generate_cc)) {
173    generate_cc = invoker.generate_cc
174  } else {
175    generate_cc = true
176  }
177
178  if (defined(invoker.generate_python)) {
179    generate_python = invoker.generate_python
180  } else {
181    generate_python = true
182  }
183
184  if (defined(invoker.generate_javascript)) {
185    generate_javascript = invoker.generate_javascript
186  } else {
187    generate_javascript = false
188  }
189
190  if (defined(invoker.generate_descriptor)) {
191    generate_descriptor = invoker.generate_descriptor
192  } else {
193    generate_descriptor = ""
194  }
195
196  if (defined(invoker.generate_py_runtime)) {
197    generate_py_runtime = invoker.generate_py_runtime
198  } else {
199    generate_py_runtime = false
200  }
201  if (generate_py_runtime) {
202    generate_python = true
203  }
204
205  # exclude_imports is only used for generating the descriptor. Therefore, the
206  # check needs to be here to avoid complaints from GN about the unused
207  # variable.
208  if (generate_descriptor != "") {
209    if (defined(invoker.exclude_imports)) {
210      exclude_imports = invoker.exclude_imports
211    } else {
212      exclude_imports = false
213    }
214  }
215
216  if (defined(invoker.generator_plugin_label)) {
217    # Straightforward way to get the name of executable doesn't work because
218    # |root_out_dir| and |root_build_dir| may differ in cross-compilation and
219    # also Windows executables have .exe at the end.
220
221    plugin_host_label = invoker.generator_plugin_label + "($host_toolchain)"
222    plugin_path =
223        get_label_info(plugin_host_label, "root_out_dir") + "/" +
224        get_label_info(plugin_host_label, "name") + _host_executable_suffix
225    generate_with_plugin = true
226  } else if (defined(invoker.generator_plugin_script)) {
227    plugin_path = invoker.generator_plugin_script
228    generate_with_plugin = true
229  } else {
230    generate_with_plugin = false
231  }
232
233  if (generate_with_plugin) {
234    if (defined(invoker.generator_plugin_suffix)) {
235      generator_plugin_suffixes = [
236        "${invoker.generator_plugin_suffix}.h",
237        "${invoker.generator_plugin_suffix}.cc",
238      ]
239    } else {
240      generator_plugin_suffixes = invoker.generator_plugin_suffixes
241    }
242  }
243
244  if (defined(invoker.proto_in_dir)) {
245    proto_in_dir = invoker.proto_in_dir
246    has_nested_dirs = false
247    foreach(proto_source, proto_sources) {
248      if (get_path_info(proto_source, "dir") != proto_in_dir) {
249        has_nested_dirs = true
250      }
251    }
252  } else {
253    proto_in_dir = get_path_info(proto_sources[0], "dir")
254    has_nested_dirs = false
255
256    # Sanity check, |proto_in_dir| should be defined to allow sub-directories.
257    foreach(proto_source, proto_sources) {
258      assert(get_path_info(proto_source, "dir") == proto_in_dir,
259             "Please define |proto_in_dir| to allow nested directories.")
260    }
261  }
262
263  # Avoid absolute path because of the assumption that |proto_in_dir| is
264  # relative to the directory of current BUILD.gn file.
265  proto_in_dir = rebase_path(proto_in_dir, ".")
266
267  if (defined(invoker.proto_out_dir)) {
268    proto_out_dir = invoker.proto_out_dir
269  } else {
270    # Absolute path to the directory of current BUILD.gn file excluding "//".
271    proto_out_dir = rebase_path(".", "//")
272    if (proto_in_dir != ".") {
273      proto_out_dir += "/$proto_in_dir"
274    }
275  }
276
277  # We need both absolute path to use in GN statements and a relative one
278  # to pass to external script.
279  if (generate_cc || generate_with_plugin) {
280    cc_out_dir = "$root_gen_dir/" + proto_out_dir
281    rel_cc_out_dir = rebase_path(cc_out_dir, root_build_dir)
282  }
283  if (generate_python) {
284    py_out_dir = "$root_out_dir/pyproto/" + proto_out_dir
285    rel_py_out_dir = rebase_path(py_out_dir, root_build_dir)
286  }
287  if (generate_javascript) {
288    js_out_dir = "$root_out_dir/jsproto/" + proto_out_dir
289    rel_js_out_dir = rebase_path(js_out_dir, root_build_dir)
290  }
291  if (generate_descriptor != "") {
292    descriptor_out =
293        "$root_gen_dir/" + proto_out_dir + "/" + generate_descriptor
294    rel_descriptor_out = rebase_path(descriptor_out, root_build_dir)
295  }
296
297  protos = rebase_path(invoker.sources, proto_in_dir)
298  protogens = []
299  protogens_py = []
300  protogens_cc = []
301  protogens_js = []
302
303  # Whether source code bindings should be generated.
304  generate_sources = generate_cc || generate_python || generate_with_plugin ||
305                     generate_javascript
306
307  # Whether library should be generated.
308  # Library is not needed when proto_library is used to generate binary descriptor, in which case
309  # corresponding library target should be omitted entirely.
310  if (defined(invoker.generate_library)) {
311    generate_library = invoker.generate_library
312  } else {
313    generate_library = generate_sources
314  }
315
316  # List output files.
317  if (generate_sources) {
318    foreach(proto, protos) {
319      proto_dir = get_path_info(proto, "dir")
320      proto_name = get_path_info(proto, "name")
321      proto_path = proto_dir + "/" + proto_name
322
323      if (generate_cc) {
324        protogens_cc += [
325          "$cc_out_dir/$proto_path.pb.h",
326          "$cc_out_dir/$proto_path.pb.cc",
327        ]
328      }
329      if (generate_python) {
330        protogens_py += [ "$py_out_dir/${proto_path}_pb2.py" ]
331      }
332      if (generate_with_plugin) {
333        foreach(suffix, generator_plugin_suffixes) {
334          protogens_cc += [ "$cc_out_dir/${proto_path}${suffix}" ]
335        }
336      }
337      if (generate_javascript) {
338        protogens_js += [ "$js_out_dir/${proto_path}.js" ]
339      }
340    }
341  }
342
343  # If descriptor needs to be generated, it should be added to list of outputs once.
344  if (generate_descriptor != "") {
345    protogens += [ descriptor_out ]
346  }
347
348  action_name = "${target_name}_gen"
349  source_set_name = target_name
350  javascript_name = "${target_name}_js"
351  py_runtime_name = "${target_name}_py_runtime"
352
353  # Generate protobuf stubs.
354  action(action_name) {
355    visibility = [
356      ":$javascript_name",
357      ":$py_runtime_name",
358      ":$source_set_name",
359    ]
360    script = "//tools/protoc_wrapper/protoc_wrapper.py"
361    args = protos
362
363    sources = proto_sources
364    outputs =
365        get_path_info(protogens + protogens_cc + protogens_js + protogens_py,
366                      "abspath")
367
368    if (defined(invoker.testonly)) {
369      testonly = invoker.testonly
370    }
371
372    args += [
373      # Wrapper should never pick a system protoc.
374      # Path should be rebased because |root_build_dir| for current toolchain
375      # may be different from |root_out_dir| of protoc built on host toolchain.
376      "--protoc",
377      "./" + rebase_path(_protoc_path, root_build_dir),
378      "--proto-in-dir",
379      rebase_path(proto_in_dir, root_build_dir),
380    ]
381
382    if (generate_cc) {
383      args += [
384        "--cc-out-dir",
385        rel_cc_out_dir,
386      ]
387      if (enable_kythe_annotations) {
388        args += [ "--enable-kythe-annotation" ]
389      }
390      if (defined(invoker.cc_generator_options)) {
391        args += [
392          "--cc-options",
393          invoker.cc_generator_options,
394        ]
395      }
396      if (defined(invoker.cc_include)) {
397        args += [
398          "--include",
399          invoker.cc_include,
400        ]
401      }
402    }
403
404    if (generate_python) {
405      args += [
406        "--py-out-dir",
407        rel_py_out_dir,
408      ]
409    }
410
411    if (generate_javascript) {
412      args += [
413        "--js-out-dir",
414        rel_js_out_dir,
415        "--protoc-gen-js",
416        "./" + rebase_path(_protoc_gen_js_path, root_build_dir),
417      ]
418    }
419
420    if (generate_with_plugin) {
421      args += [
422        "--plugin",
423        rebase_path(plugin_path, root_build_dir),
424        "--plugin-out-dir",
425        rel_cc_out_dir,
426      ]
427      if (defined(invoker.generator_plugin_options)) {
428        args += [
429          "--plugin-options",
430          invoker.generator_plugin_options,
431        ]
432      }
433    }
434
435    if (generate_descriptor != "") {
436      depfile =
437          "$root_gen_dir/" + proto_out_dir + "/" + generate_descriptor + ".d"
438      rel_depfile = rebase_path(depfile, root_build_dir)
439
440      if (exclude_imports) {
441        args += [ "--exclude-imports" ]
442      }
443
444      args += [
445        "--descriptor-set-out",
446        rel_descriptor_out,
447        "--descriptor-set-dependency-file",
448        rel_depfile,
449      ]
450    }
451    inputs = []
452    if (defined(invoker.proto_data_sources)) {
453      inputs += invoker.proto_data_sources
454    }
455
456    if (defined(invoker.import_dirs)) {
457      foreach(path, invoker.import_dirs) {
458        args += [ "--import-dir=" + rebase_path(path, root_build_dir) ]
459      }
460    }
461
462    # System protoc is not used so it's necessary to build a chromium one.
463    inputs += [ _protoc_path ]
464    deps = [ _protoc_label ]
465
466    if (enable_js_protobuf) {
467      inputs += [ _protoc_gen_js_path ]
468      deps += [ _protoc_gen_js_label ]
469    }
470
471    if (generate_with_plugin) {
472      inputs += [ plugin_path ]
473      if (defined(invoker.generator_plugin_script_deps)) {
474        # Additional scripts for plugin.
475        inputs += invoker.generator_plugin_script_deps
476      }
477      if (defined(plugin_host_label)) {
478        # Action depends on native generator plugin but for host toolchain only.
479        deps += [ plugin_host_label ]
480      }
481    }
482
483    # The deps may have steps that have to run before running protoc.
484    if (defined(invoker.proto_deps)) {
485      deps += invoker.proto_deps
486    }
487    if (defined(invoker.deps)) {
488      deps += invoker.deps
489    }
490  }
491
492  if (!generate_library) {
493    # If only descriptor is required, just generate a group wrapper for action output.
494    link_target_type = "group"
495  } else if ((defined(invoker.force_source_set) && invoker.force_source_set) ||
496             (defined(invoker.component_build_force_source_set) &&
497              invoker.component_build_force_source_set && is_component_build)) {
498    # Option to disable building a library in component build.
499    link_target_type = "source_set"
500  } else {
501    link_target_type = "static_library"
502  }
503
504  # Generated files may include other generated headers. These includes always
505  # use relative paths starting at |cc_out_dir|.
506  # However there is no necessity to add an additional directory, if all protos
507  # are located in the same directory which is in the search path by default.
508  config_name = "${target_name}_config"
509  config(config_name) {
510    include_dirs = []
511    if (has_nested_dirs && generate_cc) {
512      include_dirs += [ cc_out_dir ]
513    }
514    if (defined(invoker.import_dirs)) {
515      foreach(path, invoker.import_dirs) {
516        include_dirs += [ "$root_gen_dir/" + rebase_path(path, "//") ]
517      }
518    }
519  }
520
521  # Build generated javascript stubs.
522  if (generate_javascript) {
523    js_library(javascript_name) {
524      forward_variables_from(invoker,
525                             [
526                               "testonly",
527                               "visibility",
528                             ])
529
530      sources = protogens_js
531
532      deps = [ "//third_party/protobuf:js_proto" ]
533
534      extra_deps = [ ":$action_name" ]
535    }
536  }
537
538  # Build generated protobuf stubs as libary or source set.
539  target(link_target_type, target_name) {
540    forward_variables_from(invoker,
541                           [
542                             "defines",
543                             "testonly",
544                             "visibility",
545                           ])
546
547    if (generate_library) {
548      sources = get_path_info(protogens_cc, "abspath")
549
550      if (defined(invoker.remove_configs)) {
551        configs -= invoker.remove_configs
552      }
553
554      if (defined(invoker.extra_configs)) {
555        configs += invoker.extra_configs
556      }
557
558      # Remove Sanitizer and coverage instrumentation for a performance boost when
559      # fuzzing, since the only fuzzers that use protobuf are libprotobuf-mutator
560      # based fuzzers, and they don't actually target protobuf code.
561      configs -= not_fuzzed_remove_configs
562      configs += [ "//build/config/sanitizers:not_fuzzed" ]
563    }
564
565    public_configs = [
566      "//third_party/protobuf:using_proto",
567      "//third_party/protobuf:allow_deprecated_proto_fields",
568    ]
569    public_deps = []
570
571    if (generate_cc || generate_with_plugin) {
572      # Not necessary if all protos are located in the same directory.
573      if (has_nested_dirs || defined(invoker.import_dirs)) {
574        # By default, propagate the config for |include_dirs| to dependent
575        # targets, so that public imports can be resolved to corresponding
576        # header files. In some cases, the embedder target handles include
577        # directory propagation itself, e.g. via a common config.
578        propagate_imports_configs =
579            !defined(invoker.propagate_imports_configs) ||
580            invoker.propagate_imports_configs
581        if (propagate_imports_configs) {
582          public_configs += [ ":$config_name" ]
583        } else {
584          # Embedder handles include directory propagation to dependents.
585          configs += [ ":$config_name" ]
586        }
587      }
588
589      # If using built-in cc generator, the resulting headers reference headers
590      # within protobuf_lite. Hence, dependencies require those headers too.
591      # If using generator plugin, extra deps should be resolved by the invoker.
592      if (generate_cc) {
593        if (defined(invoker.use_protobuf_full) &&
594            invoker.use_protobuf_full == true) {
595          public_deps += [ "//third_party/protobuf:protobuf_full" ]
596        } else {
597          public_deps += [ "//third_party/protobuf:protobuf_lite" ]
598        }
599
600        if (is_win) {
601          cflags = [
602            # disable: C4125 decimal digit terminates octal escape sequence
603            # Protoc generates such sequences frequently, there's no obvious
604            # superior replacement behavior. Since this code is autogenerated,
605            # the warning would never catch a legitimate bug.
606            "/wd4125",
607          ]
608        }
609      }
610    }
611
612    public_deps += [ ":$action_name" ]
613    deps = []
614
615    # This will link any libraries in the deps (the use of invoker.deps in the
616    # action won't link it).
617    if (defined(invoker.deps)) {
618      deps += invoker.deps
619    }
620    if (defined(invoker.link_deps)) {
621      deps += invoker.link_deps
622    }
623  }
624
625  if (generate_py_runtime) {
626    group(py_runtime_name) {
627      data = protogens_py
628      deps = [
629        ":$action_name",
630        "//third_party/protobuf:py_proto_runtime",
631      ]
632    }
633  }
634}
635
636# Convert a protocol buffer between text and binary formats.
637# This can be used to run protoc with the --encode or --decode options.
638# Parameters:
639#
640#   sources: list of string
641#       The sources to loop over and run protoc on
642#
643#   inputs: list of string
644#       The file dependencies for the action. This should be the list of .proto
645#       files involved in the conversion operation.
646#
647#   output_pattern: string
648#       A path pattern with source expansion variables (like source_name_part)
649#       for where the result of conversion should be placed.
650#
651#   deps: (optional) list of label
652#       Additional dependencies for the target.
653#
654#   args: list of string
655#       Arguments to pass to the protoc tool. This could include -I for include
656#       paths, as well as the name of the proto file.
657#
658#
659# Example to convert a .textproto to a .binarybp:
660#   protoc_convert("convert_foo") {
661#     sources = [
662#       "test/data/example1.textproto",
663#       "test/data/example2.textproto",
664#     ]
665#     inputs = [
666#       "//component/core/foo.proto",
667#     ]
668#     output_pattern = "$target_gen_dir/foo_data/{{source_name_part}}.binarypb"
669#     args = [
670#       "--encode=foo.FooMessage",
671#       "-I",
672#       rebase_path("//"),
673#       "component/core/foo.proto",
674#     ]
675#   }
676template("protoc_convert") {
677  action_foreach(target_name) {
678    script = "//tools/protoc_wrapper/protoc_convert.py"
679
680    sources = invoker.sources
681
682    inputs = invoker.inputs
683
684    deps = [ _protoc_label ]
685    if (defined(invoker.deps)) {
686      deps += invoker.deps
687    }
688
689    if (defined(invoker.testonly)) {
690      testonly = invoker.testonly
691    }
692
693    outputs = [ invoker.output_pattern ]
694
695    args = [
696             "--protoc",
697             "./" + rebase_path(_protoc_path, root_build_dir),
698             "--infile",
699             "{{source}}",
700             "--outfile",
701             rebase_path(invoker.output_pattern),
702           ] + invoker.args
703  }
704}
705