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