1# Copyright 2017 The Bazel Authors. All rights reserved. 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( 16 "@bazel_skylib//lib:paths.bzl", 17 "paths", 18) 19load( 20 "//go:def.bzl", 21 "GoLibrary", 22 "go_context", 23) 24load( 25 "//go/private:go_toolchain.bzl", 26 "GO_TOOLCHAIN", 27) 28load( 29 "//go/private/rules:transition.bzl", 30 "go_reset_target", 31) 32 33GoProtoCompiler = provider( 34 doc = "Information and dependencies needed to generate Go code from protos", 35 fields = { 36 "compile": """A function with the signature: 37 38 def compile(go, compiler, protos, imports, importpath) 39 40where go is the go_context object, compiler is this GoProtoCompiler, protos 41is a list of ProtoInfo providers for protos to compile, imports is a depset 42of strings mapping proto import paths to Go import paths, and importpath is 43the import path of the Go library being generated. 44 45The function should declare output .go files and actions to generate them. 46It should return a list of .go Files to be compiled by the Go compiler. 47""", 48 "deps": """List of targets providing GoLibrary, GoSource, and GoArchive. 49These are added as implicit dependencies for any go_proto_library using this 50compiler. Typically, these are Well Known Types and proto runtime libraries.""", 51 "valid_archive": """A Boolean indicating whether the .go files produced 52by this compiler are buildable on their own. Compilers that just add methods 53to structs produced by other compilers will set this to False.""", 54 "internal": "Opaque value containing data used by compile.", 55 }, 56) 57 58def go_proto_compile(go, compiler, protos, imports, importpath): 59 """Invokes protoc to generate Go sources for a given set of protos 60 61 Args: 62 go: the go object, returned by go_context. 63 compiler: a GoProtoCompiler provider. 64 protos: list of ProtoInfo providers for protos to compile. 65 imports: depset of strings mapping proto import paths to Go import paths. 66 importpath: the import path of the Go library being generated. 67 68 Returns: 69 A list of .go Files generated by the compiler. 70 """ 71 72 go_srcs = [] 73 outpath = None 74 proto_paths = {} 75 desc_sets = [] 76 for proto in protos: 77 desc_sets.append(proto.transitive_descriptor_sets) 78 for src in proto.check_deps_sources.to_list(): 79 path = proto_path(src, proto) 80 if path in proto_paths: 81 if proto_paths[path] != src: 82 fail("proto files {} and {} have the same import path, {}".format( 83 src.path, 84 proto_paths[path].path, 85 path, 86 )) 87 continue 88 proto_paths[path] = src 89 90 out = go.declare_file( 91 go, 92 path = importpath + "/" + src.basename[:-len(".proto")], 93 ext = compiler.internal.suffix, 94 ) 95 go_srcs.append(out) 96 if outpath == None: 97 outpath = out.dirname[:-len(importpath)] 98 99 transitive_descriptor_sets = depset(direct = [], transitive = desc_sets) 100 101 args = go.actions.args() 102 args.add("-protoc", compiler.internal.protoc) 103 args.add("-importpath", importpath) 104 args.add("-out_path", outpath) 105 args.add("-plugin", compiler.internal.plugin) 106 107 # TODO(jayconrod): can we just use go.env instead? 108 args.add_all(compiler.internal.options, before_each = "-option") 109 if compiler.internal.import_path_option: 110 args.add_all([importpath], before_each = "-option", format_each = "import_path=%s") 111 args.add_all(transitive_descriptor_sets, before_each = "-descriptor_set") 112 args.add_all(go_srcs, before_each = "-expected") 113 args.add_all(imports, before_each = "-import") 114 args.add_all(proto_paths.keys()) 115 args.use_param_file("-param=%s") 116 go.actions.run( 117 inputs = depset( 118 direct = [ 119 compiler.internal.go_protoc, 120 compiler.internal.protoc, 121 compiler.internal.plugin, 122 ], 123 transitive = [transitive_descriptor_sets], 124 ), 125 outputs = go_srcs, 126 progress_message = "Generating into %s" % go_srcs[0].dirname, 127 mnemonic = "GoProtocGen", 128 executable = compiler.internal.go_protoc, 129 arguments = [args], 130 env = go.env, 131 # We may need the shell environment (potentially augmented with --action_env) 132 # to invoke protoc on Windows. If protoc was built with mingw, it probably needs 133 # .dll files in non-default locations that must be in PATH. The target configuration 134 # may not have a C compiler, so we have no idea what PATH should be. 135 use_default_shell_env = "PATH" not in go.env, 136 ) 137 return go_srcs 138 139def proto_path(src, proto): 140 """proto_path returns the string used to import the proto. This is the proto 141 source path within its repository, adjusted by import_prefix and 142 strip_import_prefix. 143 144 Args: 145 src: the proto source File. 146 proto: the ProtoInfo provider. 147 148 Returns: 149 An import path string. 150 """ 151 if proto.proto_source_root == ".": 152 # true if proto sources were generated 153 prefix = src.root.path + "/" 154 elif proto.proto_source_root.startswith(src.root.path): 155 # sometimes true when import paths are adjusted with import_prefix 156 prefix = proto.proto_source_root + "/" 157 else: 158 # usually true when paths are not adjusted 159 prefix = paths.join(src.root.path, proto.proto_source_root) + "/" 160 if not src.path.startswith(prefix): 161 # sometimes true when importing multiple adjusted protos 162 return src.path 163 return src.path[len(prefix):] 164 165def _go_proto_compiler_impl(ctx): 166 go = go_context(ctx) 167 library = go.new_library(go) 168 source = go.library_to_source(go, ctx.attr, library, ctx.coverage_instrumented()) 169 return [ 170 GoProtoCompiler( 171 deps = ctx.attr.deps, 172 compile = go_proto_compile, 173 valid_archive = ctx.attr.valid_archive, 174 internal = struct( 175 options = ctx.attr.options, 176 suffix = ctx.attr.suffix, 177 protoc = ctx.executable._protoc, 178 go_protoc = ctx.executable._go_protoc, 179 plugin = ctx.executable.plugin, 180 import_path_option = ctx.attr.import_path_option, 181 ), 182 ), 183 library, 184 source, 185 ] 186 187_go_proto_compiler = rule( 188 implementation = _go_proto_compiler_impl, 189 attrs = { 190 "deps": attr.label_list(providers = [GoLibrary]), 191 "options": attr.string_list(), 192 "suffix": attr.string(default = ".pb.go"), 193 "valid_archive": attr.bool(default = True), 194 "import_path_option": attr.bool(default = False), 195 "plugin": attr.label( 196 executable = True, 197 cfg = "exec", 198 mandatory = True, 199 ), 200 "_go_protoc": attr.label( 201 executable = True, 202 cfg = "exec", 203 default = "//go/tools/builders:go-protoc", 204 ), 205 "_protoc": attr.label( 206 executable = True, 207 cfg = "exec", 208 default = "//proto:protoc", 209 ), 210 "_go_context_data": attr.label( 211 default = "//:go_context_data", 212 ), 213 }, 214 toolchains = [GO_TOOLCHAIN], 215) 216 217def go_proto_compiler(name, **kwargs): 218 plugin = kwargs.pop("plugin", "@com_github_golang_protobuf//protoc-gen-go") 219 reset_plugin_name = name + "_reset_plugin_" 220 go_reset_target( 221 name = reset_plugin_name, 222 dep = plugin, 223 visibility = ["//visibility:private"], 224 ) 225 _go_proto_compiler( 226 name = name, 227 plugin = reset_plugin_name, 228 **kwargs 229 ) 230