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