xref: /aosp_15_r20/external/bazelbuild-rules_go/proto/compiler.bzl (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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