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