# 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: <login>
and
<password>
, 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 Authorization
field of the HTTP request.
Example attribute and netrc for a http download to an oauth2 enabled API using a bearer token:
auth_patterns = { "storage.cloudprovider.com": "Bearer <password>" }netrc:
machine storage.cloudprovider.com password RANDOM-TOKENThe final HTTP request would have the following header:
Authorization: Bearer RANDOM-TOKEN""" # 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 # `