forked from bazel-contrib/bazel-gazelle
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgo_repository.bzl
577 lines (515 loc) · 24.1 KB
/
go_repository.bzl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# Copyright 2014 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("//internal:common.bzl", "env_execute", "executable_extension")
load("//internal:go_repository_cache.bzl", "read_cache_env")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "patch", "read_user_netrc", "use_netrc")
_DOC = """
`go_repository` downloads a Go project and generates build files with Gazelle
if they are not already present. This is the simplest way to depend on
external Go projects.
When `go_repository` is in module mode, it saves downloaded modules in a shared,
internal cache within Bazel's cache. It may be cleared with `bazel clean --expunge`.
By setting the environment variable `GO_REPOSITORY_USE_HOST_CACHE=1`, you can
force `go_repository` to use the module cache on the host system in the location
returned by `go env GOPATH`. Alternatively, by setting the environment variable
`GO_REPOSITORY_USE_HOST_MODCACHE=1`, you can force `go_repository` to use only
the module cache on the host system in the location returned by `go env GOMODCACHE`.
**Example**
```starlark
load("@bazel_gazelle//:deps.bzl", "go_repository")
# Download using "go mod download"
go_repository(
name = "com_github_pkg_errors",
importpath = "github.com/pkg/errors",
sum = "h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=",
version = "v0.8.1",
)
# Download automatically via git
go_repository(
name = "com_github_pkg_errors",
commit = "816c9085562cd7ee03e7f8188a1cfd942858cded",
importpath = "github.com/pkg/errors",
)
# Download from git fork
go_repository(
name = "com_github_pkg_errors",
commit = "816c9085562cd7ee03e7f8188a1cfd942858cded",
importpath = "github.com/pkg/errors",
remote = "https://example.com/fork/github.com/pkg/errors",
vcs = "git",
)
# Download via HTTP
go_repository(
name = "com_github_pkg_errors",
importpath = "github.com/pkg/errors",
urls = ["https://codeload.github.com/pkg/errors/zip/816c9085562cd7ee03e7f8188a1cfd942858cded"],
strip_prefix = "errors-816c9085562cd7ee03e7f8188a1cfd942858cded",
type = "zip",
)
# Download major version suffixed via git
go_repository(
name = "com_github_thediveo_enumflag_v2",
commit = "0217df583bf3d37b92798602e5061b36556bcd38",
importpath = "github.com/thediveo/enumflag/v2",
remote = "https://github.com/thediveo/enumflag",
vcs = "git",
)
```
"""
# copied from
# https://github.com/bazelbuild/bazel/blob/d273cb62f43ef8169415cf60fc96e503ea2ad823/tools/build_defs/repo/http.bzl#L76
_AUTH_PATTERN_DOC = """An optional dict mapping host names to custom authorization patterns.
If a URL's host name is present in this dict the value will be used as a pattern when
generating the authorization header for the http request. This enables the use of custom
authorization schemes used in a lot of common cloud storage providers.
The pattern currently supports 2 tokens: <code><login></code> and
<code><password></code>, which are replaced with their equivalent value
in the netrc file for the same host name. After formatting, the result is set
as the value for the <code>Authorization</code> field of the HTTP request.
Example attribute and netrc for a http download to an oauth2 enabled API using a bearer token:
<pre>
auth_patterns = {
"storage.cloudprovider.com": "Bearer <password>"
}
</pre>
netrc:
<pre>
machine storage.cloudprovider.com
password RANDOM-TOKEN
</pre>
The final HTTP request would have the following header:
<pre>
Authorization: Bearer RANDOM-TOKEN
</pre>
"""
# We can't disable timeouts on Bazel, but we can set them to large values.
_GO_REPOSITORY_TIMEOUT = 86400
def _go_repository_impl(ctx):
# TODO(#549): vcs repositories are not cached and still need to be fetched.
# Download the repository or module.
fetch_repo_args = None
gazelle_path = None
# Declare Label dependencies at the top of function to avoid unnecessary fetching:
# https://docs.bazel.build/versions/main/skylark/repository_rules.html#when-is-the-implementation-function-executed
go_env_cache = str(ctx.path(Label("@bazel_gazelle_go_repository_cache//:go.env")))
if not ctx.attr.urls:
fetch_repo = str(ctx.path(Label("@bazel_gazelle_go_repository_tools//:bin/fetch_repo{}".format(executable_extension(ctx)))))
generate = ctx.attr.build_file_generation == "on"
_gazelle = "@bazel_gazelle_go_repository_tools//:bin/gazelle{}".format(executable_extension(ctx))
if generate:
gazelle_path = ctx.path(Label(_gazelle))
if ctx.attr.urls:
# HTTP mode
for key in ("commit", "tag", "vcs", "remote", "version", "sum", "replace"):
if getattr(ctx.attr, key):
fail("cannot specifiy both urls and %s" % key, key)
result = ctx.download_and_extract(
url = ctx.attr.urls,
sha256 = ctx.attr.sha256,
canonical_id = ctx.attr.canonical_id,
stripPrefix = ctx.attr.strip_prefix,
type = ctx.attr.type,
auth = use_netrc(read_user_netrc(ctx), ctx.attr.urls, ctx.attr.auth_patterns),
)
if not ctx.attr.sha256:
print("For Go module \"{path}\", integrity not specified, calculated sha256 = \"{sha256}\"".format(
path = ctx.attr.importpath,
sha256 = result.sha256,
))
elif ctx.attr.commit or ctx.attr.tag:
# repository mode
if ctx.attr.commit:
rev = ctx.attr.commit
rev_key = "commit"
elif ctx.attr.tag:
rev = ctx.attr.tag
rev_key = "tag"
for key in ("urls", "strip_prefix", "type", "sha256", "version", "sum", "replace", "canonical_id"):
if getattr(ctx.attr, key):
fail("cannot specify both %s and %s" % (rev_key, key), key)
if ctx.attr.vcs and not ctx.attr.remote:
fail("if vcs is specified, remote must also be")
fetch_repo_args = ["-dest", ctx.path(""), "-importpath", ctx.attr.importpath]
if ctx.attr.remote:
fetch_repo_args.extend(["--remote", ctx.attr.remote])
if rev:
fetch_repo_args.extend(["--rev", rev])
if ctx.attr.vcs:
fetch_repo_args.extend(["--vcs", ctx.attr.vcs])
elif ctx.attr.version:
# module mode
for key in ("urls", "strip_prefix", "type", "sha256", "commit", "tag", "vcs", "remote"):
if getattr(ctx.attr, key):
fail("cannot specify both version and %s" % key)
if not ctx.attr.sum:
fail("if version is specified, sum must also be")
fetch_path = ctx.attr.replace if ctx.attr.replace else ctx.attr.importpath
fetch_repo_args = [
"-dest=" + str(ctx.path("")),
"-importpath=" + fetch_path,
"-version=" + ctx.attr.version,
"-sum=" + ctx.attr.sum,
]
else:
fail("one of urls, commit, tag, or version must be specified")
env = read_cache_env(ctx, go_env_cache)
env_keys = [
# Respect user proxy and sumdb settings for privacy.
# TODO(jayconrod): gazelle in go_repository mode should probably
# not go out to the network at all. This means *the build*
# goes out to the network. We tolerate this for downloading
# archives, but finding module roots is a bit much.
"GOPROXY",
"GONOPROXY",
"GOPRIVATE",
"GOSUMDB",
"GONOSUMDB",
# PATH is needed to locate git and other vcs tools.
"PATH",
# HOME is needed to locate vcs configuration files (.gitconfig).
"HOME",
# Settings below are used by vcs tools.
"SSH_AUTH_SOCK",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
"HTTP_PROXY",
"HTTPS_PROXY",
"NO_PROXY",
"http_proxy",
"https_proxy",
"no_proxy",
"GIT_SSL_CAINFO",
"GIT_SSH",
"GIT_SSH_COMMAND",
"GIT_CONFIG_COUNT",
]
# Git allows passing configuration through environmental variables, this will be picked
# by go get properly: https://www.git-scm.com/docs/git-config/#Documentation/git-config.txt-GITCONFIGCOUNT
if "GIT_CONFIG_COUNT" in ctx.os.environ:
count = ctx.os.environ["GIT_CONFIG_COUNT"]
if count:
if not count.isdigit or int(count) < 1:
fail("GIT_CONFIG_COUNT has to be a positive integer")
count = int(count)
for i in range(count):
key = "GIT_CONFIG_KEY_%d" % i
value = "GIT_CONFIG_VALUE_%d" % i
for j in [key, value]:
if j not in ctx.os.environ:
fail("%s is not defined as an environment variable, but you asked for GIT_COUNT_COUNT=%d" % (j, count))
env_keys = env_keys + [key, value]
env.update({k: ctx.os.environ[k] for k in env_keys if k in ctx.os.environ})
if fetch_repo_args:
# Disable sumdb in fetch_repo. In module mode, the sum is a mandatory
# attribute of go_repository, so we don't need to look it up.
fetch_repo_env = dict(env)
fetch_repo_env["GOSUMDB"] = "off"
# Override external GO111MODULE, because it is needed by module mode, no-op in repository mode
fetch_repo_env["GO111MODULE"] = "on"
result = env_execute(
ctx,
[fetch_repo] + fetch_repo_args,
environment = fetch_repo_env,
timeout = _GO_REPOSITORY_TIMEOUT,
)
if result.return_code:
fail("failed to fetch %s: %s" % (ctx.name, result.stderr))
if ctx.attr.debug_mode and result.stderr:
print("fetch_repo: " + result.stderr)
# Repositories are fetched. Determine if build file generation is needed.
build_file_names = ctx.attr.build_file_name.split(",")
existing_build_file = ""
for name in build_file_names:
path = ctx.path(name)
if path.exists and not env_execute(ctx, ["test", "-f", path]).return_code:
existing_build_file = name
break
generate = generate or (not existing_build_file and ctx.attr.build_file_generation == "auto")
if generate:
# Build file generation is needed. Populate Gazelle directive at root build file
build_file_name = existing_build_file or build_file_names[0]
if len(ctx.attr.build_directives) > 0:
ctx.file(
build_file_name,
"\n".join(["# " + d for d in ctx.attr.build_directives]),
)
# Run Gazelle
if gazelle_path == None:
gazelle_path = ctx.path(Label(_gazelle))
# ctx.attr.name is the canonical name of this repository, which contains a '~' if and only
# if this repository is generated by a module extension rather than an invocation in
# WORKSPACE.
is_module_extension_repo = "~" in ctx.attr.name
if is_module_extension_repo:
# TODO: In Bazel 6.3.0 and earlier, there is no way to obtain a label referencing a repo
# generated by an extension from within that extension. We thus have to manually
# construct such a label pointing to the sibling `_go_repository_config` repo created by
# the `go_deps` extension. All extension-generated repos have names of the form
# `<prefix>~<name set by the extension>`.
extension_repo_prefix = ctx.attr.name.rpartition("~")[0] + "~"
repo_config = ctx.path(Label("@@" + extension_repo_prefix + "bazel_gazelle_go_repository_config//:WORKSPACE"))
else:
repo_config = ctx.path(ctx.attr.build_config)
cmd = [
gazelle_path,
"-go_repository_mode",
"-go_prefix",
ctx.attr.importpath,
"-mode",
"fix",
"-repo_root",
ctx.path(""),
"-repo_config",
repo_config,
]
if ctx.attr.version:
cmd.append("-go_repository_module_mode")
if ctx.attr.build_file_name:
cmd.extend(["-build_file_name", ctx.attr.build_file_name])
if ctx.attr.build_tags:
cmd.extend(["-build_tags", ",".join(ctx.attr.build_tags)])
if ctx.attr.build_external:
cmd.extend(["-external", ctx.attr.build_external])
if ctx.attr.build_file_proto_mode:
cmd.extend(["-proto", ctx.attr.build_file_proto_mode])
if ctx.attr.build_naming_convention:
cmd.extend(["-go_naming_convention", ctx.attr.build_naming_convention])
if is_module_extension_repo:
cmd.append("-bzlmod")
cmd.extend(ctx.attr.build_extra_args)
cmd.append(ctx.path(""))
ctx.report_progress("running Gazelle")
result = env_execute(ctx, cmd, environment = env, timeout = _GO_REPOSITORY_TIMEOUT)
if result.return_code:
fail("failed to generate BUILD files for %s: %s" % (
ctx.attr.importpath,
result.stderr,
))
if ctx.attr.debug_mode and result.stderr:
print("%s gazelle.stdout: %s" % (ctx.name, result.stdout))
print("%s gazelle.stderr: %s" % (ctx.name, result.stderr))
# Apply patches if necessary.
patch(ctx)
go_repository = repository_rule(
implementation = _go_repository_impl,
doc = _DOC,
attrs = {
# Fundamental attributes of a go repository
"importpath": attr.string(
doc = """The Go import path that matches the root directory of this repository.
In module mode (when `version` is set), this must be the module path. If
neither `urls` nor `remote` is specified, `go_repository` will
automatically find the true path of the module, applying import path
redirection.
If build files are generated for this repository, libraries will have their
`importpath` attributes prefixed with this `importpath` string. """,
mandatory = True,
),
# Attributes for a repository that should be checked out from VCS
"commit": attr.string(
doc = """If the repository is downloaded using a version control tool, this is the
commit or revision to check out. With git, this would be a sha1 commit id.
`commit` and `tag` may not both be set.""",
),
"tag": attr.string(
doc = """If the repository is downloaded using a version control tool, this is the
named revision to check out. `commit` and `tag` may not both be set.""",
),
"vcs": attr.string(
default = "",
doc = """One of `"git"`, `"hg"`, `"svn"`, `"bzr"`.
The version control system to use. This is usually determined automatically,
but it may be necessary to set this when `remote` is set and the VCS cannot
be inferred. You must have the corresponding tool installed on your host.""",
values = [
"",
"git",
"hg",
"svn",
"bzr",
],
),
"remote": attr.string(
doc = """The VCS location where the repository should be downloaded from. This is
usually inferred from `importpath`, but you can set `remote` to download
from a private repository or a fork.""",
),
# Attributes for a repository that should be downloaded via HTTP.
"urls": attr.string_list(
doc = """A list of HTTP(S) URLs where an archive containing the project can be
downloaded. Bazel will attempt to download from the first URL; the others
are mirrors.""",
),
"strip_prefix": attr.string(
doc = """If the repository is downloaded via HTTP (`urls` is set), this is a
directory prefix to strip. See [`http_archive.strip_prefix`].""",
),
"type": attr.string(
doc = """One of `"zip"`, `"tar.gz"`, `"tgz"`, `"tar.bz2"`, `"tar.xz"`.
If the repository is downloaded via HTTP (`urls` is set), this is the
file format of the repository archive. This is normally inferred from the
downloaded file name.""",
),
"sha256": attr.string(
doc = """If the repository is downloaded via HTTP (`urls` is set), this is the
SHA-256 sum of the downloaded archive. When set, Bazel will verify the archive
against this sum before extracting it.
**CAUTION:** Do not use this with services that prepare source archives on
demand, such as codeload.github.com. Any minor change in the server software
can cause differences in file order, alignment, and compression that break
SHA-256 sums.""",
),
"canonical_id": attr.string(
doc = """If the repository is downloaded via HTTP (`urls` is set) and this is set, restrict cache hits to those cases where the
repository was added to the cache with the same canonical id.""",
),
"auth_patterns": attr.string_dict(
doc = _AUTH_PATTERN_DOC,
),
# Attributes for a module that should be downloaded with the Go toolchain.
"version": attr.string(
doc = """If specified, `go_repository` will download the module at this version
using `go mod download`. `sum` must also be set. `commit`, `tag`,
and `urls` may not be set. """,
),
"sum": attr.string(
doc = """A hash of the module contents. In module mode, `go_repository` will verify
the downloaded module matches this sum. May only be set when `version`
is also set.
A value for `sum` may be found in the `go.sum` file or by running
`go mod download -json <module>@<version>`.""",
),
"replace": attr.string(
doc = """A replacement for the module named by `importpath`. The module named by
`replace` will be downloaded at `version` and verified with `sum`.
NOTE: There is no `go_repository` equivalent to file path `replace`
directives. Use `local_repository` instead.""",
),
# Attributes for a repository that needs automatic build file generation
"build_external": attr.string(
default = "static",
doc = """One of `"external"`, `"static"` or `"vendored"`.
This sets Gazelle's `-external` command line flag. In `"static"` mode,
Gazelle will not call out to the network to resolve imports.
**NOTE:** This cannot be used to ignore the `vendor` directory in a
repository. The `-external` flag only controls how Gazelle resolves
imports which are not present in the repository. Use
`build_extra_args = ["-exclude=vendor"]` instead.""",
values = [
"",
"external",
"static",
"vendored",
],
),
"build_file_name": attr.string(
default = "BUILD.bazel,BUILD",
doc = """Comma-separated list of names Gazelle will consider to be build files.
If a repository contains files named `build` that aren't related to Bazel,
it may help to set this to `"BUILD.bazel"`, especially on case-insensitive
file systems.""",
),
"build_file_generation": attr.string(
default = "auto",
doc = """One of `"auto"`, `"on"`, `"off"`.
Whether Gazelle should generate build files in the repository. In `"auto"`
mode, Gazelle will run if there is no build file in the repository root
directory.""",
values = [
"on",
"auto",
"off",
],
),
"build_naming_convention": attr.string(
values = [
"go_default_library",
"import",
"import_alias",
],
default = "import_alias",
doc = """Sets the library naming convention to use when resolving dependencies against this external
repository. If unset, the convention from the external workspace is used.
Legal values are `go_default_library`, `import`, and `import_alias`.
See the `gazelle:go_naming_convention` directive in [Directives] for more information.""",
),
"build_tags": attr.string_list(
doc = "This sets Gazelle's `-build_tags` command line flag.",
),
"build_file_proto_mode": attr.string(
doc = """One of `"default"`, `"legacy"`, `"disable"`, `"disable_global"` or `"package"`.
This sets Gazelle's `-proto` command line flag. See [Directives] for more
information on each mode.""",
values = [
"",
"default",
"package",
"disable",
"disable_global",
"legacy",
],
),
"build_extra_args": attr.string_list(
doc = "A list of additional command line arguments to pass to Gazelle when generating build files.",
),
"build_config": attr.label(
default = "@bazel_gazelle_go_repository_config//:WORKSPACE",
doc = """A file that Gazelle should read to learn about external repositories before
generating build files. This is useful for dependency resolution. For example,
a `go_repository` rule in this file establishes a mapping between a
repository name like `golang.org/x/tools` and a workspace name like
`org_golang_x_tools`. Workspace directives like
`# gazelle:repository_macro` are recognized.
`go_repository` rules will be re-evaluated when parts of WORKSPACE related
to Gazelle's configuration are changed, including Gazelle directives and
`go_repository` `name` and `importpath` attributes.
Their content should still be fetched from a local cache, but build files
will be regenerated. If this is not desirable, `build_config` may be set
to a less frequently updated file or `None` to disable this functionality.""",
),
"build_directives": attr.string_list(
default = [],
doc = """A list of directives to be written to the root level build file before
Calling Gazelle to generate build files. Each string in the list will be
prefixed with `#` automatically. A common use case is to pass a list of
Gazelle directives.""",
),
# Patches to apply after running gazelle.
"patches": attr.label_list(
doc = "A list of patches to apply to the repository after gazelle runs.",
),
"patch_tool": attr.string(
default = "",
doc = """The patch tool used to apply `patches`. If this is specified, Bazel will
use the specifed patch tool instead of the Bazel-native patch implementation.""",
),
"patch_args": attr.string_list(
default = ["-p0"],
doc = "Arguments passed to the patch tool when applying patches.",
),
"patch_cmds": attr.string_list(
default = [],
doc = "Commands to run in the repository after patches are applied.",
),
# Attributes that affect the verbosity of logging:
"debug_mode": attr.bool(
default = False,
doc = """Enables logging of fetch_repo and Gazelle output during succcesful runs. Gazelle can be noisy
so this defaults to `False`. However, setting to `True` can be useful for debugging build failures and
unexpected behavior for the given rule.
""",
),
},
)
"""See repository.md#go-repository for full documentation."""