1"""Implementation of the aspect that powers the upb_*_proto_library() rules.""" 2 3load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") 4load("//bazel/common:proto_common.bzl", "proto_common") 5load("//bazel/common:proto_info.bzl", "ProtoInfo") 6load(":upb_proto_library_internal/cc_library_func.bzl", "cc_library_func") 7load(":upb_proto_library_internal/copts.bzl", "UpbProtoLibraryCoptsInfo") 8 9_is_google3 = False 10 11GeneratedSrcsInfo = provider( 12 "Provides generated headers and sources", 13 fields = { 14 "srcs": "list of srcs", 15 "hdrs": "list of hdrs", 16 }, 17) 18 19def output_dir(ctx, proto_info): 20 """Returns the output directory where generated proto files will be placed. 21 22 Args: 23 ctx: Rule context. 24 proto_info: ProtoInfo provider. 25 26 Returns: 27 A string specifying the output directory 28 """ 29 proto_root = proto_info.proto_source_root 30 if proto_root.startswith(ctx.bin_dir.path): 31 path = proto_root 32 else: 33 path = ctx.bin_dir.path + "/" + proto_root 34 35 if proto_root == ".": 36 path = ctx.bin_dir.path 37 return path 38 39def _concat_lists(lists): 40 ret = [] 41 for lst in lists: 42 ret = ret + lst 43 return ret 44 45def _merge_generated_srcs(srcs): 46 return GeneratedSrcsInfo( 47 srcs = _concat_lists([s.srcs for s in srcs]), 48 hdrs = _concat_lists([s.hdrs for s in srcs]), 49 ) 50 51def _get_implicit_weak_field_sources(ctx, proto_info): 52 # Creating one .cc file for each Message in a proto allows the linker to be more aggressive 53 # about removing unused classes. However, since the number of outputs won't be known at Blaze 54 # analysis time, all of the generated source files are put in a directory and a TreeArtifact is 55 # used to represent them. 56 proto_artifacts = [] 57 for proto_source in proto_info.direct_sources: 58 # We can have slashes in the target name. For example, proto_source can be: 59 # dir/a.proto. However proto_source.basename will return a.proto, when in reality 60 # the BUILD file declares it as dir/a.proto, because target name contains a slash. 61 # There is no good workaround for this. 62 # I am using ctx.label.package to check if the name of the target contains slash or not. 63 # This is similar to what declare_directory does. 64 if not proto_source.short_path.startswith(ctx.label.package): 65 fail("This should never happen, proto source {} path does not start with {}.".format( 66 proto_source.short_path, 67 ctx.label.package, 68 )) 69 proto_source_name = proto_source.short_path[len(ctx.label.package) + 1:] 70 last_dot = proto_source_name.rfind(".") 71 if last_dot != -1: 72 proto_source_name = proto_source_name[:last_dot] 73 proto_artifacts.append(ctx.actions.declare_directory(proto_source_name + ".upb_weak_minitables")) 74 75 return proto_artifacts 76 77def _get_feature_configuration(ctx, cc_toolchain, proto_info): 78 requested_features = list(ctx.features) 79 80 # Disable the whole-archive behavior for protobuf generated code when the 81 # proto_one_output_per_message feature is enabled. 82 requested_features.append("disable_whole_archive_for_static_lib_if_proto_one_output_per_message") 83 unsupported_features = list(ctx.disabled_features) 84 if len(proto_info.direct_sources) != 0: 85 requested_features.append("header_modules") 86 else: 87 unsupported_features.append("header_modules") 88 return cc_common.configure_features( 89 ctx = ctx, 90 cc_toolchain = cc_toolchain, 91 requested_features = requested_features, 92 unsupported_features = unsupported_features, 93 ) 94 95def _generate_srcs_list(ctx, generator, proto_info): 96 if len(proto_info.direct_sources) == 0: 97 return GeneratedSrcsInfo(srcs = [], hdrs = [], includes = []) 98 99 ext = "." + generator 100 srcs = [] 101 hdrs = proto_common.declare_generated_files( 102 ctx.actions, 103 extension = ext + ".h", 104 proto_info = proto_info, 105 ) 106 if not (generator == "upb" and _is_google3): 107 # TODO: The OSS build should also exclude this file for the upb generator, 108 # as it is empty and unnecessary. We only added it to make the OSS build happy on 109 # Windows and macOS. 110 srcs += proto_common.declare_generated_files( 111 ctx.actions, 112 extension = ext + ".c", 113 proto_info = proto_info, 114 ) 115 116 return GeneratedSrcsInfo( 117 srcs = srcs, 118 hdrs = hdrs, 119 ) 120 121def _generate_upb_protos(ctx, generator, proto_info, feature_configuration): 122 implicit_weak = generator == "upb_minitable" and cc_common.is_enabled( 123 feature_configuration = feature_configuration, 124 feature_name = "proto_one_output_per_message", 125 ) 126 127 srcs = _generate_srcs_list(ctx, generator, proto_info) 128 additional_args = ctx.actions.args() 129 130 if implicit_weak: 131 srcs.srcs.extend(_get_implicit_weak_field_sources(ctx, proto_info)) 132 additional_args.add("--upb_minitable_opt=one_output_per_message") 133 134 proto_common.compile( 135 actions = ctx.actions, 136 proto_info = proto_info, 137 proto_lang_toolchain_info = _get_lang_toolchain(ctx, generator), 138 generated_files = srcs.srcs + srcs.hdrs, 139 experimental_exec_group = "proto_compiler", 140 additional_args = additional_args, 141 ) 142 143 return srcs 144 145def _generate_name(ctx, generator): 146 return ctx.rule.attr.name + "." + generator 147 148def _get_dep_cc_infos(target, ctx, generator, cc_provider, dep_cc_provider): 149 rule_deps = ctx.rule.attr.deps 150 dep_ccinfos = [dep[cc_provider].cc_info for dep in rule_deps] 151 if dep_cc_provider: 152 # This gives access to our direct sibling. eg. foo.upb.h can #include "foo.upb_minitable.h" 153 dep_ccinfos.append(target[dep_cc_provider].cc_info) 154 155 # This gives access to imports. eg. foo.upb.h can #include "import1.upb_minitable.h" 156 # But not transitive imports, eg. foo.upb.h cannot #include "transitive_import1.upb_minitable.h" 157 dep_ccinfos += [dep[dep_cc_provider].cc_info for dep in rule_deps] 158 159 return dep_ccinfos 160 161def _get_lang_toolchain(ctx, generator): 162 lang_toolchain_name = "_" + generator + "_toolchain" 163 return getattr(ctx.attr, lang_toolchain_name)[proto_common.ProtoLangToolchainInfo] 164 165def _compile_upb_protos(ctx, files, generator, dep_ccinfos, cc_provider, proto_info): 166 cc_info = cc_library_func( 167 ctx = ctx, 168 name = _generate_name(ctx, generator), 169 hdrs = files.hdrs, 170 srcs = files.srcs, 171 includes = [output_dir(ctx, proto_info)], 172 copts = ctx.attr._copts[UpbProtoLibraryCoptsInfo].copts, 173 dep_ccinfos = dep_ccinfos, 174 ) 175 176 return cc_provider( 177 cc_info = cc_info, 178 ) 179 180_GENERATORS = ["upb", "upbdefs", "upb_minitable"] 181 182def _get_hint_providers(ctx, generator): 183 if generator not in _GENERATORS: 184 fail("Please add new generator '{}' to _GENERATORS list".format(generator)) 185 186 possible_owners = [] 187 for generator in _GENERATORS: 188 possible_owners.append(ctx.label.relative(_generate_name(ctx, generator))) 189 190 if hasattr(cc_common, "CcSharedLibraryHintInfo"): 191 return [cc_common.CcSharedLibraryHintInfo(owners = possible_owners)] 192 elif hasattr(cc_common, "CcSharedLibraryHintInfo_6_X_constructor_do_not_use"): 193 # This branch can be deleted once 6.X is not supported by upb rules 194 return [cc_common.CcSharedLibraryHintInfo_6_X_constructor_do_not_use(owners = possible_owners)] 195 196 return [] 197 198def upb_proto_aspect_impl( 199 target, 200 ctx, 201 generator, 202 cc_provider, 203 dep_cc_provider, 204 file_provider, 205 provide_cc_shared_library_hints = True): 206 """A shared aspect implementation for upb_*proto_library() rules. 207 208 Args: 209 target: The `target` parameter from the aspect function. 210 ctx: The `ctx` parameter from the aspect function. 211 generator: A string describing which aspect we are generating. This triggers several special 212 behaviors, and ideally this will be refactored to be less magical. 213 cc_provider: The provider that this aspect will attach to the target. Should contain a 214 `cc_info` field. The aspect will ensure that each compilation action can compile and link 215 against this provider's cc_info for all proto_library() deps. 216 dep_cc_provider: For aspects that depend on other aspects, this is the provider of the aspect 217 that we depend on. The aspect will be able to include the header files from this provider. 218 file_provider: A provider that this aspect will attach to the target to expose the source 219 files generated by this aspect. These files are primarily useful for returning in 220 DefaultInfo(), so users who build the upb_*proto_library() rule directly can view the 221 generated sources. 222 provide_cc_shared_library_hints: Whether shared library hints should be provided. 223 224 Returns: 225 The `cc_provider` and `file_provider` providers as described above. 226 """ 227 dep_ccinfos = _get_dep_cc_infos(target, ctx, generator, cc_provider, dep_cc_provider) 228 if not getattr(ctx.rule.attr, "srcs", []): 229 # This target doesn't declare any sources, reexport all its deps instead. 230 # This is known as an "alias library": 231 # https://bazel.build/reference/be/protocol-buffer#proto_library.srcs 232 files = _merge_generated_srcs([dep[file_provider].srcs for dep in ctx.rule.attr.deps]) 233 wrapped_cc_info = cc_provider( 234 cc_info = cc_common.merge_cc_infos(direct_cc_infos = dep_ccinfos), 235 ) 236 else: 237 proto_info = target[ProtoInfo] 238 cc_toolchain = find_cpp_toolchain(ctx) 239 feature_configuration = _get_feature_configuration(ctx, cc_toolchain, proto_info) 240 files = _generate_upb_protos( 241 ctx, 242 generator, 243 proto_info, 244 feature_configuration, 245 ) 246 wrapped_cc_info = _compile_upb_protos( 247 ctx, 248 files, 249 generator, 250 dep_ccinfos + [_get_lang_toolchain(ctx, generator).runtime[CcInfo]], 251 cc_provider, 252 proto_info, 253 ) 254 255 hints = _get_hint_providers(ctx, generator) if provide_cc_shared_library_hints else [] 256 257 return hints + [ 258 file_provider(srcs = files), 259 wrapped_cc_info, 260 ] 261