• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1load("@rules_proto//proto:defs.bzl", "ProtoInfo")
2load(
3    "//bazel:protobuf.bzl",
4    "get_include_directory",
5    "get_plugin_args",
6    "proto_path_to_generated_filename",
7)
8load(":grpc_util.bzl", "to_upper_camel_with_extension")
9
10_GRPC_PROTO_HEADER_FMT = "{}.pbrpc.h"
11_GRPC_PROTO_SRC_FMT = "{}.pbrpc.m"
12_PROTO_HEADER_FMT = "{}.pbobjc.h"
13_PROTO_SRC_FMT = "{}.pbobjc.m"
14_GENERATED_PROTOS_DIR = "_generated_protos"
15
16_GENERATE_HDRS = 1
17_GENERATE_SRCS = 2
18_GENERATE_NON_ARC_SRCS = 3
19
20def _generate_objc_impl(ctx):
21    """Implementation of the generate_objc rule."""
22    protos = [
23        f
24        for src in ctx.attr.deps
25        for f in src[ProtoInfo].transitive_imports.to_list()
26    ]
27
28    target_package = _join_directories([ctx.label.workspace_root, ctx.label.package])
29
30    files_with_rpc = [_label_to_full_file_path(f, target_package) for f in ctx.attr.srcs]
31
32    outs = []
33    for proto in protos:
34        outs += [_get_output_file_name_from_proto(proto, _PROTO_HEADER_FMT)]
35        outs += [_get_output_file_name_from_proto(proto, _PROTO_SRC_FMT)]
36
37        file_path = _get_full_path_from_file(proto)
38        if file_path in files_with_rpc:
39            outs += [_get_output_file_name_from_proto(proto, _GRPC_PROTO_HEADER_FMT)]
40            outs += [_get_output_file_name_from_proto(proto, _GRPC_PROTO_SRC_FMT)]
41
42    out_files = [ctx.actions.declare_file(out) for out in outs]
43    dir_out = _join_directories([
44        str(ctx.genfiles_dir.path),
45        target_package,
46        _GENERATED_PROTOS_DIR,
47    ])
48
49    arguments = []
50    if ctx.executable.plugin:
51        arguments += get_plugin_args(
52            ctx.executable.plugin,
53            [],
54            dir_out,
55            False,
56        )
57        tools = [ctx.executable.plugin]
58    arguments += ["--objc_out=" + dir_out]
59
60    arguments += ["--proto_path=."]
61    arguments += [
62        "--proto_path={}".format(get_include_directory(i))
63        for i in protos
64    ]
65
66    # Include the output directory so that protoc puts the generated code in the
67    # right directory.
68    arguments += ["--proto_path={}".format(dir_out)]
69    arguments += ["--proto_path={}".format(_get_directory_from_proto(proto)) for proto in protos]
70    arguments += [_get_full_path_from_file(proto) for proto in protos]
71
72    # create a list of well known proto files if the argument is non-None
73    well_known_proto_files = []
74    if ctx.attr.use_well_known_protos:
75        f = ctx.attr.well_known_protos.files.to_list()[0].dirname
76
77        # go two levels up so that #import "google/protobuf/..." is correct
78        arguments += ["-I{0}".format(f + "/../..")]
79        well_known_proto_files = ctx.attr.well_known_protos.files.to_list()
80    ctx.actions.run(
81        inputs = protos + well_known_proto_files,
82        tools = tools,
83        outputs = out_files,
84        executable = ctx.executable._protoc,
85        arguments = arguments,
86    )
87
88    return struct(files = depset(out_files))
89
90def _label_to_full_file_path(src, package):
91    if not src.startswith("//"):
92        # Relative from current package
93        if not src.startswith(":"):
94            # "a.proto" -> ":a.proto"
95            src = ":" + src
96        src = "//" + package + src
97
98    # Converts //path/to/package:File.ext to path/to/package/File.ext.
99    src = src.replace("//", "")
100    src = src.replace(":", "/")
101    if src.startswith("/"):
102        # "//:a.proto" -> "/a.proto" so remove the initial slash
103        return src[1:]
104    else:
105        return src
106
107def _get_output_file_name_from_proto(proto, fmt):
108    return proto_path_to_generated_filename(
109        _GENERATED_PROTOS_DIR + "/" +
110        _get_directory_from_proto(proto) + _get_slash_or_null_from_proto(proto) +
111        to_upper_camel_with_extension(_get_file_name_from_proto(proto), "proto"),
112        fmt,
113    )
114
115def _get_file_name_from_proto(proto):
116    return proto.path.rpartition("/")[2]
117
118def _get_slash_or_null_from_proto(proto):
119    """Potentially returns empty (if the file is in the root directory)"""
120    return proto.path.rpartition("/")[1]
121
122def _get_directory_from_proto(proto):
123    return proto.path.rpartition("/")[0]
124
125def _get_full_path_from_file(file):
126    gen_dir_length = 0
127
128    # if file is generated, then prepare to remote its root
129    # (including CPU architecture...)
130    if not file.is_source:
131        gen_dir_length = len(file.root.path) + 1
132
133    return file.path[gen_dir_length:]
134
135def _join_directories(directories):
136    massaged_directories = [directory for directory in directories if len(directory) != 0]
137    return "/".join(massaged_directories)
138
139generate_objc = rule(
140    attrs = {
141        "deps": attr.label_list(
142            mandatory = True,
143            allow_empty = False,
144            providers = [ProtoInfo],
145        ),
146        "plugin": attr.label(
147            default = "@com_github_grpc_grpc//src/compiler:grpc_objective_c_plugin",
148            executable = True,
149            providers = ["files_to_run"],
150            cfg = "host",
151        ),
152        "srcs": attr.string_list(
153            mandatory = False,
154            allow_empty = True,
155        ),
156        "use_well_known_protos": attr.bool(
157            mandatory = False,
158            default = False,
159        ),
160        "well_known_protos": attr.label(
161            default = "@com_google_protobuf//:well_known_protos",
162        ),
163        "_protoc": attr.label(
164            default = Label("//external:protocol_compiler"),
165            executable = True,
166            cfg = "host",
167        ),
168    },
169    output_to_genfiles = True,
170    implementation = _generate_objc_impl,
171)
172
173def _group_objc_files_impl(ctx):
174    suffix = ""
175    if ctx.attr.gen_mode == _GENERATE_HDRS:
176        suffix = "h"
177    elif ctx.attr.gen_mode == _GENERATE_SRCS:
178        suffix = "pbrpc.m"
179    elif ctx.attr.gen_mode == _GENERATE_NON_ARC_SRCS:
180        suffix = "pbobjc.m"
181    else:
182        fail("Undefined gen_mode")
183    out_files = [
184        file
185        for file in ctx.attr.src.files.to_list()
186        if file.basename.endswith(suffix)
187    ]
188    return struct(files = depset(out_files))
189
190generate_objc_hdrs = rule(
191    attrs = {
192        "src": attr.label(
193            mandatory = True,
194        ),
195        "gen_mode": attr.int(
196            default = _GENERATE_HDRS,
197        ),
198    },
199    implementation = _group_objc_files_impl,
200)
201
202generate_objc_srcs = rule(
203    attrs = {
204        "src": attr.label(
205            mandatory = True,
206        ),
207        "gen_mode": attr.int(
208            default = _GENERATE_SRCS,
209        ),
210    },
211    implementation = _group_objc_files_impl,
212)
213
214generate_objc_non_arc_srcs = rule(
215    attrs = {
216        "src": attr.label(
217            mandatory = True,
218        ),
219        "gen_mode": attr.int(
220            default = _GENERATE_NON_ARC_SRCS,
221        ),
222    },
223    implementation = _group_objc_files_impl,
224)
225