xref: /aosp_15_r20/build/bazel/rules/proto_file_utils.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://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,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15load("@bazel_skylib//lib:paths.bzl", "paths")
16load("@bazel_skylib//lib:sets.bzl", "sets")
17
18def _generate_and_declare_output_files(
19        ctx,
20        proto_rel_paths,
21        type_dictionary):
22    ret = {}
23    for typ in type_dictionary:
24        ret[typ] = []
25
26    for proto_rel_path in proto_rel_paths:
27        for typ, ext in type_dictionary.items():
28            # prefix with label.name to prevent collisions between targets
29            # if proto compliation becomes an aspect, can prefix with output
30            # information instead to allow reuse, e.g. multiple cc `lite`
31            # libraries containing the same proto file
32            out_name = paths.join(ctx.label.name, paths.replace_extension(proto_rel_path, ext))
33            declared = ctx.actions.declare_file(out_name)
34            ret[typ].append(declared)
35
36    return ret
37
38def _generate_jar_proto_action(
39        proto_infos,
40        protoc,
41        ctx,
42        out_flags = [],
43        plugin_executable = None,
44        out_arg = None,
45        mnemonic = "ProtoGen",
46        transitive_proto_infos = []):
47    jar_basename = ctx.label.name + "-proto_gen"
48    jar_name = jar_basename + "-src.jar"
49    jar_file = ctx.actions.declare_file(jar_name)
50
51    _generate_proto_action(
52        proto_infos = proto_infos,
53        protoc = protoc,
54        ctx = ctx,
55        out_flags = out_flags,
56        plugin_executable = plugin_executable,
57        out_arg = out_arg,
58        mnemonic = mnemonic,
59        output_file = jar_file,
60        transitive_proto_infos = transitive_proto_infos,
61    )
62
63    srcjar_name = jar_basename + ".srcjar"
64    srcjar_file = ctx.actions.declare_file(srcjar_name)
65    ctx.actions.symlink(
66        output = srcjar_file,
67        target_file = jar_file,
68    )
69
70    return srcjar_file
71
72def _generate_proto_action(
73        proto_infos,
74        protoc,
75        ctx,
76        type_dictionary = None,
77        out_flags = [],
78        plugin_executable = None,
79        out_arg = None,
80        mnemonic = "ProtoGen",
81        output_file = None,
82        transitive_proto_infos = []):
83    """ Utility function for creating proto_compiler action.
84
85    Args:
86      proto_infos: A list of ProtoInfo.
87      protoc: proto compiler executable.
88      ctx: context, used for declaring new files only.
89      type_dictionary: a dictionary of types to output extensions
90      out_flags: protoc output flags
91      plugin_executable: plugin executable file
92      out_arg: as appropriate, if plugin_executable and out_arg are both supplied, plugin_executable is preferred
93      mnemonic: (optional) a string to describe the proto compilation action
94      output_file: (optional) File, used to specify a specific file for protoc output (typically a JAR file)
95
96    Returns:
97      Dictionary with declared files grouped by type from the type_dictionary.
98    """
99
100    # TODO(B/245629074): Don't build external/protobuf if it is provided in
101    #  toolchain already.
102    proto_srcs = []
103    proto_rel_srcs = []
104    proto_source_root_list = sets.make()
105    transitive_proto_srcs_list = []
106    transitive_proto_path_list = sets.make()
107
108    for proto_info in proto_infos:
109        sets.insert(proto_source_root_list, proto_info.proto_source_root)
110        proto_srcs.extend(proto_info.direct_sources)
111        proto_rel_srcs.extend([paths.relativize(p.path, proto_info.proto_source_root) for p in proto_info.direct_sources])
112        transitive_proto_srcs_list.append(proto_info.transitive_imports)
113        for p in proto_info.transitive_proto_path.to_list():
114            sets.insert(transitive_proto_path_list, p)
115
116    for transitive_proto_info in transitive_proto_infos:
117        sets.insert(transitive_proto_path_list, transitive_proto_info.proto_source_root)
118        transitive_proto_srcs_list.append(depset(transitive_proto_info.direct_sources))
119
120    protoc_out_name = paths.join(ctx.bin_dir.path, ctx.label.package)
121
122    if output_file:
123        protoc_out_name = paths.join(protoc_out_name, output_file.basename)
124        out_files = {
125            "out": [output_file],
126        }
127    else:
128        protoc_out_name = paths.join(protoc_out_name, ctx.label.name)
129        out_files = _generate_and_declare_output_files(
130            ctx,
131            proto_rel_srcs,
132            type_dictionary,
133        )
134
135    tools = []
136    args = ctx.actions.args()
137    if plugin_executable:
138        tools.append(plugin_executable)
139        args.add("--plugin=protoc-gen-PLUGIN=" + plugin_executable.path)
140        args.add("--PLUGIN_out=" + ",".join(out_flags) + ":" + protoc_out_name)
141    else:
142        args.add("{}={}:{}".format(out_arg, ",".join(out_flags), protoc_out_name))
143
144    # the order matters so we add the source roots first
145    args.add_all(["-I" + p for p in sets.to_list(proto_source_root_list)])
146    args.add_all(["-I" + p for p in sets.to_list(transitive_proto_path_list)])
147    args.add_all(["-I{0}={1}".format(f.short_path, f.path) for t in transitive_proto_srcs_list for f in t.to_list()])
148
149    args.add_all([f.path for f in proto_srcs])
150
151    inputs = depset(
152        direct = proto_srcs,
153        transitive = transitive_proto_srcs_list,
154    )
155
156    outputs = []
157    for outs in out_files.values():
158        outputs.extend(outs)
159
160    ctx.actions.run(
161        inputs = inputs,
162        executable = protoc,
163        tools = tools,
164        outputs = outputs,
165        arguments = [args],
166        mnemonic = mnemonic,
167    )
168    return out_files
169
170proto_file_utils = struct(
171    generate_proto_action = _generate_proto_action,
172    generate_jar_proto_action = _generate_jar_proto_action,
173)
174