github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/rules/proto_gazelle.bzl (about) 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 15 """proto_gazelle.bzl provides the proto_gazelle rule. 16 """ 17 18 load( 19 "@bazel_skylib//lib:shell.bzl", 20 "shell", 21 ) 22 23 DEFAULT_LANGUAGES = [ 24 "@bazel_gazelle//language/proto:go_default_library", 25 "@bazel_gazelle//language/go:go_default_library", 26 "@build_stack_rules_proto//language/protobuf", 27 ] 28 29 def _valid_env_variable_name(name): 30 """ Returns if a string is in the regex [a-zA-Z_][a-zA-Z0-9_]* 31 32 Given that bazel lacks support of regex, we need to implement 33 a poor man validation 34 """ 35 if not name: 36 return False 37 for i, c in enumerate(name.elems()): 38 if c.isalpha() or c == "_" or (i > 0 and c.isdigit()): 39 continue 40 return False 41 return True 42 43 def _rlocation_path(ctx, file): 44 if file.short_path.startswith("../"): 45 return file.short_path[3:] 46 else: 47 return ctx.workspace_name + "/" + file.short_path 48 49 def _gazelle_runner_impl(ctx): 50 args = [ctx.attr.command] 51 if ctx.attr.mode: 52 args.extend(["-mode", ctx.attr.mode]) 53 if ctx.attr.external: 54 args.extend(["-external", ctx.attr.external]) 55 if ctx.attr.prefix: 56 args.extend(["-go_prefix", ctx.attr.prefix]) 57 if ctx.attr.build_tags: 58 args.extend(["-build_tags", ",".join(ctx.attr.build_tags)]) 59 if ctx.attr.cfgs: 60 cfgs = ",".join([f.short_path for f in ctx.files.cfgs]) 61 args.extend(["-proto_configs", cfgs]) 62 if ctx.attr.imports: 63 imports = ",".join([f.short_path for f in ctx.files.imports]) 64 args.extend(["-proto_imports_in", imports]) 65 66 args.extend([ctx.expand_location(arg, ctx.attr.data) for arg in ctx.attr.extra_args]) 67 68 for key in ctx.attr.env: 69 if not _valid_env_variable_name(key): 70 fail("Invalid environmental variable name: '%s'" % key) 71 72 env = "\n".join(["export %s=%s" % (x, shell.quote(y)) for (x, y) in ctx.attr.env.items()]) 73 74 out_file = ctx.actions.declare_file(ctx.label.name + ".bash") 75 go_tool = ctx.toolchains["@io_bazel_rules_go//go:toolchain"].sdk.go 76 repo_config = ctx.file._repo_config 77 substitutions = { 78 "@@ARGS@@": shell.array_literal(args), 79 "@@GAZELLE_PATH@@": shell.quote(_rlocation_path(ctx, ctx.executable.gazelle)), 80 "@@GENERATED_MESSAGE@@": """ 81 # Generated by {label} 82 # DO NOT EDIT 83 """.format(label = str(ctx.label)), 84 "@@GOTOOL@@": shell.quote(_rlocation_path(ctx, go_tool)), 85 "@@ENV@@": env, 86 "@@REPO_CONFIG_PATH@@": shell.quote(_rlocation_path(ctx, repo_config)) if repo_config else "", 87 } 88 ctx.actions.expand_template( 89 template = ctx.file._template, 90 output = out_file, 91 substitutions = substitutions, 92 is_executable = True, 93 ) 94 runfiles = ctx.runfiles(files = ctx.files.cfgs + ctx.files.imports + [ 95 ctx.executable.gazelle, 96 go_tool, 97 ] + ([repo_config] if repo_config else [])).merge( 98 ctx.attr.gazelle[DefaultInfo].default_runfiles, 99 ) 100 for d in ctx.attr.data: 101 runfiles = runfiles.merge(d[DefaultInfo].default_runfiles) 102 return [DefaultInfo( 103 files = depset([out_file]), 104 runfiles = runfiles, 105 executable = out_file, 106 )] 107 108 _gazelle_runner = rule( 109 implementation = _gazelle_runner_impl, 110 attrs = { 111 "gazelle": attr.label( 112 default = "@build_stack_rules_proto//cmd/gazelle", 113 executable = True, 114 cfg = "exec", 115 ), 116 "command": attr.string( 117 values = [ 118 "fix", 119 "update", 120 "update-repos", 121 ], 122 default = "update", 123 ), 124 "mode": attr.string( 125 values = ["", "print", "fix", "diff"], 126 default = "", 127 ), 128 "external": attr.string( 129 values = ["", "external", "static", "vendored"], 130 default = "", 131 ), 132 "build_tags": attr.string_list(), 133 "prefix": attr.string(), 134 "extra_args": attr.string_list(), 135 "data": attr.label_list(allow_files = True), 136 "imports": attr.label_list(allow_files = True), 137 "cfgs": attr.label_list(allow_files = True), 138 "env": attr.string_dict(), 139 "_repo_config": attr.label( 140 default = None, 141 allow_single_file = True, 142 ), 143 "_template": attr.label( 144 default = "@bazel_gazelle//internal:gazelle.bash.in", 145 allow_single_file = True, 146 ), 147 }, 148 executable = True, 149 toolchains = ["@io_bazel_rules_go//go:toolchain"], 150 ) 151 152 def proto_gazelle(name, **kwargs): 153 """proto_gazelle is the macro that calls the gazelle runner 154 155 Args: 156 name: the name of the rule 157 **kwargs: arguments to gazelle_runner 158 """ 159 if "args" in kwargs: 160 # The args attribute has special meaning for executable rules, but we 161 # always want extra_args here instead. 162 if "extra_args" in kwargs: 163 fail("{}: both args and extra_args were provided".format(name)) 164 kwargs["extra_args"] = kwargs["args"] 165 kwargs.pop("args") 166 167 visibility = kwargs.pop("visibility", default = None) 168 169 tags_set = {t: "" for t in kwargs.pop("tags", [])} 170 tags_set["manual"] = "" 171 tags = [k for k in tags_set.keys()] 172 runner_name = name + "-runner" 173 _gazelle_runner( 174 name = runner_name, 175 tags = tags, 176 **kwargs 177 ) 178 native.sh_binary( 179 name = name, 180 srcs = [runner_name], 181 tags = tags, 182 visibility = visibility, 183 deps = ["@bazel_tools//tools/bash/runfiles"], 184 data = kwargs["data"] if "data" in kwargs else [], 185 )