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